@waynesutton/agent-memory 0.0.1 → 0.0.2-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/prds/README.md DELETED
@@ -1,988 +0,0 @@
1
- # @waynesutton/agent-memory
2
-
3
- A Convex Component for persistent, cloud-synced agent memory. Markdown-first memory backend for AI coding agents across CLIs and IDEs — with intelligent ingest, feedback loops, memory relations, and relevance decay.
4
-
5
- ## What It Does
6
-
7
- AI coding agents (Claude Code, Cursor, OpenCode, Codex, Conductor, Zed, VS Code Copilot, Pi) all use local, file-based memory systems (CLAUDE.md, .cursor/rules, AGENTS.md, etc.). These are siloed to one machine with no shared backend, no cross-tool sync, and no queryable search.
8
-
9
- `@waynesutton/agent-memory` creates a cloud-synced, markdown-first memory backend as a Convex Component. It:
10
-
11
- - Stores structured memories in Convex with full-text search and vector/semantic search
12
- - **Intelligently ingests** raw conversations into deduplicated memories via LLM pipeline
13
- - Exports memories in each tool's native format (`.cursor/rules/*.mdc`, `.claude/rules/*.md`, `AGENTS.md`, etc.)
14
- - Syncs bidirectionally between local files and the cloud
15
- - Tracks **memory history** (full audit trail of all changes)
16
- - Supports **feedback loops** — agents rate memories as helpful or unhelpful
17
- - Builds **memory relations** (graph connections between related memories)
18
- - Applies **relevance decay** — stale, low-access memories lose priority over time
19
- - Scopes memories by **agent, session, user, and project**
20
- - Provides a **read-only HTTP API** with bearer token auth and rate limiting
21
- - Exposes an MCP server with 14 tools for direct agent integration
22
- - Works standalone with any Convex app — no dependency on `@convex-dev/agent`
23
-
24
- ## Table of Contents
25
-
26
- - [Installation](#installation)
27
- - [Quick Start](#quick-start)
28
- - [Convex Component Setup](#convex-component-setup)
29
- - [Client API](#client-api)
30
- - [Intelligent Ingest](#intelligent-ingest)
31
- - [Memory History](#memory-history)
32
- - [Feedback & Scoring](#feedback--scoring)
33
- - [Memory Relations](#memory-relations)
34
- - [Relevance Decay](#relevance-decay)
35
- - [CLI Usage](#cli-usage)
36
- - [MCP Server](#mcp-server)
37
- - [Schema](#schema)
38
- - [Memory Types](#memory-types)
39
- - [Tool Format Support](#tool-format-support)
40
- - [Vector Search](#vector-search)
41
- - [Read-Only HTTP API](#read-only-http-api)
42
- - [Security](#security)
43
- - [Architecture](#architecture)
44
-
45
- ---
46
-
47
- ## Installation
48
-
49
- ```bash
50
- npm install @waynesutton/agent-memory
51
- ```
52
-
53
- Peer dependencies (must be installed in your Convex app):
54
-
55
- ```bash
56
- npm install convex convex-helpers
57
- ```
58
-
59
- ---
60
-
61
- ## Quick Start
62
-
63
- ### 1. Add the component to your Convex app
64
-
65
- ```typescript
66
- // convex/convex.config.ts
67
- import { defineApp } from "convex/server";
68
- import agentMemory from "@waynesutton/agent-memory/convex.config.js";
69
-
70
- const app = defineApp();
71
- app.use(agentMemory);
72
-
73
- export default app;
74
- ```
75
-
76
- ### 2. Create wrapper functions
77
-
78
- ```typescript
79
- // convex/memory.ts
80
- import { v } from "convex/values";
81
- import { query, mutation, action } from "./_generated/server.js";
82
- import { components } from "./_generated/api.js";
83
- import { AgentMemory } from "@waynesutton/agent-memory";
84
-
85
- const memory = new AgentMemory(components.agentMemory, {
86
- projectId: "my-project",
87
- agentId: "my-app",
88
- llmApiKey: process.env.OPENAI_API_KEY, // for intelligent ingest
89
- });
90
-
91
- // Save a memory
92
- export const remember = mutation({
93
- args: {
94
- title: v.string(),
95
- content: v.string(),
96
- memoryType: v.union(
97
- v.literal("instruction"),
98
- v.literal("learning"),
99
- v.literal("reference"),
100
- v.literal("feedback"),
101
- v.literal("journal"),
102
- ),
103
- },
104
- returns: v.string(),
105
- handler: async (ctx, args) => memory.remember(ctx, args),
106
- });
107
-
108
- // Search memories
109
- export const recall = query({
110
- args: { q: v.string() },
111
- returns: v.any(),
112
- handler: async (ctx, args) => memory.search(ctx, args.q),
113
- });
114
-
115
- // List all memories
116
- export const listMemories = query({
117
- args: {},
118
- returns: v.any(),
119
- handler: async (ctx) => memory.list(ctx),
120
- });
121
-
122
- // Intelligently ingest raw text into memories
123
- export const ingest = action({
124
- args: { content: v.string() },
125
- returns: v.any(),
126
- handler: async (ctx, args) => memory.ingest(ctx, args.content),
127
- });
128
-
129
- // View change history
130
- export const memoryHistory = query({
131
- args: { memoryId: v.string() },
132
- returns: v.any(),
133
- handler: async (ctx, args) => memory.history(ctx, args.memoryId),
134
- });
135
- ```
136
-
137
- ### 3. Deploy and start using
138
-
139
- ```bash
140
- npx convex dev
141
- ```
142
-
143
- ---
144
-
145
- ## Convex Component Setup
146
-
147
- ### Standalone (no other components)
148
-
149
- ```typescript
150
- // convex/convex.config.ts
151
- import { defineApp } from "convex/server";
152
- import agentMemory from "@waynesutton/agent-memory/convex.config.js";
153
-
154
- const app = defineApp();
155
- app.use(agentMemory);
156
- export default app;
157
- ```
158
-
159
- ### Alongside @convex-dev/agent
160
-
161
- Both components coexist independently with isolated tables:
162
-
163
- ```typescript
164
- // convex/convex.config.ts
165
- import { defineApp } from "convex/server";
166
- import agentMemory from "@waynesutton/agent-memory/convex.config.js";
167
- import agent from "@convex-dev/agent/convex.config.js";
168
-
169
- const app = defineApp();
170
- app.use(agentMemory); // isolated tables for persistent memories
171
- app.use(agent); // isolated tables for threads/messages
172
- export default app;
173
- ```
174
-
175
- You can load memories into an Agent's system prompt:
176
-
177
- ```typescript
178
- // convex/myAgent.ts
179
- import { AgentMemory } from "@waynesutton/agent-memory";
180
- import { Agent } from "@convex-dev/agent";
181
-
182
- const memory = new AgentMemory(components.agentMemory, {
183
- projectId: "my-app",
184
- });
185
-
186
- export const chat = action({
187
- args: { message: v.string() },
188
- handler: async (ctx, args) => {
189
- const bundle = await memory.getContextBundle(ctx);
190
- const memoryContext = bundle.pinned
191
- .map((m) => `## ${m.title}\n${m.content}`)
192
- .join("\n\n");
193
-
194
- const myAgent = new Agent(components.agent, {
195
- model: "claude-sonnet-4-6",
196
- instructions: `You are a helpful assistant.\n\nRelevant memories:\n${memoryContext}`,
197
- });
198
- // ... use agent as normal
199
- },
200
- });
201
- ```
202
-
203
- ---
204
-
205
- ## Client API
206
-
207
- ### Constructor
208
-
209
- ```typescript
210
- import { AgentMemory } from "@waynesutton/agent-memory";
211
-
212
- const memory = new AgentMemory(components.agentMemory, {
213
- projectId: "my-project", // required: unique project identifier
214
- defaultScope: "project", // optional: "project" | "user" | "org"
215
- userId: "user-123", // optional: for user-scoped memories
216
- agentId: "claude-code", // optional: agent identifier
217
- sessionId: "session-abc", // optional: session/conversation ID
218
- embeddingApiKey: "sk-...", // optional: enables vector search
219
- embeddingModel: "text-embedding-3-small", // optional
220
- llmApiKey: "sk-...", // optional: enables intelligent ingest
221
- llmModel: "gpt-4.1-nano", // optional: LLM for fact extraction
222
- llmBaseUrl: "https://api.openai.com/v1", // optional: custom LLM endpoint
223
- });
224
- ```
225
-
226
- ### Read Operations (query context)
227
-
228
- ```typescript
229
- // List with rich filters
230
- const all = await memory.list(ctx);
231
- const byAgent = await memory.list(ctx, { agentId: "claude-code" });
232
- const bySession = await memory.list(ctx, { sessionId: "session-123" });
233
- const bySource = await memory.list(ctx, { source: "mcp" });
234
- const byTags = await memory.list(ctx, { tags: ["api", "auth"] });
235
- const recent = await memory.list(ctx, { createdAfter: Date.now() - 86400000 });
236
-
237
- // Get a single memory
238
- const mem = await memory.get(ctx, "jh72k...");
239
-
240
- // Full-text search
241
- const results = await memory.search(ctx, "API authentication");
242
-
243
- // Progressive context bundle (feedback-boosted priority)
244
- const bundle = await memory.getContextBundle(ctx, {
245
- activePaths: ["src/api/routes.ts"],
246
- });
247
-
248
- // Export as tool-native files
249
- const files = await memory.exportForTool(ctx, "cursor");
250
- ```
251
-
252
- ### Write Operations (mutation context)
253
-
254
- ```typescript
255
- // Create (auto-records history)
256
- const id = await memory.remember(ctx, {
257
- title: "api-conventions",
258
- content: "# API Conventions\n\n- Use camelCase\n- Return JSON",
259
- memoryType: "instruction",
260
- tags: ["api", "style"],
261
- paths: ["src/api/**"],
262
- priority: 0.9,
263
- });
264
-
265
- // Update (auto-records history)
266
- await memory.update(ctx, id, { content: "Updated content", priority: 1.0 });
267
-
268
- // Archive & restore (both record history)
269
- await memory.forget(ctx, id);
270
- await memory.restore(ctx, id);
271
-
272
- // Batch operations
273
- await memory.batchArchive(ctx, ["id1", "id2", "id3"]);
274
- await memory.batchUpdate(ctx, [
275
- { memoryId: "id1", priority: 0.9 },
276
- { memoryId: "id2", tags: ["updated"] },
277
- ]);
278
- ```
279
-
280
- ### History & Audit Trail (query context)
281
-
282
- ```typescript
283
- // Get change history for a memory
284
- const history = await memory.history(ctx, id);
285
- // [{ event: "created", actor: "mcp", timestamp: ..., newContent: "..." }, ...]
286
-
287
- // Get recent changes across the project
288
- const recent = await memory.projectHistory(ctx, { limit: 20 });
289
- ```
290
-
291
- ### Feedback & Scoring (mutation/query context)
292
-
293
- ```typescript
294
- // Rate memories
295
- await memory.addFeedback(ctx, id, "positive", { comment: "Very helpful rule" });
296
- await memory.addFeedback(ctx, id, "negative", { comment: "Outdated information" });
297
-
298
- // View feedback
299
- const feedback = await memory.getFeedback(ctx, id);
300
- ```
301
-
302
- ### Memory Relations (mutation/query context)
303
-
304
- ```typescript
305
- // Create relationships
306
- await memory.addRelation(ctx, memoryA, memoryB, "extends");
307
- await memory.addRelation(ctx, memoryC, memoryA, "contradicts", { confidence: 0.9 });
308
-
309
- // View relationships
310
- const relations = await memory.getRelations(ctx, memoryA);
311
- const contradictions = await memory.getRelations(ctx, memoryA, { relationship: "contradicts" });
312
-
313
- // Remove a relationship
314
- await memory.removeRelation(ctx, relationId);
315
- ```
316
-
317
- ### Access Tracking (mutation context)
318
-
319
- ```typescript
320
- // Record that memories were read (for relevance decay)
321
- await memory.recordAccess(ctx, ["id1", "id2"]);
322
- ```
323
-
324
- ### Embedding Operations (action context)
325
-
326
- ```typescript
327
- // Single embedding
328
- await memory.embed(ctx, id);
329
-
330
- // Batch embed all un-embedded
331
- const result = await memory.embedAll(ctx);
332
-
333
- // Vector similarity search (falls back to full-text)
334
- const results = await memory.semanticSearch(ctx, "how to handle auth errors");
335
- ```
336
-
337
- ---
338
-
339
- ## Intelligent Ingest
340
-
341
- The core "smart memory" feature. Instead of manually creating memories, feed raw text and let the LLM pipeline:
342
-
343
- 1. **Extract** discrete facts/learnings from conversations or notes
344
- 2. **Search** existing memories for overlap (semantic deduplication)
345
- 3. **Decide** per-fact: ADD new, UPDATE existing, DELETE contradicted, or SKIP
346
- 4. **Return** a structured changelog of what happened
347
-
348
- ```typescript
349
- const memory = new AgentMemory(components.agentMemory, {
350
- projectId: "my-app",
351
- llmApiKey: process.env.OPENAI_API_KEY,
352
- });
353
-
354
- // In an action context
355
- const result = await memory.ingest(ctx,
356
- `User prefers TypeScript strict mode. The API should use camelCase.
357
- Actually, we switched from REST to GraphQL last week.
358
- The old REST convention docs are outdated.`
359
- );
360
-
361
- // result.results:
362
- // [
363
- // { event: "added", content: "User prefers TypeScript strict mode", memoryId: "..." },
364
- // { event: "updated", content: "API uses camelCase with GraphQL", memoryId: "...", previousContent: "..." },
365
- // { event: "deleted", content: "REST API conventions", memoryId: "...", previousContent: "..." },
366
- // ]
367
- ```
368
-
369
- ### Custom Prompts
370
-
371
- Override the extraction and decision prompts per-project:
372
-
373
- ```typescript
374
- await memory.ingest(ctx, rawText, {
375
- customExtractionPrompt: "Extract only coding conventions and preferences...",
376
- customUpdatePrompt: "When facts conflict, always prefer the newer one...",
377
- });
378
- ```
379
-
380
- Or set them in project settings for all ingest operations:
381
-
382
- ```typescript
383
- await ctx.runMutation(components.agentMemory.mutations.upsertProject, {
384
- projectId: "my-app",
385
- name: "My App",
386
- settings: {
387
- autoSync: false,
388
- syncFormats: [],
389
- factExtractionPrompt: "Your custom extraction prompt...",
390
- updateDecisionPrompt: "Your custom update decision prompt...",
391
- },
392
- });
393
- ```
394
-
395
- ---
396
-
397
- ## Memory History
398
-
399
- Every create, update, archive, restore, and merge operation records a history entry. This provides a complete audit trail of how memories change over time.
400
-
401
- ```typescript
402
- const history = await memory.history(ctx, memoryId);
403
- // Returns: MemoryHistoryEntry[]
404
- // Each entry has: event, actor, timestamp, previousContent, newContent
405
- ```
406
-
407
- History is automatically cleaned up by a weekly cron job (entries older than 90 days are removed).
408
-
409
- ---
410
-
411
- ## Feedback & Scoring
412
-
413
- Agents and users can rate memories. Feedback affects the **effective priority** used in context bundles:
414
-
415
- - Each `positive` feedback adds up to +0.05 priority (max +0.2 boost)
416
- - Each `negative` feedback subtracts up to -0.1 priority (max -0.5 penalty)
417
- - `very_negative` counts as negative with stronger signal
418
-
419
- This means good memories naturally float to the top of context bundles, while bad ones sink — without manual priority management.
420
-
421
- ---
422
-
423
- ## Memory Relations
424
-
425
- Build a knowledge graph between memories:
426
-
427
- | Relationship | Meaning |
428
- |-------------|---------|
429
- | `extends` | Memory B adds detail to Memory A |
430
- | `contradicts` | Memory B conflicts with Memory A |
431
- | `replaces` | Memory B supersedes Memory A |
432
- | `related_to` | General association |
433
-
434
- Relations are directional (`from` -> `to`) and queryable by direction and type.
435
-
436
- ---
437
-
438
- ## Relevance Decay
439
-
440
- Memories that aren't accessed lose priority over time, preventing stale memories from dominating context windows.
441
-
442
- **How it works:**
443
- - A daily cron job (3 AM UTC) checks all non-pinned memories
444
- - Memories with low access count and old `lastAccessedAt` get reduced priority
445
- - Decay follows an exponential half-life (configurable per-project, default 30 days)
446
- - Pinned memories (priority >= 0.8) are never decayed
447
-
448
- **Enable per-project:**
449
-
450
- ```typescript
451
- await ctx.runMutation(components.agentMemory.mutations.upsertProject, {
452
- projectId: "my-app",
453
- name: "My App",
454
- settings: {
455
- autoSync: false,
456
- syncFormats: [],
457
- decayEnabled: true,
458
- decayHalfLifeDays: 30, // memories lose half their priority every 30 days of no access
459
- },
460
- });
461
- ```
462
-
463
- ---
464
-
465
- ## CLI Usage
466
-
467
- The CLI syncs memories between local tool files and Convex.
468
-
469
- ### Environment Variable
470
-
471
- ```bash
472
- export CONVEX_URL="https://your-deployment.convex.cloud"
473
- ```
474
-
475
- ### Commands
476
-
477
- #### `npx agent-memory init`
478
-
479
- Detect tools in the current directory and register the project.
480
-
481
- ```bash
482
- npx agent-memory init --project my-app --name "My App"
483
- ```
484
-
485
- Detects: Claude Code, Cursor, OpenCode, Codex, Conductor, Zed, VS Code Copilot, Pi.
486
-
487
- #### `npx agent-memory push`
488
-
489
- Push local memory files to Convex.
490
-
491
- ```bash
492
- npx agent-memory push --project my-app
493
- npx agent-memory push --project my-app --format claude-code # specific tool only
494
- ```
495
-
496
- #### `npx agent-memory pull`
497
-
498
- Pull memories from Convex to local files.
499
-
500
- ```bash
501
- npx agent-memory pull --project my-app --format cursor
502
- npx agent-memory pull --project my-app --format claude-code
503
- ```
504
-
505
- #### `npx agent-memory list`
506
-
507
- List all memories in the terminal.
508
-
509
- ```bash
510
- npx agent-memory list --project my-app
511
- npx agent-memory list --project my-app --type instruction
512
- ```
513
-
514
- #### `npx agent-memory search <query>`
515
-
516
- Search memories from the terminal.
517
-
518
- ```bash
519
- npx agent-memory search "API conventions" --project my-app --limit 5
520
- ```
521
-
522
- #### `npx agent-memory mcp`
523
-
524
- Start the MCP server (see [MCP Server](#mcp-server) section).
525
-
526
- ```bash
527
- npx agent-memory mcp --project my-app
528
- npx agent-memory mcp --project my-app --llm-api-key $OPENAI_API_KEY # enable ingest
529
- ```
530
-
531
- ### Hook Integration
532
-
533
- Auto-sync on Claude Code session start/end:
534
-
535
- ```json
536
- // .claude/settings.json
537
- {
538
- "hooks": {
539
- "SessionStart": [{
540
- "hooks": [{ "type": "command", "command": "npx agent-memory pull --format claude-code" }]
541
- }],
542
- "SessionEnd": [{
543
- "hooks": [{ "type": "command", "command": "npx agent-memory push --format claude-code" }]
544
- }]
545
- }
546
- }
547
- ```
548
-
549
- ---
550
-
551
- ## MCP Server
552
-
553
- The MCP server runs as a local process, bridging AI tools to your Convex backend via stdio/JSON-RPC.
554
-
555
- ```
556
- ┌─────────────┐ stdio/JSON-RPC ┌──────────────────┐ ConvexHttpClient ┌─────────┐
557
- │ Claude Code │ <────────────────> │ MCP Server │ <──────────────────> │ Convex │
558
- │ Cursor │ │ (local process) │ │ Cloud │
559
- │ VS Code │ │ npx agent-memory │ │ │
560
- └─────────────┘ └──────────────────┘ └─────────┘
561
- ```
562
-
563
- ### Starting the Server
564
-
565
- ```bash
566
- npx agent-memory mcp --project my-app
567
- ```
568
-
569
- Options:
570
- | Flag | Description |
571
- |------|-------------|
572
- | `--project <id>` | Project ID (default: "default") |
573
- | `--read-only` | Disable write operations |
574
- | `--disable-tools <tools>` | Comma-separated tool names to disable |
575
- | `--embedding-api-key <key>` | Enable vector search |
576
- | `--llm-api-key <key>` | Enable intelligent ingest |
577
- | `--llm-model <model>` | LLM model for ingest (default: "gpt-4.1-nano") |
578
-
579
- ### MCP Tools (14 total)
580
-
581
- | Tool | Description |
582
- |------|-------------|
583
- | `memory_remember` | Save a new memory (with agent/session scoping) |
584
- | `memory_recall` | Search memories by keyword (full-text) |
585
- | `memory_semantic_recall` | Search memories by meaning (vector) |
586
- | `memory_list` | List memories with filters (agent, session, source, tags) |
587
- | `memory_context` | Get context bundle (pinned + relevant) |
588
- | `memory_forget` | Archive a memory |
589
- | `memory_restore` | Restore an archived memory |
590
- | `memory_update` | Update an existing memory |
591
- | `memory_history` | View change audit trail |
592
- | `memory_feedback` | Rate a memory as helpful/unhelpful |
593
- | `memory_relate` | Create relationship between memories |
594
- | `memory_relations` | View memory graph connections |
595
- | `memory_batch_archive` | Archive multiple memories at once |
596
- | `memory_ingest` | Intelligently extract memories from raw text |
597
-
598
- ### MCP Resources
599
-
600
- | URI | Description |
601
- |-----|-------------|
602
- | `memory://project/{id}/pinned` | High-priority memories auto-loaded at session start |
603
-
604
- ### Configuration in Claude Code
605
-
606
- ```json
607
- // .claude/settings.json
608
- {
609
- "mcpServers": {
610
- "agent-memory": {
611
- "command": "npx",
612
- "args": [
613
- "agent-memory", "mcp",
614
- "--project", "my-app",
615
- "--llm-api-key", "${env:OPENAI_API_KEY}"
616
- ],
617
- "env": {
618
- "CONVEX_URL": "${env:CONVEX_URL}",
619
- "OPENAI_API_KEY": "${env:OPENAI_API_KEY}"
620
- }
621
- }
622
- }
623
- }
624
- ```
625
-
626
- ### Configuration in Cursor
627
-
628
- ```json
629
- // .cursor/mcp.json
630
- {
631
- "mcpServers": {
632
- "agent-memory": {
633
- "command": "npx",
634
- "args": [
635
- "agent-memory", "mcp",
636
- "--project", "my-app",
637
- "--llm-api-key", "${env:OPENAI_API_KEY}"
638
- ],
639
- "env": {
640
- "CONVEX_URL": "${env:CONVEX_URL}",
641
- "OPENAI_API_KEY": "${env:OPENAI_API_KEY}"
642
- }
643
- }
644
- }
645
- }
646
- ```
647
-
648
- ---
649
-
650
- ## Schema
651
-
652
- The component creates 7 isolated tables in your Convex deployment:
653
-
654
- ### `memories`
655
-
656
- | Field | Type | Description |
657
- |-------|------|-------------|
658
- | `projectId` | `string` | Project identifier |
659
- | `scope` | `"project" \| "user" \| "org"` | Visibility scope |
660
- | `userId` | `string?` | Owner for user-scoped memories |
661
- | `agentId` | `string?` | Agent that created/owns this memory |
662
- | `sessionId` | `string?` | Session/conversation ID |
663
- | `title` | `string` | Short title/slug |
664
- | `content` | `string` | Markdown content |
665
- | `memoryType` | `MemoryType` | Category (see below) |
666
- | `tags` | `string[]` | Searchable tags |
667
- | `paths` | `string[]?` | File glob patterns for relevance matching |
668
- | `priority` | `number?` | 0-1 scale (>= 0.8 = pinned) |
669
- | `source` | `string?` | Origin tool ("claude-code", "cursor", "mcp", "ingest", etc.) |
670
- | `checksum` | `string` | FNV-1a hash for change detection |
671
- | `archived` | `boolean` | Soft delete flag |
672
- | `embeddingId` | `Id?` | Link to vector embedding |
673
- | `accessCount` | `number?` | Times this memory was accessed |
674
- | `lastAccessedAt` | `number?` | Timestamp of last access |
675
- | `positiveCount` | `number?` | Positive feedback count |
676
- | `negativeCount` | `number?` | Negative feedback count |
677
-
678
- **Indexes:** `by_project`, `by_project_scope`, `by_project_title`, `by_type_priority`, `by_agent`, `by_session`, `by_source`, `by_last_accessed`
679
- **Search indexes:** `search_content` (full-text on content), `search_title` (full-text on title)
680
-
681
- ### `embeddings`
682
-
683
- Vector embeddings for semantic search. Linked to memories via `memoryId`.
684
-
685
- **Vector index:** `by_embedding` (1536 dimensions, OpenAI-compatible)
686
-
687
- ### `projects`
688
-
689
- Project registry with settings for sync, custom prompts, and decay configuration.
690
-
691
- ### `syncLog`
692
-
693
- Tracks push/pull sync events for conflict detection.
694
-
695
- ### `memoryHistory`
696
-
697
- Audit trail of all memory changes: created, updated, archived, restored, merged.
698
-
699
- ### `memoryFeedback`
700
-
701
- Quality signals from agents/users: positive, negative, very_negative with optional comments.
702
-
703
- ### `memoryRelations`
704
-
705
- Directional graph connections between memories with relationship types and metadata.
706
-
707
- ---
708
-
709
- ## Memory Types
710
-
711
- | Type | Description | Maps To |
712
- |------|-------------|---------|
713
- | `instruction` | Rules and conventions | `.claude/rules/`, `.cursor/rules/`, `AGENTS.md` |
714
- | `learning` | Auto-discovered patterns | Claude Code auto-memory |
715
- | `reference` | Architecture docs, API specs | Reference documentation |
716
- | `feedback` | Corrections, preferences | User feedback on behavior |
717
- | `journal` | Session logs | OpenCode journal entries |
718
-
719
- ---
720
-
721
- ## Tool Format Support
722
-
723
- The component reads from and writes to 8 tool formats:
724
-
725
- | Tool | Parser Reads | Formatter Writes |
726
- |------|-------------|-----------------|
727
- | **Claude Code** | `.claude/rules/*.md` (YAML frontmatter) | `.claude/rules/<title>.md` |
728
- | **Cursor** | `.cursor/rules/*.mdc` (YAML frontmatter) | `.cursor/rules/<title>.mdc` |
729
- | **OpenCode** | `AGENTS.md` (## sections) | `AGENTS.md` or `journal/<title>.md` |
730
- | **Codex** | `AGENTS.md`, `AGENTS.override.md` | `AGENTS.md` or `<dir>/AGENTS.md` |
731
- | **Conductor** | `.conductor/rules/*.md` | `.conductor/rules/<title>.md` |
732
- | **Zed** | `.zed/rules/*.md` | `.zed/rules/<title>.md` |
733
- | **VS Code Copilot** | `.github/copilot-instructions.md`, `.copilot/rules/*.md` | `.github/copilot-instructions.md` |
734
- | **Pi** | `.pi/rules/*.md` | `.pi/rules/<title>.md` |
735
-
736
- ### Cross-Tool Sync Example
737
-
738
- Push from Claude Code, pull as Cursor rules:
739
-
740
- ```bash
741
- # In your project directory with .claude/rules/ files
742
- npx agent-memory push --project my-app --format claude-code
743
-
744
- # On another machine or for another tool
745
- npx agent-memory pull --project my-app --format cursor
746
- # Creates .cursor/rules/*.mdc files with proper frontmatter
747
- ```
748
-
749
- ---
750
-
751
- ## Vector Search
752
-
753
- Vector search is opt-in. Everything works with full-text search by default.
754
-
755
- ### Enabling Vector Search
756
-
757
- Pass an OpenAI API key when configuring:
758
-
759
- ```typescript
760
- const memory = new AgentMemory(components.agentMemory, {
761
- projectId: "my-app",
762
- embeddingApiKey: process.env.OPENAI_API_KEY,
763
- embeddingModel: "text-embedding-3-small", // default
764
- });
765
- ```
766
-
767
- Or via CLI/MCP:
768
-
769
- ```bash
770
- npx agent-memory mcp --project my-app --embedding-api-key $OPENAI_API_KEY
771
- ```
772
-
773
- ### Backfilling Embeddings
774
-
775
- ```typescript
776
- // In an action context
777
- const result = await memory.embedAll(ctx);
778
- console.log(`Embedded ${result.embedded} memories, skipped ${result.skipped}`);
779
- ```
780
-
781
- ### Fallback Behavior
782
-
783
- If no embedding API key is provided, `semanticSearch` automatically falls back to full-text search. No errors, no configuration changes needed.
784
-
785
- ---
786
-
787
- ## Read-Only HTTP API
788
-
789
- Expose memories as REST endpoints for dashboards, CI/CD, and external integrations. The component provides a `MemoryHttpApi` class that generates `httpAction` handlers — your app mounts them on its own `httpRouter`.
790
-
791
- ### Setup
792
-
793
- ```typescript
794
- // convex/http.ts
795
- import { httpRouter } from "convex/server";
796
- import { MemoryHttpApi } from "@waynesutton/agent-memory/http";
797
- import { components } from "./_generated/api";
798
-
799
- const http = httpRouter();
800
- const memoryApi = new MemoryHttpApi(components.agentMemory, {
801
- corsOrigins: ["https://myapp.com"], // optional, defaults to ["*"]
802
- });
803
- memoryApi.mount(http, "/api/memory");
804
- export default http;
805
- ```
806
-
807
- ### Creating API Keys
808
-
809
- ```typescript
810
- // convex/memory.ts — behind your app's own auth
811
- import { AgentMemory } from "@waynesutton/agent-memory";
812
-
813
- const memory = new AgentMemory(components.agentMemory, { projectId: "my-app" });
814
-
815
- export const createReadKey = mutation({
816
- args: {},
817
- handler: async (ctx) => {
818
- const identity = await ctx.auth.getUserIdentity();
819
- if (!identity) throw new Error("Not authenticated");
820
- return await memory.createApiKey(ctx, {
821
- name: "Dashboard key",
822
- permissions: ["list", "search", "context"],
823
- });
824
- },
825
- });
826
- ```
827
-
828
- ### Using the API
829
-
830
- ```bash
831
- # List memories
832
- curl -H "Authorization: Bearer am_<key>" \
833
- https://your-deployment.convex.cloud/api/memory/list
834
-
835
- # Search
836
- curl -H "Authorization: Bearer am_<key>" \
837
- "https://your-deployment.convex.cloud/api/memory/search?q=API+conventions"
838
-
839
- # Get context bundle
840
- curl -H "Authorization: Bearer am_<key>" \
841
- https://your-deployment.convex.cloud/api/memory/context
842
- ```
843
-
844
- ### Endpoints
845
-
846
- | Path | Permission | Description |
847
- |------|------------|-------------|
848
- | `/list` | `list` | List memories with filters |
849
- | `/get?id=<id>` | `get` | Get single memory |
850
- | `/search?q=<query>` | `search` | Full-text search |
851
- | `/context` | `context` | Progressive context bundle |
852
- | `/export?format=<format>` | `export` | Export in tool format |
853
- | `/history?id=<id>` | `history` | Memory audit trail |
854
- | `/relations?id=<id>` | `relations` | Memory graph |
855
-
856
- ### Rate Limiting
857
-
858
- Self-contained fixed-window token bucket (no external dependency):
859
-
860
- - **Default:** 100 requests per 60 seconds
861
- - **Configurable:** per-key override > per-project setting > global default
862
- - Returns `429` with `retryAfterMs` when exceeded
863
- - Old window records cleaned up hourly by cron
864
-
865
- ### Available Permissions
866
-
867
- `list`, `get`, `search`, `context`, `export`, `history`, `relations`
868
-
869
- ---
870
-
871
- ## Security
872
-
873
- 6 layers of protection:
874
-
875
- | Layer | What | How |
876
- |-------|------|-----|
877
- | **Deployment URL** | Gate to your Convex backend | `CONVEX_URL` env var required. Each app has its own isolated deployment. |
878
- | **Auth Token** | Authenticates the caller | Optional `CONVEX_AUTH_TOKEN` for production/team use. |
879
- | **API Keys** | HTTP API access control | Bearer tokens with per-key permissions, expiry, and rate limits. Keys stored as hashes. |
880
- | **Project Scope** | Isolates by project | `--project` flag. MCP server and API keys only access that project's memories. |
881
- | **Tool Disabling** | Restrict operations | `--read-only` or `--disable-tools` flags for fine-grained control. |
882
- | **Convex Isolation** | Runtime sandboxing | Component tables are isolated. Queries can't write. Mutations are transactional. |
883
-
884
- ### Examples
885
-
886
- ```bash
887
- # Full access (default)
888
- npx agent-memory mcp --project my-app
889
-
890
- # Read-only (no write/delete/ingest tools)
891
- npx agent-memory mcp --project my-app --read-only
892
-
893
- # Disable specific tools
894
- npx agent-memory mcp --project my-app --disable-tools memory_forget,memory_ingest
895
- ```
896
-
897
- ### MCP Config with Secrets
898
-
899
- ```json
900
- {
901
- "mcpServers": {
902
- "agent-memory": {
903
- "command": "npx",
904
- "args": ["agent-memory", "mcp", "--project", "my-app"],
905
- "env": {
906
- "CONVEX_URL": "${env:CONVEX_URL}",
907
- "CONVEX_AUTH_TOKEN": "${env:CONVEX_AUTH_TOKEN}"
908
- }
909
- }
910
- }
911
- }
912
- ```
913
-
914
- ---
915
-
916
- ## Architecture
917
-
918
- ```
919
- @waynesutton/agent-memory
920
- ├── src/
921
- │ ├── component/ # Convex backend (defineComponent)
922
- │ │ ├── schema.ts # 9 tables: memories, embeddings, projects, syncLog,
923
- │ │ │ # memoryHistory, memoryFeedback, memoryRelations,
924
- │ │ │ # apiKeys, rateLimitTokens
925
- │ │ ├── mutations.ts # CRUD + batch + feedback + relations + history tracking
926
- │ │ ├── queries.ts # list, search, context bundle, history, feedback, relations
927
- │ │ ├── actions.ts # embeddings, semantic search, intelligent ingest pipeline
928
- │ │ ├── apiKeyMutations.ts # API key create/revoke, rate limit consume
929
- │ │ ├── apiKeyQueries.ts # API key validation, listing
930
- │ │ ├── crons.ts # Daily relevance decay + weekly history cleanup + hourly rate limit cleanup
931
- │ │ ├── cronActions.ts # Internal actions called by cron jobs
932
- │ │ ├── cronQueries.ts # Internal queries for cron job data
933
- │ │ ├── format.ts # Memory -> tool-native file conversion
934
- │ │ └── checksum.ts # FNV-1a content hashing
935
- │ ├── client/
936
- │ │ ├── index.ts # AgentMemory class (public API)
937
- │ │ └── http.ts # MemoryHttpApi class (read-only HTTP API)
938
- │ ├── cli/
939
- │ │ ├── index.ts # CLI: init, push, pull, list, search, mcp
940
- │ │ ├── sync.ts # Push/pull sync logic
941
- │ │ └── parsers/ # 8 tool parsers (local files -> memories)
942
- │ ├── mcp/
943
- │ │ └── server.ts # MCP server (14 tools + resources)
944
- │ ├── shared.ts # Shared types and validators
945
- │ └── test.ts # Test helper for convex-test
946
- └── example/
947
- └── convex/ # Example Convex app
948
- ```
949
-
950
- ### Key Design Principles
951
-
952
- - **Works without any API key** — full-text search, CRUD, sync, export, history, feedback, and relations all work with zero external dependencies
953
- - **Vector search is opt-in** — pass `embeddingApiKey` to enable; falls back to full-text automatically
954
- - **Intelligent ingest is opt-in** — pass `llmApiKey` to enable LLM-powered fact extraction and deduplication
955
- - **Standalone** — no dependency on `@convex-dev/agent` or any other component
956
- - **Markdown-first** — memories are markdown documents with optional YAML frontmatter
957
- - **Checksum-based sync** — only changed content is pushed/pulled (FNV-1a hashing)
958
- - **Progressive disclosure** — context bundles tier memories as pinned/relevant/available
959
- - **Feedback-boosted scoring** — positive feedback raises priority; negative feedback lowers it
960
- - **Self-maintaining** — cron jobs handle relevance decay and history cleanup automatically
961
- - **Multi-dimensional scoping** — project + user + agent + session isolation
962
-
963
- ---
964
-
965
- ## Testing
966
-
967
- ### Using in Your Tests
968
-
969
- ```typescript
970
- import { convexTest } from "convex-test";
971
- import agentMemoryTest from "@waynesutton/agent-memory/test";
972
-
973
- const t = convexTest();
974
- agentMemoryTest.register(t);
975
- ```
976
-
977
- ### Running Component Tests
978
-
979
- ```bash
980
- npm test # run all tests
981
- npm run test:watch # watch mode
982
- ```
983
-
984
- ---
985
-
986
- ## License
987
-
988
- Apache-2.0