Merge branch 'claude/add-memory-layer-De82O' into extension/claude-mem
This commit is contained in:
commit
cc60285f49
390
docs/guides/claude-mem-setup.md
Normal file
390
docs/guides/claude-mem-setup.md
Normal file
@ -0,0 +1,390 @@
|
||||
# Clawdbot + Claude-Mem Setup Guide
|
||||
|
||||
Complete setup guide for integrating Clawdbot with Claude-Mem persistent memory.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This guide walks you through:
|
||||
1. Installing Clawdbot (AI messaging gateway)
|
||||
2. Installing Claude-Mem (persistent memory system)
|
||||
3. Connecting them via the `memory-claudemem` plugin
|
||||
|
||||
**What you get:**
|
||||
- Clawdbot routes messages between you and AI across multiple channels (Telegram, Discord, WhatsApp, etc.)
|
||||
- Claude-Mem captures observations from every tool call and injects relevant context into future sessions
|
||||
- Progressive disclosure memory tools (`memory_search`, `memory_observations`)
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Node.js 22+** (required for both Clawdbot and Claude-Mem)
|
||||
- **macOS, Linux, or Windows (WSL2)**
|
||||
- **Git** (for installing from source)
|
||||
|
||||
Check your Node version:
|
||||
```bash
|
||||
node -v
|
||||
# Should show v22.x.x or higher
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 1: Install Clawdbot
|
||||
|
||||
### Option A: Quick Install (Recommended)
|
||||
|
||||
```bash
|
||||
curl -fsSL https://clawd.bot/install.sh | bash
|
||||
```
|
||||
|
||||
Windows (PowerShell):
|
||||
```powershell
|
||||
iwr -useb https://clawd.bot/install.ps1 | iex
|
||||
```
|
||||
|
||||
This:
|
||||
- Installs Node 22+ if needed
|
||||
- Installs `clawdbot` globally via npm
|
||||
- Runs the onboarding wizard
|
||||
|
||||
### Option B: Manual Install
|
||||
|
||||
If you already have Node 22+:
|
||||
|
||||
```bash
|
||||
npm install -g clawdbot@latest
|
||||
```
|
||||
|
||||
Then run onboarding:
|
||||
|
||||
```bash
|
||||
clawdbot onboard --install-daemon
|
||||
```
|
||||
|
||||
### Verify Installation
|
||||
|
||||
```bash
|
||||
clawdbot --version
|
||||
clawdbot doctor
|
||||
```
|
||||
|
||||
### Common Issue: "clawdbot not found"
|
||||
|
||||
If your shell can't find `clawdbot`, add npm's global bin to your PATH:
|
||||
|
||||
```bash
|
||||
# Add to ~/.zshrc or ~/.bashrc
|
||||
export PATH="$(npm prefix -g)/bin:$PATH"
|
||||
```
|
||||
|
||||
Then restart your terminal or run `source ~/.zshrc`.
|
||||
|
||||
---
|
||||
|
||||
## Part 2: Install Claude-Mem
|
||||
|
||||
Claude-Mem is a separate system that provides persistent memory. You need to install it independently.
|
||||
|
||||
### Option A: Via Claude Code Plugin (if using Claude Code)
|
||||
|
||||
If you use Claude Code (Anthropic's CLI), install claude-mem as a plugin:
|
||||
|
||||
```bash
|
||||
# In a Claude Code session:
|
||||
/plugin marketplace add thedotmack/claude-mem
|
||||
/plugin install claude-mem
|
||||
```
|
||||
|
||||
Restart Claude Code. The worker starts automatically.
|
||||
|
||||
### Option B: Manual Installation (Standalone)
|
||||
|
||||
Clone the repository:
|
||||
|
||||
```bash
|
||||
cd ~/Scripts # or wherever you keep projects
|
||||
git clone https://github.com/thedotmack/claude-mem.git
|
||||
cd claude-mem
|
||||
```
|
||||
|
||||
Install dependencies and build:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
Start the worker:
|
||||
|
||||
```bash
|
||||
npm run worker
|
||||
# Or run in background:
|
||||
nohup npm run worker > /tmp/claude-mem-worker.log 2>&1 &
|
||||
```
|
||||
|
||||
### Verify Claude-Mem is Running
|
||||
|
||||
The worker runs on port 37777 by default.
|
||||
|
||||
```bash
|
||||
curl http://localhost:37777/api/health
|
||||
# Should return: OK or {"status":"ok"}
|
||||
```
|
||||
|
||||
Open the web viewer:
|
||||
```bash
|
||||
open http://localhost:37777
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 3: Enable the Memory-Claudemem Plugin
|
||||
|
||||
The `memory-claudemem` plugin connects Clawdbot to the Claude-Mem worker.
|
||||
|
||||
### Step 1: Disable Built-in Memory (Optional but Recommended)
|
||||
|
||||
Clawdbot has its own memory system. To avoid conflicts, disable it:
|
||||
|
||||
```bash
|
||||
clawdbot config set agents.defaults.memorySearch.enabled false
|
||||
```
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
clawdbot config get agents.defaults.memorySearch.enabled
|
||||
# Should return: false
|
||||
```
|
||||
|
||||
### Step 2: Enable the Claude-Mem Plugin
|
||||
|
||||
The plugin is bundled in the `extensions/memory-claudemem` directory. Enable it:
|
||||
|
||||
```bash
|
||||
clawdbot config set plugins.memory-claudemem.workerUrl "http://localhost:37777"
|
||||
```
|
||||
|
||||
Optional: Adjust timeout (default 10 seconds):
|
||||
```bash
|
||||
clawdbot config set plugins.memory-claudemem.workerTimeout 15000
|
||||
```
|
||||
|
||||
### Step 3: Verify Plugin Status
|
||||
|
||||
Check the worker connection:
|
||||
|
||||
```bash
|
||||
clawdbot claude-mem status
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
✓ Worker running at http://localhost:37777
|
||||
```
|
||||
|
||||
If you see `✗ Worker not responding`, make sure Claude-Mem is running (see Part 2).
|
||||
|
||||
---
|
||||
|
||||
## Part 4: Test the Integration
|
||||
|
||||
### Test 1: Search Memories
|
||||
|
||||
```bash
|
||||
clawdbot claude-mem search "authentication"
|
||||
```
|
||||
|
||||
This queries Claude-Mem for observations matching "authentication".
|
||||
|
||||
### Test 2: Run an Agent with Memory
|
||||
|
||||
Send a test message through Clawdbot:
|
||||
|
||||
```bash
|
||||
clawdbot agent --message "List files in the current directory"
|
||||
```
|
||||
|
||||
This should:
|
||||
1. Execute the task
|
||||
2. Record the observation to Claude-Mem (fire-and-forget)
|
||||
3. Future queries will have this context injected
|
||||
|
||||
### Test 3: Verify Observation Recording
|
||||
|
||||
Open the Claude-Mem web viewer:
|
||||
```bash
|
||||
open http://localhost:37777
|
||||
```
|
||||
|
||||
You should see new observations for tool calls made by Clawdbot.
|
||||
|
||||
---
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
### Full Config Example
|
||||
|
||||
Edit `~/.clawdbot/config.yml`:
|
||||
|
||||
```yaml
|
||||
# Disable built-in memory (use claude-mem instead)
|
||||
agents:
|
||||
defaults:
|
||||
memorySearch:
|
||||
enabled: false
|
||||
|
||||
# Enable claude-mem plugin
|
||||
plugins:
|
||||
memory-claudemem:
|
||||
workerUrl: http://localhost:37777
|
||||
workerTimeout: 10000
|
||||
```
|
||||
|
||||
### Rollback to Built-in Memory
|
||||
|
||||
If you want to switch back to Clawdbot's built-in memory:
|
||||
|
||||
```yaml
|
||||
agents:
|
||||
defaults:
|
||||
memorySearch:
|
||||
enabled: true
|
||||
|
||||
plugins:
|
||||
memory-claudemem:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available Tools
|
||||
|
||||
When the plugin is active, these tools are available to the AI agent:
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `memory_search` | Search past observations. Returns compact results with IDs. |
|
||||
| `memory_observations` | Get full details for specific observation IDs. |
|
||||
|
||||
### 3-Layer Workflow
|
||||
|
||||
1. **Search** (`memory_search`): Get index with IDs (~50-100 tokens/result)
|
||||
2. **Filter**: Review results, identify relevant IDs
|
||||
3. **Fetch** (`memory_observations`): Get full details (~500-1000 tokens/result)
|
||||
|
||||
This pattern saves ~10x tokens compared to fetching everything.
|
||||
|
||||
---
|
||||
|
||||
## CLI Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `clawdbot claude-mem status` | Check if the worker is responding |
|
||||
| `clawdbot claude-mem search <query>` | Search memories (JSON output) |
|
||||
|
||||
Options for search:
|
||||
```bash
|
||||
clawdbot claude-mem search "authentication" --limit 20
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Worker Not Responding
|
||||
|
||||
1. Check if Claude-Mem is running:
|
||||
```bash
|
||||
curl http://localhost:37777/api/health
|
||||
```
|
||||
|
||||
2. If not running, start it:
|
||||
```bash
|
||||
cd ~/Scripts/claude-mem
|
||||
npm run worker
|
||||
```
|
||||
|
||||
3. Check logs:
|
||||
```bash
|
||||
tail -f /tmp/claude-mem-worker.log
|
||||
```
|
||||
|
||||
### Plugin Not Loading
|
||||
|
||||
1. Check plugin list:
|
||||
```bash
|
||||
clawdbot plugins list
|
||||
```
|
||||
|
||||
2. Look for `memory-claudemem` in the output.
|
||||
|
||||
3. If missing, check the config:
|
||||
```bash
|
||||
clawdbot config get plugins.memory-claudemem
|
||||
```
|
||||
|
||||
### No Observations Being Recorded
|
||||
|
||||
1. Enable debug logging:
|
||||
```bash
|
||||
clawdbot config set logging.level debug
|
||||
```
|
||||
|
||||
2. Run a command and check logs:
|
||||
```bash
|
||||
clawdbot agent --message "test"
|
||||
tail -f ~/.clawdbot/logs/gateway.log | grep claude-mem
|
||||
```
|
||||
|
||||
### Context Not Being Injected
|
||||
|
||||
1. Check that there are observations in Claude-Mem (via web UI at http://localhost:37777)
|
||||
2. Ensure the prompt is longer than 5 characters (short prompts skip injection)
|
||||
3. Check logs for "injecting X memories into context"
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌──────────────────┐ ┌──────────────────┐
|
||||
│ Clawdbot │ │ Claude-Mem │
|
||||
│ Gateway │ │ Worker │
|
||||
├──────────────────┤ ├──────────────────┤
|
||||
│ │ │ │
|
||||
│ before_agent │────▶│ context inject │
|
||||
│ after_tool_call │────▶│ POST /observe │
|
||||
│ │ │ │
|
||||
│ memory_search │────▶│ GET /search │
|
||||
│ memory_observations──▶│ POST /batch │
|
||||
│ │ │ │
|
||||
└──────────────────┘ └──────────────────┘
|
||||
│ │
|
||||
└────────────────────────┘
|
||||
http://localhost:37777
|
||||
```
|
||||
|
||||
**Hooks:**
|
||||
- `before_agent_start`: Injects relevant memories as context
|
||||
- `after_tool_call`: Records observations (fire-and-forget)
|
||||
|
||||
**Tools:**
|
||||
- `memory_search`: Queries Claude-Mem's search API
|
||||
- `memory_observations`: Fetches full observation details
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **Set up messaging channels**: `clawdbot channels add telegram` (or discord, slack, etc.)
|
||||
- **Configure the AI model**: `clawdbot config set agents.defaults.model claude-sonnet-4-20250514`
|
||||
- **Start the gateway**: `clawdbot gateway run`
|
||||
- **Open the dashboard**: `clawdbot dashboard`
|
||||
|
||||
For more information:
|
||||
- Clawdbot docs: https://docs.clawd.bot
|
||||
- Claude-Mem docs: https://docs.claude-mem.ai
|
||||
619
docs/plans/claude-mem-integration.md
Normal file
619
docs/plans/claude-mem-integration.md
Normal file
@ -0,0 +1,619 @@
|
||||
# Claude-Mem Integration Plan
|
||||
|
||||
Integrate `thedotmack/claude-mem` as the primary observation agent and memory layer for Clawdbot.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: Documentation Discovery
|
||||
|
||||
### Allowed APIs (Clawdbot Plugin System)
|
||||
|
||||
**Source:** `/home/user/clawdbot/src/plugins/types.ts:235-274`
|
||||
|
||||
| API | Signature | Use Case |
|
||||
|-----|-----------|----------|
|
||||
| `api.registerTool` | `(tool: AnyAgentTool \| ClawdbotPluginToolFactory, opts?: ClawdbotPluginToolOptions) => void` | Register memory tools |
|
||||
| `api.on` | `<K extends PluginHookName>(hookName: K, handler: PluginHookHandlerMap[K]) => void` | Register lifecycle hooks |
|
||||
| `api.registerCli` | `(registrar: ClawdbotPluginCliRegistrar, opts?: { commands?: string[] }) => void` | Add CLI commands |
|
||||
| `api.registerService` | `(service: ClawdbotPluginService) => void` | Health check service |
|
||||
| `api.logger` | `PluginLogger` | Logging (`info`, `warn`, `debug`) |
|
||||
| `api.pluginConfig` | `Record<string, unknown>` | Plugin configuration |
|
||||
|
||||
### Available Hooks
|
||||
|
||||
**Source:** `/home/user/clawdbot/src/plugins/types.ts:289-303`
|
||||
|
||||
| Hook | Execution | Clawdbot → Claude-Mem |
|
||||
|------|-----------|----------------------|
|
||||
| `session_start` | Parallel (fire-and-forget) | → `SessionStart` |
|
||||
| `before_agent_start` | Sequential (can modify) | → `UserPromptSubmit` + context injection |
|
||||
| `after_tool_call` | Parallel (fire-and-forget) | → `PostToolUse` |
|
||||
| `agent_end` | Parallel (fire-and-forget) | → `Stop` |
|
||||
| `session_end` | Parallel (fire-and-forget) | → `SessionEnd` |
|
||||
|
||||
### Allowed APIs (Claude-Mem Worker)
|
||||
|
||||
**Source:** GitHub Issue #348, Platform Integration Guide
|
||||
|
||||
| Endpoint | Method | Request | Response |
|
||||
|----------|--------|---------|----------|
|
||||
| `/api/health` | GET | - | 200 OK |
|
||||
| `/api/sessions/observations` | POST | `{ claudeSessionId, tool_name, tool_input, tool_response, cwd? }` | `{ id }` |
|
||||
| `/api/search` | GET | `?query=<q>&type=observations&format=index&limit=<n>` | `SearchResult[]` |
|
||||
| `/api/observations/batch` | POST | `{ ids: number[], orderBy?, limit? }` | `Observation[]` |
|
||||
|
||||
**Note:** `created_at_epoch` is in MILLISECONDS (not seconds).
|
||||
|
||||
### Memory Disable Mechanism
|
||||
|
||||
**Source:** `/home/user/clawdbot/src/agents/memory-search.ts:276`
|
||||
|
||||
```typescript
|
||||
// Existing kill switch - returns null when disabled
|
||||
if (!resolved.enabled) return null;
|
||||
```
|
||||
|
||||
**Config location:** `agents.defaults.memorySearch.enabled` (default: `true`)
|
||||
|
||||
### Copy-Ready Patterns
|
||||
|
||||
| Pattern | Source File | Lines |
|
||||
|---------|-------------|-------|
|
||||
| Minimal plugin structure | `/home/user/clawdbot/extensions/memory-core/index.ts` | 1-36 |
|
||||
| Tool registration | `/home/user/clawdbot/extensions/memory-lancedb/index.ts` | 238-286 |
|
||||
| Hook registration (`before_agent_start`) | `/home/user/clawdbot/extensions/memory-lancedb/index.ts` | 468-491 |
|
||||
| Hook registration (`agent_end`) | `/home/user/clawdbot/extensions/memory-lancedb/index.ts` | 496-569 |
|
||||
| CLI commands | `/home/user/clawdbot/extensions/memory-lancedb/index.ts` | 418-460 |
|
||||
| Config schema with `parse` + `uiHints` | `/home/user/clawdbot/extensions/memory-lancedb/config.ts` | 61-114 |
|
||||
| Plugin manifest | `/home/user/clawdbot/extensions/memory-lancedb/clawdbot.plugin.json` | 1-67 |
|
||||
|
||||
### Anti-Patterns to Avoid
|
||||
|
||||
1. **Do NOT invent API endpoints** - Only use confirmed endpoints from Phase 0
|
||||
2. **Do NOT use `Type.Union` in tool schemas** - Use `stringEnum`/`optionalStringEnum` instead
|
||||
3. **Do NOT use raw `format` property** - Reserved keyword in some validators
|
||||
4. **Do NOT add dependencies to root package.json** - Plugin deps go in plugin's package.json
|
||||
5. **Do NOT use `workspace:*` in dependencies** - Use in `devDependencies` or `peerDependencies` only
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Disable Built-in Memory
|
||||
|
||||
### What to Implement
|
||||
|
||||
1. Add `memorySearch.enabled: false` config to disable built-in memory tools
|
||||
2. User configures this in their config file when using claude-mem
|
||||
|
||||
**No code changes required.** The existing kill switch at `src/agents/memory-search.ts:276` already returns `null` when `enabled: false`.
|
||||
|
||||
### Documentation References
|
||||
|
||||
- Config schema: `/home/user/clawdbot/src/config/types.tools.ts:223` (`enabled?: boolean`)
|
||||
- Kill switch: `/home/user/clawdbot/src/agents/memory-search.ts:276`
|
||||
- Tool creation guard: `/home/user/clawdbot/src/agents/tools/memory-tool.ts:32`
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
```bash
|
||||
# 1. Set config
|
||||
clawdbot config set agents.defaults.memorySearch.enabled false
|
||||
|
||||
# 2. Verify memory tools are not registered
|
||||
clawdbot agent --dry-run 2>&1 | grep -c "memory_search"
|
||||
# Expected: 0
|
||||
|
||||
# 3. Verify config is persisted
|
||||
clawdbot config get agents.defaults.memorySearch.enabled
|
||||
# Expected: false
|
||||
```
|
||||
|
||||
### Anti-Pattern Guards
|
||||
|
||||
- Do NOT modify `resolveMemorySearchConfig()` - existing logic sufficient
|
||||
- Do NOT add new config keys - reuse existing `enabled` flag
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Create Plugin Structure
|
||||
|
||||
### What to Implement
|
||||
|
||||
Create `extensions/memory-claudemem/` by copying structure from `extensions/memory-lancedb/`.
|
||||
|
||||
### Documentation References
|
||||
|
||||
- Copy from: `/home/user/clawdbot/extensions/memory-lancedb/` (directory structure)
|
||||
- Manifest spec: `/home/user/clawdbot/docs/plugins/manifest.md:16-42`
|
||||
|
||||
### Files to Create
|
||||
|
||||
```
|
||||
extensions/memory-claudemem/
|
||||
├── package.json # Copy from memory-lancedb/package.json
|
||||
├── clawdbot.plugin.json # Copy from memory-lancedb/clawdbot.plugin.json
|
||||
├── index.ts # Plugin entry point
|
||||
├── config.ts # Config schema
|
||||
├── client.ts # HTTP client for worker
|
||||
└── types.ts # Type definitions
|
||||
```
|
||||
|
||||
### package.json (Copy and Modify)
|
||||
|
||||
**Copy from:** `/home/user/clawdbot/extensions/memory-lancedb/package.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@clawdbot/memory-claudemem",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"clawdbot": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"clawdbot": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### clawdbot.plugin.json (Copy and Modify)
|
||||
|
||||
**Copy from:** `/home/user/clawdbot/extensions/memory-lancedb/clawdbot.plugin.json:1-20`
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "memory-claudemem",
|
||||
"name": "Memory (Claude-Mem)",
|
||||
"description": "Real-time observation and memory via claude-mem worker",
|
||||
"kind": "memory",
|
||||
"version": "0.0.1",
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"workerUrl": { "type": "string", "default": "http://localhost:37777" },
|
||||
"workerTimeout": { "type": "number", "default": 10000 }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
```bash
|
||||
# 1. Verify plugin loads
|
||||
cd extensions/memory-claudemem && pnpm install
|
||||
|
||||
# 2. Verify manifest is valid JSON
|
||||
cat clawdbot.plugin.json | jq .
|
||||
|
||||
# 3. Verify plugin appears in list
|
||||
clawdbot plugins list | grep memory-claudemem
|
||||
```
|
||||
|
||||
### Anti-Pattern Guards
|
||||
|
||||
- Do NOT add `workspace:*` to `dependencies` (breaks `npm install`)
|
||||
- Do NOT add plugin deps to root `package.json`
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: HTTP Client
|
||||
|
||||
### What to Implement
|
||||
|
||||
Create `client.ts` with methods for claude-mem worker API.
|
||||
|
||||
### Documentation References
|
||||
|
||||
- Observation endpoint: `POST /api/sessions/observations` (GitHub Issue #348)
|
||||
- Search endpoint: `GET /api/search?query=<q>&format=index`
|
||||
- Batch fetch: `POST /api/observations/batch` (v7.3.0+)
|
||||
- Health: `GET /api/health`
|
||||
|
||||
### client.ts
|
||||
|
||||
```typescript
|
||||
// Copy timeout + AbortController pattern from:
|
||||
// /home/user/clawdbot/src/infra/fetch-with-timeout.ts (if exists)
|
||||
// Otherwise use standard fetch with AbortController
|
||||
|
||||
export class ClaudeMemClient {
|
||||
constructor(private baseUrl: string, private timeout: number) {}
|
||||
|
||||
async ping(): Promise<boolean> {
|
||||
try {
|
||||
const res = await fetch(`${this.baseUrl}/api/health`, {
|
||||
signal: AbortSignal.timeout(this.timeout)
|
||||
});
|
||||
return res.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async observe(sessionId: string, toolName: string, toolInput: unknown, toolResponse: unknown): Promise<void> {
|
||||
await fetch(`${this.baseUrl}/api/sessions/observations`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
claudeSessionId: sessionId,
|
||||
tool_name: toolName,
|
||||
tool_input: toolInput,
|
||||
tool_response: toolResponse
|
||||
}),
|
||||
signal: AbortSignal.timeout(this.timeout)
|
||||
});
|
||||
}
|
||||
|
||||
async search(query: string, limit = 10): Promise<SearchResult[]> {
|
||||
const params = new URLSearchParams({
|
||||
query,
|
||||
type: "observations",
|
||||
format: "index",
|
||||
limit: String(limit)
|
||||
});
|
||||
const res = await fetch(`${this.baseUrl}/api/search?${params}`, {
|
||||
signal: AbortSignal.timeout(this.timeout)
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async getObservations(ids: number[]): Promise<Observation[]> {
|
||||
const res = await fetch(`${this.baseUrl}/api/observations/batch`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ ids }),
|
||||
signal: AbortSignal.timeout(this.timeout)
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
```bash
|
||||
# 1. Start claude-mem worker
|
||||
claude-mem worker &
|
||||
|
||||
# 2. Test health endpoint
|
||||
curl http://localhost:37777/api/health
|
||||
# Expected: 200 OK
|
||||
|
||||
# 3. Test search endpoint
|
||||
curl "http://localhost:37777/api/search?query=test&format=index&limit=5"
|
||||
# Expected: JSON array
|
||||
```
|
||||
|
||||
### Anti-Pattern Guards
|
||||
|
||||
- Do NOT invent endpoints like `/api/observe` - use documented `/api/sessions/observations`
|
||||
- Do NOT assume `created_at_epoch` is in seconds - it's MILLISECONDS
|
||||
- Do NOT skip error handling - worker may be offline
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Hook Registration
|
||||
|
||||
### What to Implement
|
||||
|
||||
Register hooks that forward events to claude-mem worker.
|
||||
|
||||
### Documentation References
|
||||
|
||||
- Hook handler signature: `/home/user/clawdbot/src/plugins/types.ts:466-520`
|
||||
- Example `before_agent_start`: `/home/user/clawdbot/extensions/memory-lancedb/index.ts:468-491`
|
||||
- Example `agent_end`: `/home/user/clawdbot/extensions/memory-lancedb/index.ts:496-569`
|
||||
|
||||
### Hook: after_tool_call → PostToolUse
|
||||
|
||||
**Copy pattern from:** `/home/user/clawdbot/extensions/memory-lancedb/index.ts:496-520`
|
||||
|
||||
```typescript
|
||||
// In index.ts register() function
|
||||
api.on("after_tool_call", async (event, ctx) => {
|
||||
// Skip our own tools to prevent recursion
|
||||
if (event.toolName.startsWith("memory_")) return;
|
||||
|
||||
try {
|
||||
await client.observe(
|
||||
ctx.sessionId,
|
||||
event.toolName,
|
||||
event.params,
|
||||
event.result
|
||||
);
|
||||
} catch (err) {
|
||||
api.logger.warn?.(`claude-mem: observation failed: ${err}`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Hook: before_agent_start → Context Injection
|
||||
|
||||
**Copy pattern from:** `/home/user/clawdbot/extensions/memory-lancedb/index.ts:468-491`
|
||||
|
||||
```typescript
|
||||
api.on("before_agent_start", async (event, ctx) => {
|
||||
try {
|
||||
const results = await client.search(event.prompt, 5);
|
||||
if (results.length === 0) return;
|
||||
|
||||
const context = results
|
||||
.map(r => `- [#${r.id}] ${r.title}: ${r.snippet}`)
|
||||
.join("\n");
|
||||
|
||||
return {
|
||||
prependContext: `<claude-mem-context>\n${context}\n</claude-mem-context>`
|
||||
};
|
||||
} catch (err) {
|
||||
api.logger.warn?.(`claude-mem: context injection failed: ${err}`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
```bash
|
||||
# 1. Enable debug logging
|
||||
clawdbot config set plugins.memory-claudemem.debug true
|
||||
|
||||
# 2. Run a test message
|
||||
clawdbot message send "test message"
|
||||
|
||||
# 3. Check logs for observation
|
||||
tail -f ~/.clawdbot/logs/gateway.log | grep claude-mem
|
||||
|
||||
# 4. Verify in claude-mem UI
|
||||
open http://localhost:37777
|
||||
```
|
||||
|
||||
### Anti-Pattern Guards
|
||||
|
||||
- Do NOT observe `memory_*` tools (causes recursion)
|
||||
- Do NOT block on observation failures (use fire-and-forget pattern for void hooks)
|
||||
- Do NOT modify event object for void hooks (only `before_agent_start` can return modifications)
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Progressive Disclosure Tools
|
||||
|
||||
### What to Implement
|
||||
|
||||
Register three tools following claude-mem's 3-layer retrieval pattern.
|
||||
|
||||
### Documentation References
|
||||
|
||||
- Tool registration: `/home/user/clawdbot/extensions/memory-lancedb/index.ts:238-286`
|
||||
- Tool factory pattern: `/home/user/clawdbot/src/plugins/types.ts:69-77`
|
||||
- Tool schema guardrails: Use `Type.Object` with `Type.String`, `Type.Optional`, `Type.Number`
|
||||
|
||||
### Layer 1: memory_search (~50-100 tokens per result)
|
||||
|
||||
**Copy pattern from:** `/home/user/clawdbot/extensions/memory-lancedb/index.ts:238-270`
|
||||
|
||||
```typescript
|
||||
api.registerTool({
|
||||
name: "memory_search",
|
||||
label: "Memory Search",
|
||||
description: "Search past observations. Returns compact results with IDs. Use memory_observations for full details.",
|
||||
parameters: Type.Object({
|
||||
query: Type.String({ description: "Natural language search query" }),
|
||||
limit: Type.Optional(Type.Number({ description: "Max results (default: 10)" }))
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const results = await client.search(params.query, params.limit ?? 10);
|
||||
const text = results.length === 0
|
||||
? "No memories found."
|
||||
: results.map((r, i) => `${i + 1}. [#${r.id}] ${r.title}`).join("\n");
|
||||
return { content: [{ type: "text", text }] };
|
||||
}
|
||||
}, { name: "memory_search" });
|
||||
```
|
||||
|
||||
### Layer 3: memory_observations (~500-1000 tokens per result)
|
||||
|
||||
```typescript
|
||||
api.registerTool({
|
||||
name: "memory_observations",
|
||||
label: "Memory Observations",
|
||||
description: "Get full details for specific observation IDs. Use after memory_search to filter.",
|
||||
parameters: Type.Object({
|
||||
ids: Type.Array(Type.Number(), { description: "Observation IDs from memory_search" })
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const observations = await client.getObservations(params.ids);
|
||||
const text = observations.map(o =>
|
||||
`## #${o.id}\n${o.narrative}\n\nFiles: ${o.files_modified?.join(", ") || "none"}`
|
||||
).join("\n\n---\n\n");
|
||||
return { content: [{ type: "text", text }] };
|
||||
}
|
||||
}, { name: "memory_observations" });
|
||||
```
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
```bash
|
||||
# 1. Verify tools are registered
|
||||
clawdbot tools list | grep memory_
|
||||
|
||||
# 2. Test search tool
|
||||
clawdbot agent --message "Use memory_search to find recent file edits"
|
||||
|
||||
# 3. Test observations tool
|
||||
clawdbot agent --message "Use memory_observations with IDs [1, 2, 3]"
|
||||
```
|
||||
|
||||
### Anti-Pattern Guards
|
||||
|
||||
- Do NOT use `Type.Union` in parameters - not allowed by schema guardrails
|
||||
- Do NOT use raw `format` property name - reserved keyword
|
||||
- Do NOT return tool results without `content: [{ type: "text", text }]` structure
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: CLI Commands
|
||||
|
||||
### What to Implement
|
||||
|
||||
Add CLI commands for worker status and manual search.
|
||||
|
||||
### Documentation References
|
||||
|
||||
- CLI registration: `/home/user/clawdbot/extensions/memory-lancedb/index.ts:418-460`
|
||||
- Commander.js pattern: `/home/user/clawdbot/src/plugins/types.ts:193-200`
|
||||
|
||||
### Implementation
|
||||
|
||||
**Copy pattern from:** `/home/user/clawdbot/extensions/memory-lancedb/index.ts:418-460`
|
||||
|
||||
```typescript
|
||||
api.registerCli(({ program }) => {
|
||||
const mem = program
|
||||
.command("claude-mem")
|
||||
.description("Claude-mem integration commands");
|
||||
|
||||
mem.command("status")
|
||||
.description("Check claude-mem worker status")
|
||||
.action(async () => {
|
||||
const alive = await client.ping();
|
||||
console.log(alive
|
||||
? `✓ Worker running at ${cfg.workerUrl}`
|
||||
: `✗ Worker not responding at ${cfg.workerUrl}`);
|
||||
});
|
||||
|
||||
mem.command("search")
|
||||
.description("Search memories")
|
||||
.argument("<query>", "Search query")
|
||||
.action(async (query) => {
|
||||
const results = await client.search(query);
|
||||
console.log(JSON.stringify(results, null, 2));
|
||||
});
|
||||
}, { commands: ["claude-mem"] });
|
||||
```
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
```bash
|
||||
# 1. Verify command appears in help
|
||||
clawdbot claude-mem --help
|
||||
|
||||
# 2. Test status command
|
||||
clawdbot claude-mem status
|
||||
|
||||
# 3. Test search command
|
||||
clawdbot claude-mem search "authentication"
|
||||
```
|
||||
|
||||
### Anti-Pattern Guards
|
||||
|
||||
- Do NOT use reserved command names (help, send, config, status, etc.) - see `/home/user/clawdbot/src/plugins/commands.ts:33-69`
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Verification
|
||||
|
||||
### Implementation Verification
|
||||
|
||||
1. **Memory disabled when configured:**
|
||||
```bash
|
||||
clawdbot config set agents.defaults.memorySearch.enabled false
|
||||
clawdbot tools list | grep -v memory_claudemem | grep memory_
|
||||
# Expected: no results (built-in memory tools gone)
|
||||
```
|
||||
|
||||
2. **Plugin loads successfully:**
|
||||
```bash
|
||||
clawdbot plugins list | grep memory-claudemem
|
||||
# Expected: memory-claudemem (enabled)
|
||||
```
|
||||
|
||||
3. **Worker connection verified:**
|
||||
```bash
|
||||
clawdbot claude-mem status
|
||||
# Expected: ✓ Worker running at http://localhost:37777
|
||||
```
|
||||
|
||||
4. **Observations recorded:**
|
||||
```bash
|
||||
# Run a command
|
||||
clawdbot agent --message "List files in current directory"
|
||||
|
||||
# Check worker UI
|
||||
open http://localhost:37777
|
||||
# Expected: New observation for "bash" tool
|
||||
```
|
||||
|
||||
5. **Context injection works:**
|
||||
```bash
|
||||
# First, create some observations
|
||||
clawdbot agent --message "Create a file called test.txt"
|
||||
|
||||
# Then search should inject context
|
||||
DEBUG=claude-mem clawdbot agent --message "What files did I create recently?"
|
||||
# Expected: Logs show context injection
|
||||
```
|
||||
|
||||
### Anti-Pattern Check
|
||||
|
||||
```bash
|
||||
# Grep for known bad patterns
|
||||
grep -r "Type\.Union" extensions/memory-claudemem/
|
||||
# Expected: no results
|
||||
|
||||
grep -r "format:" extensions/memory-claudemem/*.ts
|
||||
# Expected: no results (or only in non-schema contexts)
|
||||
|
||||
grep -r "/api/observe" extensions/memory-claudemem/
|
||||
# Expected: no results (should use /api/sessions/observations)
|
||||
```
|
||||
|
||||
### Test Suite
|
||||
|
||||
```bash
|
||||
cd extensions/memory-claudemem
|
||||
pnpm test
|
||||
# Expected: All tests pass
|
||||
|
||||
pnpm lint
|
||||
# Expected: No lint errors
|
||||
|
||||
pnpm build
|
||||
# Expected: Builds without type errors
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Summary
|
||||
|
||||
### User Config (to use claude-mem)
|
||||
|
||||
```yaml
|
||||
# ~/.clawdbot/config.yml
|
||||
|
||||
# Disable built-in memory
|
||||
agents:
|
||||
defaults:
|
||||
memorySearch:
|
||||
enabled: false
|
||||
|
||||
# Enable claude-mem plugin
|
||||
plugins:
|
||||
memory-claudemem:
|
||||
workerUrl: http://localhost:37777
|
||||
workerTimeout: 10000
|
||||
```
|
||||
|
||||
### Rollback (to restore built-in memory)
|
||||
|
||||
```yaml
|
||||
agents:
|
||||
defaults:
|
||||
memorySearch:
|
||||
enabled: true
|
||||
|
||||
plugins:
|
||||
memory-claudemem:
|
||||
enabled: false
|
||||
```
|
||||
27
extensions/memory-claudemem/clawdbot.plugin.json
Normal file
27
extensions/memory-claudemem/clawdbot.plugin.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"id": "memory-claudemem",
|
||||
"name": "Memory (Claude-Mem)",
|
||||
"description": "Real-time observation and memory via claude-mem worker",
|
||||
"kind": "memory",
|
||||
"version": "0.0.1",
|
||||
"uiHints": {
|
||||
"workerUrl": {
|
||||
"label": "Worker URL",
|
||||
"placeholder": "http://localhost:37777",
|
||||
"help": "URL of the claude-mem worker"
|
||||
},
|
||||
"workerTimeout": {
|
||||
"label": "Timeout (ms)",
|
||||
"placeholder": "10000",
|
||||
"advanced": true
|
||||
}
|
||||
},
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"workerUrl": { "type": "string", "default": "http://localhost:37777" },
|
||||
"workerTimeout": { "type": "number", "default": 10000 }
|
||||
}
|
||||
}
|
||||
}
|
||||
143
extensions/memory-claudemem/client.ts
Normal file
143
extensions/memory-claudemem/client.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import type { SearchResult, Observation } from "./types.js";
|
||||
|
||||
export class ClaudeMemClient {
|
||||
constructor(
|
||||
private readonly baseUrl: string,
|
||||
private readonly timeout: number,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Check if the claude-mem worker is healthy.
|
||||
* Returns true if the worker responds to health check, false otherwise.
|
||||
*/
|
||||
async ping(): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/api/health`, {
|
||||
method: "GET",
|
||||
signal: AbortSignal.timeout(this.timeout),
|
||||
});
|
||||
return response.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record an observation to the claude-mem worker.
|
||||
* Fire-and-forget pattern: errors are logged but do not block.
|
||||
*/
|
||||
async observe(
|
||||
claudeSessionId: string,
|
||||
toolName: string,
|
||||
toolInput: unknown,
|
||||
toolResponse: unknown,
|
||||
cwd?: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const body = {
|
||||
claudeSessionId,
|
||||
tool_name: toolName,
|
||||
tool_input: toolInput,
|
||||
tool_response: toolResponse,
|
||||
...(cwd && { cwd }),
|
||||
};
|
||||
|
||||
await fetch(`${this.baseUrl}/api/sessions/observations`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
signal: AbortSignal.timeout(this.timeout),
|
||||
});
|
||||
// Fire-and-forget: we don't check response status or throw on errors
|
||||
} catch {
|
||||
// Silently ignore errors - worker may be offline
|
||||
// This is intentional: observations should never block the main flow
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search observations by query.
|
||||
* Returns an array of search results with id, title, snippet, and score.
|
||||
*/
|
||||
async search(query: string, limit = 10): Promise<SearchResult[]> {
|
||||
try {
|
||||
const searchParams = new URLSearchParams({
|
||||
query,
|
||||
type: "observations",
|
||||
format: "index",
|
||||
limit: String(limit),
|
||||
});
|
||||
|
||||
const response = await fetch(
|
||||
`${this.baseUrl}/api/search?${searchParams.toString()}`,
|
||||
{
|
||||
method: "GET",
|
||||
signal: AbortSignal.timeout(this.timeout),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// The API returns results in an array format
|
||||
if (Array.isArray(data)) {
|
||||
return data as SearchResult[];
|
||||
}
|
||||
|
||||
// Handle wrapped response format { results: [...] }
|
||||
if (data && Array.isArray(data.results)) {
|
||||
return data.results as SearchResult[];
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch full observation details by IDs.
|
||||
* Returns observations with narrative, files_modified, tool_name, tool_input, tool_response, and created_at_epoch.
|
||||
*/
|
||||
async getObservations(ids: number[]): Promise<Observation[]> {
|
||||
if (ids.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/api/observations/batch`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ ids }),
|
||||
signal: AbortSignal.timeout(this.timeout),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Handle direct array response
|
||||
if (Array.isArray(data)) {
|
||||
return data as Observation[];
|
||||
}
|
||||
|
||||
// Handle wrapped response format { observations: [...] }
|
||||
if (data && Array.isArray(data.observations)) {
|
||||
return data.observations as Observation[];
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
49
extensions/memory-claudemem/config.ts
Normal file
49
extensions/memory-claudemem/config.ts
Normal file
@ -0,0 +1,49 @@
|
||||
export type ClaudeMemConfig = {
|
||||
workerUrl: string;
|
||||
workerTimeout: number;
|
||||
};
|
||||
|
||||
const DEFAULT_WORKER_URL = "http://localhost:37777";
|
||||
const DEFAULT_TIMEOUT = 10000;
|
||||
|
||||
function assertAllowedKeys(
|
||||
value: Record<string, unknown>,
|
||||
allowed: string[],
|
||||
label: string,
|
||||
) {
|
||||
const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
|
||||
if (unknown.length === 0) return;
|
||||
throw new Error(`${label} has unknown keys: ${unknown.join(", ")}`);
|
||||
}
|
||||
|
||||
export const claudeMemConfigSchema = {
|
||||
parse(value: unknown): ClaudeMemConfig {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
// Return defaults if no config provided
|
||||
return {
|
||||
workerUrl: DEFAULT_WORKER_URL,
|
||||
workerTimeout: DEFAULT_TIMEOUT,
|
||||
};
|
||||
}
|
||||
const cfg = value as Record<string, unknown>;
|
||||
assertAllowedKeys(cfg, ["workerUrl", "workerTimeout"], "claude-mem config");
|
||||
|
||||
return {
|
||||
workerUrl: typeof cfg.workerUrl === "string" ? cfg.workerUrl : DEFAULT_WORKER_URL,
|
||||
workerTimeout: typeof cfg.workerTimeout === "number" ? cfg.workerTimeout : DEFAULT_TIMEOUT,
|
||||
};
|
||||
},
|
||||
uiHints: {
|
||||
workerUrl: {
|
||||
label: "Worker URL",
|
||||
placeholder: DEFAULT_WORKER_URL,
|
||||
help: "URL of the claude-mem worker",
|
||||
},
|
||||
workerTimeout: {
|
||||
label: "Timeout (ms)",
|
||||
placeholder: String(DEFAULT_TIMEOUT),
|
||||
advanced: true,
|
||||
help: "Request timeout in milliseconds",
|
||||
},
|
||||
},
|
||||
};
|
||||
195
extensions/memory-claudemem/index.ts
Normal file
195
extensions/memory-claudemem/index.ts
Normal file
@ -0,0 +1,195 @@
|
||||
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { claudeMemConfigSchema } from "./config.js";
|
||||
import { ClaudeMemClient } from "./client.js";
|
||||
|
||||
const claudeMemPlugin = {
|
||||
id: "memory-claudemem",
|
||||
name: "Memory (Claude-Mem)",
|
||||
description: "Real-time observation and memory via claude-mem worker",
|
||||
kind: "memory" as const,
|
||||
configSchema: claudeMemConfigSchema,
|
||||
|
||||
register(api: ClawdbotPluginApi) {
|
||||
const cfg = claudeMemConfigSchema.parse(api.pluginConfig);
|
||||
const client = new ClaudeMemClient(cfg.workerUrl, cfg.workerTimeout);
|
||||
|
||||
api.logger.info(
|
||||
`memory-claudemem: plugin registered (worker: ${cfg.workerUrl})`,
|
||||
);
|
||||
|
||||
// Hook: after_tool_call → Observe tool calls (fire-and-forget)
|
||||
api.on("after_tool_call", async (event, ctx) => {
|
||||
// Skip memory tools to prevent recursion
|
||||
if (event.toolName.startsWith("memory_")) return;
|
||||
|
||||
try {
|
||||
// Fire-and-forget: don't await, let it run in parallel
|
||||
// Use sessionKey as the session identifier (may be undefined)
|
||||
client.observe(
|
||||
ctx.sessionKey ?? "unknown",
|
||||
event.toolName,
|
||||
event.params,
|
||||
event.result,
|
||||
);
|
||||
} catch (err) {
|
||||
api.logger.warn?.(`memory-claudemem: observation failed: ${err}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Hook: before_agent_start → Context injection from memory search
|
||||
api.on("before_agent_start", async (event) => {
|
||||
// Skip if prompt is empty or too short
|
||||
if (!event.prompt || event.prompt.length < 5) return;
|
||||
|
||||
try {
|
||||
const results = await client.search(event.prompt, 5);
|
||||
if (results.length === 0) return;
|
||||
|
||||
const memoryContext = results
|
||||
.map((r) => `- [#${r.id}] ${r.title}: ${r.snippet}`)
|
||||
.join("\n");
|
||||
|
||||
api.logger.info?.(
|
||||
`memory-claudemem: injecting ${results.length} memories into context`,
|
||||
);
|
||||
|
||||
return {
|
||||
prependContext: `<claude-mem-context>\nThe following memories may be relevant:\n${memoryContext}\n</claude-mem-context>`,
|
||||
};
|
||||
} catch (err) {
|
||||
api.logger.warn?.(`memory-claudemem: context injection failed: ${err}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Phase 5: Tool registration
|
||||
// memory_search - Layer 1: compact results (~50-100 tokens per result)
|
||||
api.registerTool(
|
||||
{
|
||||
name: "memory_search",
|
||||
label: "Memory Search",
|
||||
description:
|
||||
"Search past observations. Returns compact results with IDs. Use memory_observations for full details.",
|
||||
parameters: Type.Object({
|
||||
query: Type.String({ description: "Search query" }),
|
||||
limit: Type.Optional(
|
||||
Type.Number({ description: "Max results (default: 10)" }),
|
||||
),
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const { query, limit = 10 } = params as {
|
||||
query: string;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
const results = await client.search(query, limit);
|
||||
|
||||
if (results.length === 0) {
|
||||
return {
|
||||
content: [{ type: "text", text: "No relevant memories found." }],
|
||||
};
|
||||
}
|
||||
|
||||
const text = results.map((r) => `[#${r.id}] ${r.title}`).join("\n");
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Found ${results.length} memories:\n\n${text}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
{ name: "memory_search" },
|
||||
);
|
||||
|
||||
// memory_observations - Layer 3: full details (~500-1000 tokens per result)
|
||||
api.registerTool(
|
||||
{
|
||||
name: "memory_observations",
|
||||
label: "Memory Observations",
|
||||
description:
|
||||
"Get full details for specific observation IDs. Use after memory_search to filter.",
|
||||
parameters: Type.Object({
|
||||
ids: Type.Array(Type.Number(), {
|
||||
description: "Array of observation IDs to fetch",
|
||||
}),
|
||||
}),
|
||||
async execute(_toolCallId, params) {
|
||||
const { ids } = params as { ids: number[] };
|
||||
|
||||
if (ids.length === 0) {
|
||||
return {
|
||||
content: [{ type: "text", text: "No observation IDs provided." }],
|
||||
};
|
||||
}
|
||||
|
||||
const observations = await client.getObservations(ids);
|
||||
|
||||
if (observations.length === 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "No observations found for the given IDs.",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const text = observations
|
||||
.map((obs) => {
|
||||
const filesSection =
|
||||
obs.files_modified && obs.files_modified.length > 0
|
||||
? `\n\nFiles: ${obs.files_modified.join(", ")}`
|
||||
: "";
|
||||
return `## #${obs.id}\n${obs.narrative}${filesSection}`;
|
||||
})
|
||||
.join("\n\n---\n\n");
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text }],
|
||||
};
|
||||
},
|
||||
},
|
||||
{ name: "memory_observations" },
|
||||
);
|
||||
|
||||
// Phase 6: CLI registration
|
||||
api.registerCli(
|
||||
({ program }) => {
|
||||
const claudeMem = program
|
||||
.command("claude-mem")
|
||||
.description("Claude-mem memory plugin commands");
|
||||
|
||||
claudeMem
|
||||
.command("status")
|
||||
.description("Check if the claude-mem worker is responding")
|
||||
.action(async () => {
|
||||
const isHealthy = await client.ping();
|
||||
if (isHealthy) {
|
||||
console.log(`✓ Worker running at ${cfg.workerUrl}`);
|
||||
} else {
|
||||
console.log(`✗ Worker not responding at ${cfg.workerUrl}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
});
|
||||
|
||||
claudeMem
|
||||
.command("search")
|
||||
.description("Search memories")
|
||||
.argument("<query>", "Search query")
|
||||
.option("--limit <n>", "Max results", "10")
|
||||
.action(async (query, opts) => {
|
||||
const results = await client.search(query, parseInt(opts.limit));
|
||||
console.log(JSON.stringify(results, null, 2));
|
||||
});
|
||||
},
|
||||
{ commands: ["claude-mem"] },
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default claudeMemPlugin;
|
||||
13
extensions/memory-claudemem/package.json
Normal file
13
extensions/memory-claudemem/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@clawdbot/memory-claudemem",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"clawdbot": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"clawdbot": "*"
|
||||
}
|
||||
}
|
||||
24
extensions/memory-claudemem/types.ts
Normal file
24
extensions/memory-claudemem/types.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/** Search result from claude-mem worker (index format) */
|
||||
export type SearchResult = {
|
||||
id: number;
|
||||
title: string;
|
||||
snippet?: string;
|
||||
score?: number;
|
||||
};
|
||||
|
||||
/** Full observation from claude-mem worker */
|
||||
export type Observation = {
|
||||
id: number;
|
||||
narrative: string;
|
||||
files_modified?: string[];
|
||||
tool_name?: string;
|
||||
tool_input?: unknown;
|
||||
tool_response?: unknown;
|
||||
created_at_epoch?: number;
|
||||
};
|
||||
|
||||
/** Plugin configuration */
|
||||
export type ClaudeMemConfig = {
|
||||
workerUrl: string;
|
||||
workerTimeout: number;
|
||||
};
|
||||
@ -283,5 +283,10 @@
|
||||
"apps/macos/.build/**",
|
||||
"dist/Moltbot.app/**"
|
||||
]
|
||||
}
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"@whiskeysockets/baileys",
|
||||
"node-llama-cpp",
|
||||
"protobufjs"
|
||||
]
|
||||
}
|
||||
|
||||
101
pnpm-lock.yaml
generated
101
pnpm-lock.yaml
generated
@ -261,6 +261,13 @@ importers:
|
||||
node-llama-cpp:
|
||||
specifier: 3.15.0
|
||||
version: 3.15.0(typescript@5.9.3)
|
||||
optionalDependencies:
|
||||
'@napi-rs/canvas':
|
||||
specifier: ^0.1.88
|
||||
version: 0.1.88
|
||||
node-llama-cpp:
|
||||
specifier: 3.15.0
|
||||
version: 3.15.0(typescript@5.9.3)
|
||||
|
||||
extensions/bluebubbles: {}
|
||||
|
||||
@ -354,11 +361,21 @@ importers:
|
||||
|
||||
extensions/mattermost: {}
|
||||
|
||||
extensions/memory-claudemem:
|
||||
devDependencies:
|
||||
clawdbot:
|
||||
specifier: workspace:*
|
||||
version: link:../..
|
||||
|
||||
extensions/memory-core:
|
||||
devDependencies:
|
||||
moltbot:
|
||||
specifier: workspace:*
|
||||
version: link:../..
|
||||
dependencies:
|
||||
clawdbot:
|
||||
specifier: '>=2026.1.24'
|
||||
version: 2026.1.24(@types/express@5.0.6)(audio-decode@2.2.3)(devtools-protocol@0.0.1561482)(typescript@5.9.3)
|
||||
|
||||
extensions/memory-lancedb:
|
||||
dependencies:
|
||||
@ -1323,6 +1340,7 @@ packages:
|
||||
resolution: {integrity: sha512-aYrIoEG24AC+wILCL57Ius/Y4yU+xFHDPKLvmjzzN4byAjzeIGF0TC86S5RBt4Ji+dxS7yIWV5Q/gE5/fybIFQ==}
|
||||
engines: {node: '>= 18'}
|
||||
cpu: [x64, arm64]
|
||||
cpu: [x64, arm64]
|
||||
os: [darwin, linux, win32]
|
||||
peerDependencies:
|
||||
apache-arrow: '>=15.0.0 <=18.1.0'
|
||||
@ -3219,6 +3237,11 @@ packages:
|
||||
engines: {node: '>=22.12.0'}
|
||||
hasBin: true
|
||||
|
||||
clawdbot@2026.1.24:
|
||||
resolution: {integrity: sha512-foszbNXzk743kQBx2Nfc2KNlStZyBAVAYLQJ+KaONh009r8oB1a74kQ8wTnKX+SDNaQ9MvaE9tOisVUi+H3F+Q==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
hasBin: true
|
||||
|
||||
cli-cursor@5.0.0:
|
||||
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
|
||||
engines: {node: '>=18'}
|
||||
@ -9176,6 +9199,84 @@ snapshots:
|
||||
- typescript
|
||||
- utf-8-validate
|
||||
|
||||
clawdbot@2026.1.24(@types/express@5.0.6)(audio-decode@2.2.3)(devtools-protocol@0.0.1561482)(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@agentclientprotocol/sdk': 0.13.1(zod@4.3.6)
|
||||
'@aws-sdk/client-bedrock': 3.975.0
|
||||
'@buape/carbon': 0.14.0(hono@4.11.4)
|
||||
'@clack/prompts': 0.11.0
|
||||
'@grammyjs/runner': 2.0.3(grammy@1.39.3)
|
||||
'@grammyjs/transformer-throttler': 1.2.1(grammy@1.39.3)
|
||||
'@homebridge/ciao': 1.3.4
|
||||
'@line/bot-sdk': 10.6.0
|
||||
'@lydell/node-pty': 1.2.0-beta.3
|
||||
'@mariozechner/pi-agent-core': 0.49.3(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-ai': 0.49.3(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-coding-agent': 0.49.3(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-tui': 0.49.3
|
||||
'@mozilla/readability': 0.6.0
|
||||
'@sinclair/typebox': 0.34.47
|
||||
'@slack/bolt': 4.6.0(@types/express@5.0.6)
|
||||
'@slack/web-api': 7.13.0
|
||||
'@whiskeysockets/baileys': 7.0.0-rc.9(audio-decode@2.2.3)(sharp@0.34.5)
|
||||
ajv: 8.17.1
|
||||
body-parser: 2.2.2
|
||||
chalk: 5.6.2
|
||||
chokidar: 5.0.0
|
||||
chromium-bidi: 13.0.1(devtools-protocol@0.0.1561482)
|
||||
cli-highlight: 2.1.11
|
||||
commander: 14.0.2
|
||||
croner: 9.1.0
|
||||
detect-libc: 2.1.2
|
||||
discord-api-types: 0.38.37
|
||||
dotenv: 17.2.3
|
||||
express: 5.2.1
|
||||
file-type: 21.3.0
|
||||
grammy: 1.39.3
|
||||
hono: 4.11.4
|
||||
jiti: 2.6.1
|
||||
json5: 2.2.3
|
||||
jszip: 3.10.1
|
||||
linkedom: 0.18.12
|
||||
long: 5.3.2
|
||||
markdown-it: 14.1.0
|
||||
node-edge-tts: 1.2.9
|
||||
osc-progress: 0.3.0
|
||||
pdfjs-dist: 5.4.530
|
||||
playwright-core: 1.58.0
|
||||
proper-lockfile: 4.1.2
|
||||
qrcode-terminal: 0.12.0
|
||||
sharp: 0.34.5
|
||||
sqlite-vec: 0.1.7-alpha.2
|
||||
tar: 7.5.4
|
||||
tslog: 4.10.2
|
||||
undici: 7.19.0
|
||||
ws: 8.19.0
|
||||
yaml: 2.8.2
|
||||
zod: 4.3.6
|
||||
optionalDependencies:
|
||||
'@napi-rs/canvas': 0.1.88
|
||||
node-llama-cpp: 3.15.0(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- '@discordjs/opus'
|
||||
- '@modelcontextprotocol/sdk'
|
||||
- '@types/express'
|
||||
- audio-decode
|
||||
- aws-crt
|
||||
- bufferutil
|
||||
- canvas
|
||||
- debug
|
||||
- devtools-protocol
|
||||
- encoding
|
||||
- ffmpeg-static
|
||||
- jimp
|
||||
- link-preview-js
|
||||
- node-opus
|
||||
- opusscript
|
||||
- supports-color
|
||||
- typescript
|
||||
- utf-8-validate
|
||||
|
||||
cli-cursor@5.0.0:
|
||||
dependencies:
|
||||
restore-cursor: 5.1.0
|
||||
|
||||
Loading…
Reference in New Issue
Block a user