From 0f5bd5fff1af8cb94601df68bbd27b97a154c19d Mon Sep 17 00:00:00 2001 From: Jiayuan Zhang Date: Tue, 17 Feb 2026 15:33:39 +0800 Subject: [PATCH] refactor(agent): remove legacy memory subsystem --- apps/cli/src/commands/profile.ts | 7 - apps/cli/src/interactive.ts | 2 +- apps/cli/src/non-interactive.ts | 2 +- apps/cli/src/profile.ts | 6 - apps/desktop/src/main/ipc/agent.ts | 2 - .../src/renderer/src/components/tool-list.tsx | 4 - apps/desktop/src/renderer/src/stores/tools.ts | 5 - docs/skills-and-tools.md | 3 +- packages/core/src/agent/profile/index.ts | 2 - .../core/src/agent/profile/storage.test.ts | 6 - packages/core/src/agent/profile/storage.ts | 6 +- packages/core/src/agent/profile/templates.ts | 47 +-- packages/core/src/agent/profile/types.ts | 3 - packages/core/src/agent/runner.ts | 6 +- .../src/agent/system-prompt/builder.test.ts | 8 +- .../core/src/agent/system-prompt/builder.ts | 2 - .../src/agent/system-prompt/sections.test.ts | 10 - .../core/src/agent/system-prompt/sections.ts | 34 +-- .../core/src/agent/system-prompt/types.ts | 1 - packages/core/src/agent/tools.ts | 16 +- packages/core/src/agent/tools/groups.ts | 3 - .../src/agent/tools/memory-search.test.ts | 154 ---------- .../core/src/agent/tools/memory-search.ts | 276 ------------------ packages/core/src/agent/types.ts | 2 +- packages/ui/src/components/tool-call-item.tsx | 5 - scripts/swe-bench/run.ts | 2 +- 26 files changed, 15 insertions(+), 599 deletions(-) delete mode 100644 packages/core/src/agent/tools/memory-search.test.ts delete mode 100644 packages/core/src/agent/tools/memory-search.ts diff --git a/apps/cli/src/commands/profile.ts b/apps/cli/src/commands/profile.ts index ba181c21..babe753b 100644 --- a/apps/cli/src/commands/profile.ts +++ b/apps/cli/src/commands/profile.ts @@ -48,7 +48,6 @@ ${cyan("Profile Structure:")} - soul.md Agent identity, personality and behavior - user.md Information about the user - workspace.md Workspace rules and conventions - - memory.md Persistent knowledge ${cyan("Examples:")} ${dim("# Create a new profile")} @@ -94,7 +93,6 @@ function cmdNew(profileId: string | undefined) { console.log(" - soul.md (identity, personality and behavior)"); console.log(" - user.md (information about the user)"); console.log(" - workspace.md (workspace rules and conventions)"); - console.log(" - memory.md (persistent knowledge)"); console.log(""); console.log("Run interactive setup to personalize your agent:"); console.log(` multica profile setup ${profileId}`); @@ -165,11 +163,6 @@ function cmdShow(profileId: string | undefined) { console.log(""); } - if (profile.memory) { - console.log(`${green("=== memory.md ===")}`); - console.log(profile.memory.trim()); - console.log(""); - } } async function cmdEdit(profileId: string | undefined) { diff --git a/apps/cli/src/interactive.ts b/apps/cli/src/interactive.ts index 99509cbf..a1a58602 100644 --- a/apps/cli/src/interactive.ts +++ b/apps/cli/src/interactive.ts @@ -31,7 +31,7 @@ function printUsage() { console.log(`${cyan("Usage:")} pnpm agent:interactive [options]`); console.log(""); console.log(`${cyan("Options:")}`); - console.log(` ${yellow("--profile")} ID Load agent profile (identity, soul, tools, memory)`); + console.log(` ${yellow("--profile")} ID Load agent profile (identity, soul, tools)`); console.log(` ${yellow("--provider")} NAME LLM provider (e.g., openai, anthropic, kimi)`); console.log(` ${yellow("--model")} NAME Model name`); console.log(` ${yellow("--system")} TEXT System prompt (ignored if --profile is set)`); diff --git a/apps/cli/src/non-interactive.ts b/apps/cli/src/non-interactive.ts index 5de9b036..54a46ef6 100644 --- a/apps/cli/src/non-interactive.ts +++ b/apps/cli/src/non-interactive.ts @@ -25,7 +25,7 @@ function printUsage() { console.log(" echo \"your prompt\" | pnpm agent:cli"); console.log(""); console.log("Options:"); - console.log(" --profile ID Load agent profile (identity, soul, tools, memory)"); + console.log(" --profile ID Load agent profile (identity, soul, tools)"); console.log(" --provider NAME LLM provider (e.g., openai, anthropic, kimi)"); console.log(" --model NAME Model name"); console.log(" --api-key KEY API key (overrides environment variable)"); diff --git a/apps/cli/src/profile.ts b/apps/cli/src/profile.ts index f250fe02..9c78fcb3 100644 --- a/apps/cli/src/profile.ts +++ b/apps/cli/src/profile.ts @@ -68,7 +68,6 @@ function cmdNew(profileId: string | undefined) { console.log(" - soul.md (identity, personality and behavior)"); console.log(" - user.md (information about the user)"); console.log(" - workspace.md (workspace rules and conventions)"); - console.log(" - memory.md (persistent knowledge)"); console.log(""); console.log("Edit these files to customize your agent, then run:"); console.log(` pnpm agent:cli --profile ${profileId} "Hello"`); @@ -137,11 +136,6 @@ function cmdShow(profileId: string | undefined) { console.log(""); } - if (profile.memory) { - console.log("=== memory.md ==="); - console.log(profile.memory.trim()); - console.log(""); - } } async function cmdEdit(profileId: string | undefined) { diff --git a/apps/desktop/src/main/ipc/agent.ts b/apps/desktop/src/main/ipc/agent.ts index 428198af..c8f93ca2 100644 --- a/apps/desktop/src/main/ipc/agent.ts +++ b/apps/desktop/src/main/ipc/agent.ts @@ -12,7 +12,6 @@ const TOOL_GROUPS: Record = { 'group:fs': ['read', 'write', 'edit', 'glob'], 'group:runtime': ['exec', 'process'], 'group:web': ['web_search', 'web_fetch'], - 'group:memory': ['memory_search'], 'group:subagent': ['delegate'], 'group:cron': ['cron'], } @@ -22,7 +21,6 @@ const ALL_KNOWN_TOOLS = [ ...TOOL_GROUPS['group:fs'], ...TOOL_GROUPS['group:runtime'], ...TOOL_GROUPS['group:web'], - ...TOOL_GROUPS['group:memory'], ...TOOL_GROUPS['group:subagent'], ...TOOL_GROUPS['group:cron'], ] diff --git a/apps/desktop/src/renderer/src/components/tool-list.tsx b/apps/desktop/src/renderer/src/components/tool-list.tsx index 1417cfd8..884cdcac 100644 --- a/apps/desktop/src/renderer/src/components/tool-list.tsx +++ b/apps/desktop/src/renderer/src/components/tool-list.tsx @@ -11,7 +11,6 @@ import { FolderOpen, Code, Globe, - Brain, ChevronRight, Loader2, Clock, @@ -25,7 +24,6 @@ const GROUP_NAMES: Record = { fs: 'File System', runtime: 'Runtime', web: 'Web', - memory: 'Memory', subagent: 'Subagent', cron: 'Cron', other: 'Other', @@ -36,7 +34,6 @@ const GROUP_DESCRIPTIONS: Record = { fs: 'Read, write, and manage files', runtime: 'Execute code and commands', web: 'Fetch and interact with web content', - memory: 'Store and recall information', subagent: 'Delegate tasks to sub-agents', cron: 'Schedule recurring tasks', other: 'Miscellaneous tools', @@ -47,7 +44,6 @@ const GROUP_ICONS: Record = { fs: FolderOpen, runtime: Code, web: Globe, - memory: Brain, subagent: Users, cron: Clock, other: Code, diff --git a/apps/desktop/src/renderer/src/stores/tools.ts b/apps/desktop/src/renderer/src/stores/tools.ts index 9bbb14a7..3408e852 100644 --- a/apps/desktop/src/renderer/src/stores/tools.ts +++ b/apps/desktop/src/renderer/src/stores/tools.ts @@ -22,11 +22,6 @@ const TOOL_DESCRIPTIONS: Record = { process: 'Manage background processes', web_fetch: 'Fetch content from URLs', web_search: 'Search the web via Devv Search', - memory_get: 'Get stored memory value', - memory_set: 'Store a memory value', - memory_delete: 'Delete a memory value', - memory_list: 'List all memory keys', - memory_search: 'Search memory files for keywords', cron: 'Create and manage scheduled tasks', } diff --git a/docs/skills-and-tools.md b/docs/skills-and-tools.md index a332b3dd..dc3a7597 100644 --- a/docs/skills-and-tools.md +++ b/docs/skills-and-tools.md @@ -41,7 +41,7 @@ multica skills remove - base coding tools (`read/write/edit/...`) - extended tools (`exec`, `process`, `glob`, `web_fetch`, `web_search`, `data`, `cron`, `delegate`) -- conditional tools (`memory_search`, `send_file`) +- conditional tools (`send_file`) Tool errors are wrapped into structured tool results instead of crashing runs. @@ -52,7 +52,6 @@ Supported group aliases: - `group:fs` -> `read, write, edit, glob` - `group:runtime` -> `exec, process` - `group:web` -> `web_search, web_fetch` -- `group:memory` -> `memory_search` - `group:subagent` -> `delegate` - `group:cron` -> `cron` - `group:data` -> `data` diff --git a/packages/core/src/agent/profile/index.ts b/packages/core/src/agent/profile/index.ts index b7303048..a14d429c 100644 --- a/packages/core/src/agent/profile/index.ts +++ b/packages/core/src/agent/profile/index.ts @@ -49,7 +49,6 @@ export function createAgentProfile( profile.soul = DEFAULT_TEMPLATES.soul; profile.user = DEFAULT_TEMPLATES.user; profile.workspace = DEFAULT_TEMPLATES.workspace; - profile.memory = DEFAULT_TEMPLATES.memory; profile.heartbeat = DEFAULT_TEMPLATES.heartbeat; profile.config = { name: "Multica" }; @@ -151,7 +150,6 @@ export class ProfileManager { soul: profile.soul, user: profile.user, workspace: profile.workspace, - memory: profile.memory, heartbeat: profile.heartbeat, config: profile.config, }, diff --git a/packages/core/src/agent/profile/storage.test.ts b/packages/core/src/agent/profile/storage.test.ts index 6d900195..759a0d80 100644 --- a/packages/core/src/agent/profile/storage.test.ts +++ b/packages/core/src/agent/profile/storage.test.ts @@ -153,7 +153,6 @@ describe("storage", () => { writeFileSync(join(dir, "soul.md"), "Soul content"); writeFileSync(join(dir, "user.md"), "User content"); writeFileSync(join(dir, "workspace.md"), "Workspace content"); - writeFileSync(join(dir, "memory.md"), "Memory content"); const profile = loadProfile(profileId, { baseDir: testBaseDir }); @@ -161,7 +160,6 @@ describe("storage", () => { expect(profile.soul).toBe("Soul content"); expect(profile.user).toBe("User content"); expect(profile.workspace).toBe("Workspace content"); - expect(profile.memory).toBe("Memory content"); }); it("should return undefined for missing files", () => { @@ -177,7 +175,6 @@ describe("storage", () => { expect(profile.soul).toBe("Soul only"); expect(profile.user).toBeUndefined(); expect(profile.workspace).toBeUndefined(); - expect(profile.memory).toBeUndefined(); }); it("should handle non-existent profile", () => { @@ -195,7 +192,6 @@ describe("storage", () => { soul: "Soul data", user: "User data", workspace: "Workspace data", - memory: "Memory data", }; saveProfile(profile, { baseDir: testBaseDir }); @@ -204,7 +200,6 @@ describe("storage", () => { expect(readFileSync(join(dir, "soul.md"), "utf-8")).toBe("Soul data"); expect(readFileSync(join(dir, "user.md"), "utf-8")).toBe("User data"); expect(readFileSync(join(dir, "workspace.md"), "utf-8")).toBe("Workspace data"); - expect(readFileSync(join(dir, "memory.md"), "utf-8")).toBe("Memory data"); }); it("should only save defined fields", () => { @@ -213,7 +208,6 @@ describe("storage", () => { soul: "Soul only", user: undefined, workspace: undefined, - memory: undefined, }; saveProfile(profile, { baseDir: testBaseDir }); diff --git a/packages/core/src/agent/profile/storage.ts b/packages/core/src/agent/profile/storage.ts index 5b2c54d2..c00a422d 100644 --- a/packages/core/src/agent/profile/storage.ts +++ b/packages/core/src/agent/profile/storage.ts @@ -94,7 +94,6 @@ export function loadProfile(profileId: string, options?: StorageOptions): AgentP soul: readProfileFile(profileId, PROFILE_FILES.soul, options), user: readProfileFile(profileId, PROFILE_FILES.user, options), workspace: readProfileFile(profileId, PROFILE_FILES.workspace, options), - memory: readProfileFile(profileId, PROFILE_FILES.memory, options), heartbeat: readProfileFile(profileId, PROFILE_FILES.heartbeat, options), config: readProfileConfig(profileId, options), }; @@ -102,7 +101,7 @@ export function loadProfile(profileId: string, options?: StorageOptions): AgentP /** 保存 AgentProfile(只写入非空字段) */ export function saveProfile(profile: AgentProfile, options?: StorageOptions): void { - const { id, soul, user, workspace, memory, heartbeat, config } = profile; + const { id, soul, user, workspace, heartbeat, config } = profile; if (soul !== undefined) { writeProfileFile(id, PROFILE_FILES.soul, soul, options); @@ -113,9 +112,6 @@ export function saveProfile(profile: AgentProfile, options?: StorageOptions): vo if (workspace !== undefined) { writeProfileFile(id, PROFILE_FILES.workspace, workspace, options); } - if (memory !== undefined) { - writeProfileFile(id, PROFILE_FILES.memory, memory, options); - } if (heartbeat !== undefined) { writeProfileFile(id, PROFILE_FILES.heartbeat, heartbeat, options); } diff --git a/packages/core/src/agent/profile/templates.ts b/packages/core/src/agent/profile/templates.ts index b32ebaaa..6e7f63fc 100644 --- a/packages/core/src/agent/profile/templates.ts +++ b/packages/core/src/agent/profile/templates.ts @@ -31,7 +31,7 @@ _You're not a chatbot. You're becoming someone._ ## Continuity -Each session, you wake up fresh. These files are your memory. Read them. Update them. They're how you persist. +Each session, you wake up fresh. These files are your continuity. Read them. Update them. They're how you persist. If you change this file, tell the user — it's your soul, and they should know. @@ -71,7 +71,6 @@ Your profile directory contains these files (use \`edit\` or \`write\` to update | \`soul.md\` | Who you are, your identity and values | Rarely — tell user if you do | | \`user.md\` | About your human | As you learn about them | | \`workspace.md\` | This file — your rules | When you discover better conventions | -| \`memory.md\` | Long-term knowledge | Regularly — capture what matters | | \`heartbeat.md\` | Background check instructions | When heartbeat behavior should change | ## Every Session @@ -80,41 +79,10 @@ Before doing anything else: 1. Read \`soul.md\` — this is who you are 2. Read \`user.md\` — this is who you're helping -3. Check \`memory.md\` for context +3. Read \`workspace.md\` — these are your operating conventions Don't ask permission. Just do it. -## Memory - -You wake up fresh each session. These files are your continuity: - -- **Long-term:** \`MEMORY.md\` — your curated memories, lessons learned -- **Daily notes:** \`memory/YYYY-MM-DD.md\` — raw logs of what happened (optional) -- **Heartbeat:** \`heartbeat.md\` — periodic check loop instructions - -Capture what matters. Decisions, context, things to remember. - -### 📝 Write It Down - No "Mental Notes"! - -⚠️ **CRITICAL**: You CANNOT "remember" things mentally. Your memory resets each session. If you don't write it to a file, it's gone. - -**Which file to edit:** -- \`user.md\` — About your human: name, preferences, habits, context, anything personal -- \`memory.md\` — Your learnings: decisions made, lessons learned, important context -- \`workspace.md\` — Your rules: conventions, workflows, how you should operate -- \`soul.md\` — Your identity: only change if user wants to reshape who you are -- \`heartbeat.md\` — Periodic background checks and alert rules - -**Rules:** -- **DO NOT** say "I'll remember that" without ACTUALLY calling \`edit\` or \`write\` on a file -- **DO NOT** make "mental notes" — they don't exist -- When you learn something about the user (name, preference, habit) → IMMEDIATELY update \`user.md\` -- When you learn a lesson, make a decision, or gain context → IMMEDIATELY update \`memory.md\` -- When you discover a better workflow or convention → update \`workspace.md\` -- When you make a mistake → document it so future-you doesn't repeat it - -**Text > Brain** 📝 - ## Safety - Don't exfiltrate private data. Ever. @@ -140,17 +108,6 @@ Capture what matters. Decisions, context, things to remember. ## Make It Yours This is a starting point. Add your own conventions, style, and rules as you figure out what works. -`, - - memory: `# Memory - -_(Persistent knowledge will be stored here. Update this as you learn.)_ - -## Key Decisions - -## Lessons Learned - -## Important Context `, heartbeat: `# heartbeat.md diff --git a/packages/core/src/agent/profile/types.ts b/packages/core/src/agent/profile/types.ts index 631eb4b1..f0940eb5 100644 --- a/packages/core/src/agent/profile/types.ts +++ b/packages/core/src/agent/profile/types.ts @@ -10,7 +10,6 @@ export const PROFILE_FILES = { soul: "soul.md", user: "user.md", workspace: "workspace.md", - memory: "memory.md", heartbeat: "heartbeat.md", config: "config.json", } as const; @@ -56,8 +55,6 @@ export interface AgentProfile { user?: string | undefined; /** Workspace guidelines - behavior rules and conventions */ workspace?: string | undefined; - /** Persistent memory - long-term knowledge base */ - memory?: string | undefined; /** Periodic heartbeat instructions */ heartbeat?: string | undefined; /** Profile configuration (from config.json) */ diff --git a/packages/core/src/agent/runner.ts b/packages/core/src/agent/runner.ts index e095a90b..83f7f080 100644 --- a/packages/core/src/agent/runner.ts +++ b/packages/core/src/agent/runner.ts @@ -519,7 +519,7 @@ export class Agent { }); // Load Agent Profile (if profileId is specified) - // Every Agent should have a Profile for memory, tools config, and other settings + // Every Agent should have a Profile for tools config and other settings if (options.profileId) { this.profile = new ProfileManager({ profileId: options.profileId, @@ -644,7 +644,6 @@ export class Agent { // Merge Profile tools config with options.tools (options takes precedence) const profileToolsConfig = this.profile?.getToolsConfig(); const mergedToolsConfig = mergeToolsConfig(profileToolsConfig, options.tools); - const profileDir = this.profile?.getProfileDir(); // Use this.sessionId (which may be auto-generated) instead of options.sessionId // (which may be undefined). Without this, delegate tool has no session context. this.toolsOptions = mergedToolsConfig @@ -653,7 +652,6 @@ export class Agent { sessionId: this.sessionId, cwd: effectiveCwd, tools: mergedToolsConfig, - profileDir, provider: this.resolvedProvider, runLog: this.runLog, onExecApprovalNeeded: this.guardedExecApproval, @@ -662,7 +660,6 @@ export class Agent { ...options, sessionId: this.sessionId, cwd: effectiveCwd, - profileDir, provider: this.resolvedProvider, runLog: this.runLog, onExecApprovalNeeded: this.guardedExecApproval, @@ -1883,7 +1880,6 @@ export class Agent { soul: profile.soul, user: profile.user, workspace: profile.workspace, - memory: profile.memory, heartbeat: profile.heartbeat, config: profile.config, }, diff --git a/packages/core/src/agent/system-prompt/builder.test.ts b/packages/core/src/agent/system-prompt/builder.test.ts index ef85cab4..f1e17239 100644 --- a/packages/core/src/agent/system-prompt/builder.test.ts +++ b/packages/core/src/agent/system-prompt/builder.test.ts @@ -7,7 +7,6 @@ const PROFILE = { soul: "# Soul\nYou are a helpful coding assistant.", user: "# User\nName: Alice", workspace: "# Workspace\nFollow conventional commits.", - memory: "# Memory\nUser prefers TypeScript.", config: { name: "TestAgent" }, }; @@ -17,12 +16,11 @@ describe("buildSystemPrompt", () => { // ── Full mode ───────────────────────────────────────────────────────── it("full mode includes workspace section only (progressive disclosure)", () => { - // Soul, user, memory are read on-demand by the agent + // Soul and user are read on-demand by the agent const result = buildSystemPrompt({ mode: "full", profile: PROFILE }); expect(result).not.toContain("# Soul"); expect(result).not.toContain("# User"); expect(result).toContain("# Workspace"); - expect(result).not.toContain("# Memory"); }); it("full mode includes safety constitution", () => { @@ -82,7 +80,6 @@ describe("buildSystemPrompt", () => { expect(result).toContain("/home/user/.super-multica/agent-profiles/test"); expect(result).toContain("soul.md"); expect(result).toContain("user.md"); - expect(result).toContain("memory.md"); }); it("full mode excludes subagent section", () => { @@ -100,7 +97,6 @@ describe("buildSystemPrompt", () => { expect(result).not.toContain("# Soul"); expect(result).not.toContain("# User"); expect(result).not.toContain("# Workspace"); - expect(result).not.toContain("# Memory"); }); it("minimal mode includes safety constitution", () => { @@ -246,7 +242,7 @@ describe("buildSystemPromptWithReport", () => { const identity = report.sections.find((s) => s.name === "identity"); expect(identity?.included).toBe(true); - // User and memory are excluded (progressive disclosure) + // User is excluded (progressive disclosure) const user = report.sections.find((s) => s.name === "user"); expect(user?.included).toBe(false); diff --git a/packages/core/src/agent/system-prompt/builder.ts b/packages/core/src/agent/system-prompt/builder.ts index f49269b6..56852ab9 100644 --- a/packages/core/src/agent/system-prompt/builder.ts +++ b/packages/core/src/agent/system-prompt/builder.ts @@ -15,7 +15,6 @@ import { buildConditionalToolSections, buildExtraPromptSection, buildIdentitySection, - buildMemoryFileSection, buildProfileDirSection, buildRuntimeSection, buildSafetySection, @@ -74,7 +73,6 @@ export function buildSystemPromptWithReport(options: SystemPromptOptions): { { name: "workspace", lines: buildWorkspaceSection(profile, mode, profileDir, workspaceDir), ...(workspaceTruncated ? { truncated: true, originalChars: workspaceOriginalChars } : {}), }, - { name: "memory", lines: buildMemoryFileSection(profile, mode) }, { name: "heartbeat", lines: buildHeartbeatSection(profile, mode) }, { name: "safety", lines: buildSafetySection(includeSafety) }, { name: "tooling", lines: buildToolingSummary(tools, mode) }, diff --git a/packages/core/src/agent/system-prompt/sections.test.ts b/packages/core/src/agent/system-prompt/sections.test.ts index 3f43c06b..c3ac4cb7 100644 --- a/packages/core/src/agent/system-prompt/sections.test.ts +++ b/packages/core/src/agent/system-prompt/sections.test.ts @@ -2,7 +2,6 @@ import { describe, expect, it } from "vitest"; import { buildConditionalToolSections, buildIdentitySection, - buildMemoryFileSection, buildProfileDirSection, buildRuntimeSection, buildSafetySection, @@ -63,7 +62,6 @@ describe("buildWorkspaceSection", () => { expect(text).toContain("/path/to/profile"); expect(text).toContain("soul.md"); expect(text).toContain("user.md"); - expect(text).toContain("memory.md"); expect(text).toContain("Rules here"); }); @@ -78,14 +76,6 @@ describe("buildWorkspaceSection", () => { }); }); -describe("buildMemoryFileSection", () => { - it("returns empty in all modes (progressive disclosure)", () => { - // Memory content is no longer injected - agent reads memory.md on demand - expect(buildMemoryFileSection({ memory: "Key facts" }, "full")).toEqual([]); - expect(buildMemoryFileSection({ memory: "data" }, "minimal")).toEqual([]); - }); -}); - describe("buildSafetySection", () => { it("returns safety text when enabled", () => { const result = buildSafetySection(true); diff --git a/packages/core/src/agent/system-prompt/sections.ts b/packages/core/src/agent/system-prompt/sections.ts index 139b355b..3a3a0d8b 100644 --- a/packages/core/src/agent/system-prompt/sections.ts +++ b/packages/core/src/agent/system-prompt/sections.ts @@ -27,7 +27,6 @@ const CORE_TOOL_SUMMARIES: Record = { process: "Manage background exec sessions", web_search: "Search the web via Devv Search", web_fetch: "Fetch and extract readable content from a URL", - memory_search: "Search memory files by keyword", delegate: "Run tasks in parallel via sub-agents", data: "Query structured financial and market data", }; @@ -42,7 +41,6 @@ const TOOL_ORDER = [ "process", "web_search", "web_fetch", - "memory_search", "delegate", "data", ]; @@ -126,7 +124,7 @@ export function buildUserSection( /** * Workspace section — workspace.md content with profile directory path. * This is the primary profile content injected into system prompt. - * Other profile files (soul.md, user.md, memory.md) are read on demand. + * Other profile files (soul.md, user.md) are read on demand. */ export function buildWorkspaceSection( profile: ProfileContent | undefined, @@ -155,13 +153,12 @@ export function buildWorkspaceSection( "## Profile", "", `Your profile directory: \`${profileDir}\``, - "Use this as the base path for profile files (soul.md, user.md, memory.md, heartbeat.md, memory/*.md).", + "Use this as the base path for profile files (soul.md, user.md, workspace.md, heartbeat.md).", "", "Profile files:", "- `soul.md` — Your identity and values", "- `user.md` — Information about your user", "- `workspace.md` — Guidelines and conventions (below)", - "- `memory.md` — Persistent knowledge", "- `heartbeat.md` — Background heartbeat loop instructions", "", ); @@ -176,18 +173,6 @@ export function buildWorkspaceSection( return lines; } -/** - * Memory section — no longer injected into system prompt. - * Agent reads memory.md on demand from profile directory. - */ -export function buildMemoryFileSection( - _profile: ProfileContent | undefined, - _mode: SystemPromptMode, -): string[] { - // Progressive disclosure: agent reads memory.md on demand - return []; -} - /** * Heartbeat section — full mode only. * Keeps heartbeat protocol explicit in the agent instructions. @@ -298,21 +283,6 @@ export function buildConditionalToolSections( const toolSet = new Set(tools.map((t) => t.toLowerCase())); const lines: string[] = []; - // Memory tools - if (toolSet.has("memory_search")) { - lines.push( - "## Memory Recall", - "Before answering anything about prior work, decisions, dates, people, preferences, or todos:", - "1. Run `memory_search` to find relevant entries in memory files", - "2. Use `read` to pull needed context", - "", - "To update memory, use `edit` on the appropriate file:", - "- `memory.md` — Long-term knowledge (decisions, preferences, important context)", - "- `memory/YYYY-MM-DD.md` — Daily logs and session notes", - "", - ); - } - // Delegate tool (full mode only — sub-agents cannot delegate) if (mode === "full" && toolSet.has("delegate")) { lines.push( diff --git a/packages/core/src/agent/system-prompt/types.ts b/packages/core/src/agent/system-prompt/types.ts index 5420733b..81c799e2 100644 --- a/packages/core/src/agent/system-prompt/types.ts +++ b/packages/core/src/agent/system-prompt/types.ts @@ -62,7 +62,6 @@ export interface ProfileContent { soul?: string | undefined; user?: string | undefined; workspace?: string | undefined; - memory?: string | undefined; heartbeat?: string | undefined; config?: ProfileConfig | undefined; } diff --git a/packages/core/src/agent/tools.ts b/packages/core/src/agent/tools.ts index 83a61876..d583fba4 100644 --- a/packages/core/src/agent/tools.ts +++ b/packages/core/src/agent/tools.ts @@ -7,7 +7,6 @@ import { createProcessTool } from "./tools/process.js"; import { createGlobTool } from "./tools/glob.js"; import { createWebFetchTool, createWebSearchTool } from "./tools/web/index.js"; import { createDelegateTool } from "./tools/delegate.js"; -import { createMemorySearchTool } from "./tools/memory-search.js"; import { createCronTool } from "./tools/cron/index.js"; import { createDataTool } from "./tools/data/index.js"; import { createSendFileTool } from "./tools/send-file.js"; @@ -23,8 +22,6 @@ export { resolveModel } from "./providers/index.js"; /** Options for creating tools */ export interface CreateToolsOptions { cwd: string; - /** Profile directory for memory_search tool (optional) */ - profileDir?: string | undefined; /** Whether this agent is a subagent (passed to delegate tool) */ isSubagent?: boolean | undefined; /** Session ID of the agent (passed to delegate tool) */ @@ -112,7 +109,7 @@ function wrapTool( export function createAllTools(options: CreateToolsOptions | string): AgentTool[] { // Support legacy string argument for backwards compatibility const opts: CreateToolsOptions = typeof options === "string" ? { cwd: options } : options; - const { cwd, profileDir, isSubagent, sessionId } = opts; + const { cwd, isSubagent, sessionId } = opts; const baseTools = createCodingTools(cwd) .filter((tool) => tool.name !== "bash") @@ -138,12 +135,6 @@ export function createAllTools(options: CreateToolsOptions | string): AgentTool< dataTool as AgentTool, ]; - // Add memory_search tool if profileDir is provided - if (profileDir) { - const memorySearchTool = createMemorySearchTool(profileDir); - tools.push(memorySearchTool as AgentTool); - } - // Add send_file tool if channel send callback is provided if (opts.onChannelSendFile) { const sendFileTool = createSendFileTool(opts.onChannelSendFile); @@ -166,10 +157,8 @@ export function createAllTools(options: CreateToolsOptions | string): AgentTool< return tools; } -/** Extended options for resolveTools that includes profileDir */ +/** Extended options for resolveTools */ export interface ResolveToolsOptions extends AgentOptions { - /** Profile directory for memory_search tool (computed from profileId if not provided) */ - profileDir?: string | undefined; /** Run-log instance (forwarded to delegate tool) */ runLog?: import("./run-log.js").RunLog | undefined; } @@ -189,7 +178,6 @@ export function resolveTools(options: ResolveToolsOptions): AgentTool[] { // Create all tools const allTools = createAllTools({ cwd, - profileDir: options.profileDir, isSubagent: options.isSubagent, sessionId: options.sessionId, provider: options.provider, diff --git a/packages/core/src/agent/tools/groups.ts b/packages/core/src/agent/tools/groups.ts index 465d6875..1afa17f4 100644 --- a/packages/core/src/agent/tools/groups.ts +++ b/packages/core/src/agent/tools/groups.ts @@ -30,9 +30,6 @@ export const TOOL_GROUPS: Record = { // Web tools "group:web": ["web_search", "web_fetch"], - // Memory tools (requires profile) - "group:memory": ["memory_search"], - // Subagent tools "group:subagent": ["delegate"], diff --git a/packages/core/src/agent/tools/memory-search.test.ts b/packages/core/src/agent/tools/memory-search.test.ts deleted file mode 100644 index db955bb6..00000000 --- a/packages/core/src/agent/tools/memory-search.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { mkdirSync, writeFileSync, rmSync } from "fs"; -import { join } from "path"; -import { tmpdir } from "os"; -import { createMemorySearchTool } from "./memory-search.js"; - -describe("memory_search tool", () => { - let testDir: string; - - beforeEach(() => { - testDir = join(tmpdir(), `memory-search-test-${Date.now()}`); - mkdirSync(testDir, { recursive: true }); - }); - - afterEach(() => { - rmSync(testDir, { recursive: true, force: true }); - }); - - it("creates tool with correct name and description", () => { - const tool = createMemorySearchTool(testDir); - expect(tool.name).toBe("memory_search"); - expect(tool.label).toBe("Memory Search"); - expect(tool.description).toContain("memory files"); - }); - - it("returns no matches when no memory files exist", async () => { - const tool = createMemorySearchTool(testDir); - const result = await tool.execute("test-call", { query: "test" }, undefined); - expect(result.details?.matches).toHaveLength(0); - expect(result.details?.filesSearched).toBe(0); - }); - - it("searches memory.md file", async () => { - // Create memory.md with test content - writeFileSync( - join(testDir, "memory.md"), - "# Memory\n\nUser prefers TypeScript over JavaScript.\n\nDecision: Use ESLint for linting.\n", - ); - - const tool = createMemorySearchTool(testDir); - const result = await tool.execute("test-call", { query: "TypeScript" }, undefined); - - expect(result.details?.matches).toHaveLength(1); - expect(result.details?.matches[0]?.file).toBe("memory.md"); - expect(result.details?.matches[0]?.content).toContain("TypeScript"); - }); - - it("searches memory/*.md files", async () => { - // Create memory directory with daily logs - const memoryDir = join(testDir, "memory"); - mkdirSync(memoryDir); - writeFileSync( - join(memoryDir, "2024-01-15.md"), - "# 2024-01-15\n\nDiscussed API design with team.\n", - ); - writeFileSync( - join(memoryDir, "2024-01-16.md"), - "# 2024-01-16\n\nImplemented user authentication.\n", - ); - - const tool = createMemorySearchTool(testDir); - const result = await tool.execute("test-call", { query: "API" }, undefined); - - expect(result.details?.matches).toHaveLength(1); - expect(result.details?.matches[0]?.file).toBe("memory/2024-01-15.md"); - }); - - it("searches both memory.md and memory/*.md", async () => { - // Create memory.md - writeFileSync(join(testDir, "memory.md"), "Important: Always test code.\n"); - - // Create memory directory - const memoryDir = join(testDir, "memory"); - mkdirSync(memoryDir); - writeFileSync(join(memoryDir, "2024-01-15.md"), "Remember to test before deploy.\n"); - - const tool = createMemorySearchTool(testDir); - const result = await tool.execute("test-call", { query: "test" }, undefined); - - expect(result.details?.matches).toHaveLength(2); - expect(result.details?.filesSearched).toBe(2); - }); - - it("is case-insensitive by default", async () => { - writeFileSync(join(testDir, "memory.md"), "User prefers TYPESCRIPT.\n"); - - const tool = createMemorySearchTool(testDir); - const result = await tool.execute("test-call", { query: "typescript" }, undefined); - - expect(result.details?.matches).toHaveLength(1); - }); - - it("supports case-sensitive search", async () => { - writeFileSync(join(testDir, "memory.md"), "User prefers TYPESCRIPT.\n"); - - const tool = createMemorySearchTool(testDir); - - // Case-sensitive search should not match - const result1 = await tool.execute( - "test-call", - { query: "typescript", caseSensitive: true }, - undefined, - ); - expect(result1.details?.matches).toHaveLength(0); - - // Case-sensitive search should match - const result2 = await tool.execute( - "test-call", - { query: "TYPESCRIPT", caseSensitive: true }, - undefined, - ); - expect(result2.details?.matches).toHaveLength(1); - }); - - it("includes context lines in results", async () => { - writeFileSync( - join(testDir, "memory.md"), - "Line 1\nLine 2\nMatch here\nLine 4\nLine 5\n", - ); - - const tool = createMemorySearchTool(testDir); - const result = await tool.execute("test-call", { query: "Match" }, undefined); - - expect(result.details?.matches).toHaveLength(1); - expect(result.details?.matches[0]?.context.before).toContain("Line 2"); - expect(result.details?.matches[0]?.context.after).toContain("Line 4"); - }); - - it("respects maxResults limit", async () => { - // Create file with multiple matches - writeFileSync( - join(testDir, "memory.md"), - "test line 1\ntest line 2\ntest line 3\ntest line 4\ntest line 5\n", - ); - - const tool = createMemorySearchTool(testDir); - const result = await tool.execute( - "test-call", - { query: "test", maxResults: 2 }, - undefined, - ); - - expect(result.details?.matches).toHaveLength(2); - expect(result.details?.totalMatches).toBe(5); - expect(result.details?.truncated).toBe(true); - }); - - it("throws error for empty query", async () => { - const tool = createMemorySearchTool(testDir); - await expect(tool.execute("test-call", { query: "" }, undefined)).rejects.toThrow( - "Query must not be empty", - ); - }); -}); diff --git a/packages/core/src/agent/tools/memory-search.ts b/packages/core/src/agent/tools/memory-search.ts deleted file mode 100644 index 666f0426..00000000 --- a/packages/core/src/agent/tools/memory-search.ts +++ /dev/null @@ -1,276 +0,0 @@ -import { Type } from "@sinclair/typebox"; -import type { AgentTool } from "@mariozechner/pi-agent-core"; -import * as fs from "fs/promises"; -import * as path from "path"; -import fg from "fast-glob"; - -const MemorySearchSchema = Type.Object({ - query: Type.String({ - description: "Search query - keywords or phrases to find in memory files.", - }), - maxResults: Type.Optional( - Type.Number({ - description: "Maximum number of results to return. Defaults to 10.", - minimum: 1, - maximum: 50, - }), - ), - caseSensitive: Type.Optional( - Type.Boolean({ - description: "Whether the search is case-sensitive. Defaults to false.", - }), - ), -}); - -type MemorySearchArgs = { - query: string; - maxResults?: number; - caseSensitive?: boolean; -}; - -export type MemorySearchMatch = { - file: string; - line: number; - content: string; - context: { - before: string[]; - after: string[]; - }; -}; - -export type MemorySearchResult = { - matches: MemorySearchMatch[]; - totalMatches: number; - filesSearched: number; - truncated: boolean; -}; - -const DEFAULT_MAX_RESULTS = 10; -const CONTEXT_LINES = 2; - -/** - * Create a memory_search tool for searching memory files. - * - * @param profileDir - Profile directory containing memory.md and memory/ folder - */ -export function createMemorySearchTool( - profileDir: string, -): AgentTool { - return { - name: "memory_search", - label: "Memory Search", - description: - "Search through memory files (memory.md and memory/*.md) for keywords or phrases. " + - "Use this before answering questions about prior work, decisions, dates, people, preferences, or todos. " + - "Returns matching lines with context.", - parameters: MemorySearchSchema, - execute: async (_toolCallId, args, _signal) => { - const { query, maxResults, caseSensitive } = args as MemorySearchArgs; - - if (!query || query.trim() === "") { - throw new Error("Query must not be empty"); - } - - const limit = Math.min(maxResults || DEFAULT_MAX_RESULTS, 50); - const searchQuery = caseSensitive ? query : query.toLowerCase(); - - // Find all memory files - const memoryFiles = await findMemoryFiles(profileDir); - - if (memoryFiles.length === 0) { - return { - content: [{ type: "text", text: "No memory files found." }], - details: { - matches: [], - totalMatches: 0, - filesSearched: 0, - truncated: false, - }, - }; - } - - // Search each file - const allMatches: MemorySearchMatch[] = []; - - for (const file of memoryFiles) { - const matches = await searchFile(file, searchQuery, caseSensitive ?? false, profileDir); - allMatches.push(...matches); - } - - // Sort by relevance (files with more matches first, then by line number) - allMatches.sort((a, b) => { - if (a.file !== b.file) { - // Count matches per file - const aCount = allMatches.filter((m) => m.file === a.file).length; - const bCount = allMatches.filter((m) => m.file === b.file).length; - return bCount - aCount; - } - return a.line - b.line; - }); - - const totalMatches = allMatches.length; - const truncated = allMatches.length > limit; - const limitedMatches = allMatches.slice(0, limit); - - // Format output - const output = formatSearchResults(limitedMatches, totalMatches, truncated, memoryFiles.length); - - return { - content: [{ type: "text", text: output }], - details: { - matches: limitedMatches, - totalMatches, - filesSearched: memoryFiles.length, - truncated, - }, - }; - }, - }; -} - -/** - * Find all memory files in the profile directory. - */ -async function findMemoryFiles(profileDir: string): Promise { - const files: string[] = []; - - // Check for memory.md in profile root - const memoryMd = path.join(profileDir, "memory.md"); - try { - await fs.access(memoryMd); - files.push(memoryMd); - } catch { - // File doesn't exist - } - - // Check for memory/*.md files - const memoryDir = path.join(profileDir, "memory"); - try { - await fs.access(memoryDir); - const mdFiles = await fg("*.md", { - cwd: memoryDir, - onlyFiles: true, - absolute: true, - }); - files.push(...mdFiles); - } catch { - // Directory doesn't exist - } - - return files; -} - -/** - * Search a single file for the query. - */ -async function searchFile( - filePath: string, - query: string, - caseSensitive: boolean, - profileDir: string, -): Promise { - const matches: MemorySearchMatch[] = []; - - try { - const content = await fs.readFile(filePath, "utf-8"); - const lines = content.split("\n"); - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]!; - const searchLine = caseSensitive ? line : line.toLowerCase(); - - if (searchLine.includes(query)) { - // Get context lines - const beforeLines: string[] = []; - const afterLines: string[] = []; - - for (let j = Math.max(0, i - CONTEXT_LINES); j < i; j++) { - beforeLines.push(lines[j]!); - } - - for (let j = i + 1; j <= Math.min(lines.length - 1, i + CONTEXT_LINES); j++) { - afterLines.push(lines[j]!); - } - - // Get relative path for display - const relativePath = path.relative(profileDir, filePath); - - matches.push({ - file: relativePath, - line: i + 1, // 1-indexed - content: line, - context: { - before: beforeLines, - after: afterLines, - }, - }); - } - } - } catch (err) { - // Skip files that can't be read - console.error(`Failed to read ${filePath}:`, err); - } - - return matches; -} - -/** - * Format search results for display. - */ -function formatSearchResults( - matches: MemorySearchMatch[], - totalMatches: number, - truncated: boolean, - filesSearched: number, -): string { - if (matches.length === 0) { - return `No matches found in ${filesSearched} memory file(s).`; - } - - const lines: string[] = []; - lines.push(`Found ${totalMatches} match(es) in ${filesSearched} file(s):`); - - if (truncated) { - lines.push(`(Showing first ${matches.length} results)`); - } - - lines.push(""); - - // Group by file - const byFile = new Map(); - for (const match of matches) { - const existing = byFile.get(match.file) || []; - existing.push(match); - byFile.set(match.file, existing); - } - - for (const [file, fileMatches] of byFile) { - lines.push(`## ${file}`); - lines.push(""); - - for (const match of fileMatches) { - lines.push(`**Line ${match.line}:**`); - - // Show context before - if (match.context.before.length > 0) { - for (const ctx of match.context.before) { - lines.push(` ${ctx}`); - } - } - - // Show matching line (highlighted) - lines.push(`> ${match.content}`); - - // Show context after - if (match.context.after.length > 0) { - for (const ctx of match.context.after) { - lines.push(` ${ctx}`); - } - } - - lines.push(""); - } - } - - return lines.join("\n"); -} diff --git a/packages/core/src/agent/types.ts b/packages/core/src/agent/types.ts index bc895d1a..2b829a0b 100644 --- a/packages/core/src/agent/types.ts +++ b/packages/core/src/agent/types.ts @@ -21,7 +21,7 @@ export type AgentLogger = { }; export type AgentOptions = { - /** Agent Profile ID - loads predefined identity, personality, memory and other configurations */ + /** Agent Profile ID - loads predefined identity, personality, and other configurations */ profileId?: string | undefined; /** Profile base directory, defaults to ~/.super-multica/agent-profiles */ profileBaseDir?: string | undefined; diff --git a/packages/ui/src/components/tool-call-item.tsx b/packages/ui/src/components/tool-call-item.tsx index ad258b51..1c6551a5 100644 --- a/packages/ui/src/components/tool-call-item.tsx +++ b/packages/ui/src/components/tool-call-item.tsx @@ -9,7 +9,6 @@ import { Search, FolderOpen, Globe, - Database, GitBranch, BarChart3, ChevronRight, @@ -35,10 +34,6 @@ const TOOL_DISPLAY: Record = { glob: { label: "Glob", icon: Search }, web_search: { label: "WebSearch", icon: Globe }, web_fetch: { label: "WebFetch", icon: Globe }, - memory_get: { label: "MemoryGet", icon: Database }, - memory_set: { label: "MemorySet", icon: Database }, - memory_delete: { label: "MemoryDelete", icon: Database }, - memory_list: { label: "MemoryList", icon: Database }, delegate: { label: "Delegate", icon: GitBranch }, data: { label: "Data", icon: BarChart3 }, } diff --git a/scripts/swe-bench/run.ts b/scripts/swe-bench/run.ts index e35efa72..42025bac 100644 --- a/scripts/swe-bench/run.ts +++ b/scripts/swe-bench/run.ts @@ -217,7 +217,7 @@ async function runTask( enableSkills: false, tools: { // Only allow coding tools — no web, no cron, no sessions - deny: ["web_fetch", "web_search", "cron", "data", "delegate", "memory_search", "send_file"], + deny: ["web_fetch", "web_search", "cron", "data", "delegate", "send_file"], }, };