diff --git a/README.md b/README.md index dd419cf..1ea38e6 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,9 @@ Untitled_Artwork 3 - Long-term memory for OpenClaw. Automatically remembers conversations, recalls relevant context, and builds a persistent user profile — all powered by [Supermemory](https://supermemory.ai) cloud. No local infrastructure required. -> **Requires [Supermemory Pro or above](https://console.supermemory.ai/billing)** - Unlock the state of the art memory for your OpenClaw bot. +> **Requires [Supermemory Pro or above](https://app.supermemory.ai/?view=integrations)** - Unlock the state of the art memory for your OpenClaw bot. ## Install @@ -21,7 +20,7 @@ Restart OpenClaw after installing. openclaw supermemory setup ``` -Enter your API key from [console.supermemory.ai](https://console.supermemory.ai). That's it. +Enter your API key from [app.supermemory.ai](https://app.supermemory.ai/?view=integrations). That's it. ### Advanced Setup diff --git a/commands/cli.ts b/commands/cli.ts index 7cfef62..c38e552 100644 --- a/commands/cli.ts +++ b/commands/cli.ts @@ -23,7 +23,7 @@ export function registerCliSetup(api: OpenClawPluginApi): void { const configPath = path.join(configDir, "openclaw.json") console.log("\n🧠 Supermemory Setup\n") - console.log("Get your API key from: https://console.supermemory.ai\n") + console.log("Get your API key from: https://app.supermemory.ai\n") const rl = readline.createInterface({ input: process.stdin, @@ -87,7 +87,7 @@ export function registerCliSetup(api: OpenClawPluginApi): void { console.log("\n🧠 Supermemory Advanced Setup\n") console.log("Press Enter to use default values shown in [brackets]\n") - console.log("Get your API key from: https://console.supermemory.ai\n") + console.log("Get your API key from: https://app.supermemory.ai\n") const rl = readline.createInterface({ input: process.stdin, @@ -190,6 +190,17 @@ export function registerCliSetup(api: OpenClawPluginApi): void { console.log(" Invalid value, using default: all") } + console.log("\nEntity context:") + console.log( + " Instructions that guide what memories are extracted from conversations.", + ) + console.log( + " Leave blank to use the built-in default (recommended for most users).", + ) + const entityContextInput = await ask( + "Entity context (optional, press Enter for default): ", + ) + console.log("\n--- Custom Container Tags (Advanced) ---") console.log("Define custom containers for AI-driven memory routing.") const enableCustomContainerTagsInput = await ask( @@ -264,6 +275,8 @@ export function registerCliSetup(api: OpenClawPluginApi): void { if (profileFrequency !== 50) pluginConfig.profileFrequency = profileFrequency if (captureMode !== "all") pluginConfig.captureMode = captureMode + if (entityContextInput.trim()) + pluginConfig.entityContext = entityContextInput.trim() if (enableCustomContainerTags) pluginConfig.enableCustomContainerTags = true if (customContainerInstructions.trim()) { @@ -298,10 +311,20 @@ export function registerCliSetup(api: OpenClawPluginApi): void { console.log(` Max results: ${maxRecallResults}`) console.log(` Profile freq: ${profileFrequency}`) console.log(` Capture mode: ${captureMode}`) + const entityPreview = entityContextInput.trim() + if (entityPreview) { + const truncated = + entityPreview.length > 50 + ? `${entityPreview.slice(0, 50)}...` + : entityPreview + console.log(` Entity context: "${truncated}"`) + } else { + console.log(" Entity context: (default)") + } console.log( - ` Custom containers: ${enableCustomContainerTags ? "enabled" : "disabled"}`, + ` Container tags: ${enableCustomContainerTags ? "enabled" : "disabled"}`, ) - console.log(` Custom containers: ${customContainers.length}`) + console.log(` Container count: ${customContainers.length}`) if (customContainerInstructions.trim()) { console.log( ` Routing instructions: "${customContainerInstructions.trim().slice(0, 50)}${customContainerInstructions.length > 50 ? "..." : ""}"`, @@ -381,10 +404,18 @@ export function registerCliSetup(api: OpenClawPluginApi): void { console.log( ` Capture mode: ${pluginConfig.captureMode ?? "all"}`, ) + const entityCtx = pluginConfig.entityContext as string | undefined + if (entityCtx) { + const truncated = + entityCtx.length > 50 ? `${entityCtx.slice(0, 50)}...` : entityCtx + console.log(` Entity context: "${truncated}"`) + } else { + console.log(" Entity context: (default)") + } console.log( - ` Custom containers: ${pluginConfig.enableCustomContainerTags ? "enabled" : "disabled"}`, + ` Container tags: ${pluginConfig.enableCustomContainerTags ? "enabled" : "disabled"}`, ) - console.log(` Custom containers: ${customContainers.length}`) + console.log(` Container count: ${customContainers.length}`) console.log("") }) }, diff --git a/commands/slash.ts b/commands/slash.ts index 48f437a..ff7e74f 100644 --- a/commands/slash.ts +++ b/commands/slash.ts @@ -33,7 +33,7 @@ export function registerStubCommands(api: OpenClawPluginApi): void { export function registerCommands( api: OpenClawPluginApi, client: SupermemoryClient, - _cfg: SupermemoryConfig, + cfg: SupermemoryConfig, getSessionKey: () => string | undefined, ): void { api.registerCommand({ @@ -56,6 +56,8 @@ export function registerCommands( text, { type: category, source: "openclaw_command" }, sk ? buildDocumentId(sk) : undefined, + undefined, + cfg.entityContext, ) const preview = text.length > 60 ? `${text.slice(0, 60)}…` : text @@ -81,7 +83,7 @@ export function registerCommands( log.debug(`/recall command: "${query}"`) try { - const results = await client.search(query, _cfg.maxRecallResults) + const results = await client.search(query, cfg.maxRecallResults) if (results.length === 0) { return { text: `No memories found for: "${query}"` } diff --git a/config.ts b/config.ts index f592ecb..e9d917e 100644 --- a/config.ts +++ b/config.ts @@ -1,4 +1,5 @@ import { hostname } from "node:os" +import { DEFAULT_ENTITY_CONTEXT } from "./memory.ts" export type CaptureMode = "everything" | "all" @@ -15,6 +16,7 @@ export type SupermemoryConfig = { maxRecallResults: number profileFrequency: number captureMode: CaptureMode + entityContext: string debug: boolean enableCustomContainerTags: boolean customContainers: CustomContainer[] @@ -29,6 +31,7 @@ const ALLOWED_KEYS = [ "maxRecallResults", "profileFrequency", "captureMode", + "entityContext", "debug", "enableCustomContainerTags", "customContainers", @@ -117,6 +120,10 @@ export function parseConfig(raw: unknown): SupermemoryConfig { cfg.captureMode === "everything" ? ("everything" as const) : ("all" as const), + entityContext: + typeof cfg.entityContext === "string" && cfg.entityContext.trim() + ? cfg.entityContext.trim() + : DEFAULT_ENTITY_CONTEXT, debug: (cfg.debug as boolean) ?? false, enableCustomContainerTags: (cfg.enableCustomContainerTags as boolean) ?? false, @@ -140,6 +147,7 @@ export const supermemoryConfigSchema = { maxRecallResults: { type: "number" }, profileFrequency: { type: "number" }, captureMode: { type: "string", enum: ["all", "everything"] }, + entityContext: { type: "string" }, debug: { type: "boolean" }, enableCustomContainerTags: { type: "boolean" }, customContainers: { diff --git a/hooks/capture.ts b/hooks/capture.ts index 4b73979..9bfd930 100644 --- a/hooks/capture.ts +++ b/hooks/capture.ts @@ -1,7 +1,9 @@ import type { SupermemoryClient } from "../client.ts" import type { SupermemoryConfig } from "../config.ts" import { log } from "../logger.ts" -import { buildDocumentId, ENTITY_CONTEXT } from "../memory.ts" +import { buildDocumentId } from "../memory.ts" + +const SKIPPED_PROVIDERS = ["exec-event", "cron-event", "heartbeat"] function getLastTurn(messages: unknown[]): unknown[] { let lastUserIdx = -1 @@ -31,8 +33,8 @@ export function buildCaptureHandler( log.info( `agent_end fired: provider="${ctx.messageProvider}" success=${event.success}`, ) - const provider = ctx.messageProvider - if (provider === "exec-event" || provider === "cron-event") { + const provider = ctx.messageProvider as string + if (SKIPPED_PROVIDERS.includes(provider)) { return } @@ -107,7 +109,7 @@ export function buildCaptureHandler( { source: "openclaw", timestamp: new Date().toISOString() }, customId, undefined, - ENTITY_CONTEXT, + cfg.entityContext, ) } catch (err) { log.error("capture failed", err) diff --git a/memory.ts b/memory.ts index a65c51c..2e56dd4 100644 --- a/memory.ts +++ b/memory.ts @@ -16,8 +16,36 @@ export function detectCategory(text: string): MemoryCategory { return "other" } -export const ENTITY_CONTEXT = - "Messages are tagged with [role: user] and [role: assistant]. Only create memories from what the user actually said — their preferences, decisions, and important personal details. Agent (assistant) responses are just context, not facts to remember. Only remember things that will be useful later. Ignore noise like greetings or status updates." +export const DEFAULT_ENTITY_CONTEXT = `Conversation between a user and an AI assistant. Format: [role: user] ... [user:end] and [role: assistant] ... [assistant:end]. + +You do NOT need to generate memories for every message. Most messages are not worth remembering. Only extract things that will be useful in FUTURE conversations. + +REMEMBER (lasting personal facts): +- "doesn't eat pork or beef" ← dietary restriction, useful forever +- "prefers TypeScript over JavaScript" ← preference +- "works at Acme Corp as a backend engineer" ← personal detail +- "lives in San Francisco" ← personal detail +- "uses Neovim, prefers dark themes" ← preference +- "building a recipe app in Next.js" ← ongoing project +- "weekly standup on Mondays at 10am EST" ← routine +- "remember my server IP is 192.168.1.100" ← user explicitly asked to remember + +DO NOT REMEMBER (session-specific, ephemeral, or assistant-generated): +- "looking for food recommendations" ← temporary intent, not a lasting fact +- "wants a list of YC companies" ← one-time task, not a preference +- "found 193 YC companies from the directory" ← the ASSISTANT did this, not the user +- "saved a JSON file at /path/to/file" ← the ASSISTANT did this +- "is using Algolia API to search" ← implementation detail of current task +- "wants chicken pho, ramen, udon..." ← assistant's suggestions, not user's preference +- Any action the assistant performed (searching, writing files, generating code) +- Any recommendation or list the assistant provided +- Any in-progress task status or intermediate step + +KEY RULES: +- The assistant's output is CONTEXT ONLY — never attribute assistant actions to the user +- If the user asks "find X" or "do Y", that is a one-time request, NOT a memory +- Only store preferences if the user explicitly states them ("I like...", "I prefer...", "I always...") +- When in doubt, do NOT create a memory. Less is more.` export function buildDocumentId(sessionKey: string): string { const sanitized = sessionKey diff --git a/openclaw.plugin.json b/openclaw.plugin.json index df11b93..40d7a49 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -6,7 +6,7 @@ "label": "Supermemory API Key", "sensitive": true, "placeholder": "sm_...", - "help": "Your API key from console.supermemory.ai (or use ${SUPERMEMORY_OPENCLAW_API_KEY})" + "help": "Your API key from app.supermemory.ai (or use ${SUPERMEMORY_OPENCLAW_API_KEY})" }, "containerTag": { "label": "Container Tag", @@ -39,6 +39,12 @@ "help": "'all' (default) = filter out short texts and injected context, 'everything' = capture all messages", "advanced": true }, + "entityContext": { + "label": "Entity Context", + "placeholder": "Custom instructions for memory extraction...", + "help": "Instructions that guide what memories are extracted from conversations. Leave blank to use the built-in default.", + "advanced": true + }, "debug": { "label": "Debug Logging", "help": "Enable verbose debug logs for API calls and responses", @@ -71,6 +77,7 @@ "maxRecallResults": { "type": "number", "minimum": 1, "maximum": 20 }, "profileFrequency": { "type": "number", "minimum": 1, "maximum": 500 }, "captureMode": { "type": "string", "enum": ["everything", "all"] }, + "entityContext": { "type": "string" }, "debug": { "type": "boolean" }, "enableCustomContainerTags": { "type": "boolean" }, "customContainers": { diff --git a/tools/store.ts b/tools/store.ts index 5cfaf02..0d1b93b 100644 --- a/tools/store.ts +++ b/tools/store.ts @@ -13,7 +13,7 @@ import { export function registerStoreTool( api: OpenClawPluginApi, client: SupermemoryClient, - _cfg: SupermemoryConfig, + cfg: SupermemoryConfig, getSessionKey: () => string | undefined, ): void { api.registerTool( @@ -48,6 +48,7 @@ export function registerStoreTool( { type: category, source: "openclaw_tool" }, customId, params.containerTag, + cfg.entityContext, ) const preview =