Repository navigation
Define the canonical extract / recall / remember MCP triad as the agent interface #15
Description
Context
The local MCP server (packages/mcp/src/index.ts) exposes ~20 fine-grained tools (scrape_markdown, crawl_site, extract_brand, remember_site_snapshot, upload_proof_to_walrus, recall_site_context, query_site_memory, get_context_bundle, ...) and the hosted server (packages/mcp/src/hosted.ts) exposes a different read-only set via registerToolAliases. There is no single, stable triad an agent (or a Talus/Nexus node) can bind to. The roadmap (exec summary + §6) makes contextmem.extract / .recall / .remember the canonical capabilities to publish as Talus Tools. Note buildAgentReadableSite is already imported in index.ts but never wired into a tool.
Goal / user story
As an agent builder, I want three high-level, well-documented MCP tools — contextmem_extract, contextmem_recall, contextmem_remember — with stable typed I/O, so I can integrate ContextMEM in one step and have my calls map 1:1 to Talus Nexus DAG nodes, without learning 20 internal verbs.
Acceptance criteria
-
contextmem_extractaccepts{ target, mode?: 'auto'|'web'|'walrus', maxPages?, namespace? }and returns a stable JSON envelope{ namespace, runId, target, pageCount, chunkGraphDigest, artifactDigest, summary }by composingcrawlWebSite/scrapeWebPage/resolveWalrusTarget+buildAgentReadableSite+ facts. -
contextmem_rememberaccepts{ runDir|runId, namespace?, storeOnWalrus?: boolean, whatChanged? }and unifies today'sremember_site_snapshot+upload_proof_to_walrus+rememberStorageIndex, returning{ namespace, writeMode, written, receipt?, memoryRef }. -
contextmem_recallaccepts{ namespace, query, topK?, includeSources?: boolean }and returns{ namespace, query, answer?, sources: [...], memory }, unifyingrecall_site_context/query_site_memory/get_context_bundle. - All three tools exist on BOTH the local (
index.ts) and hosted (hosted.ts) servers with identical names and output shapes (hostedextract/remembermay be gated/no-op where the Worker can't pack — return a typedunsupportedenvelope rather than throwing). - The ~20 existing tools remain registered as advanced aliases (reuse the
registerToolAliasespattern) so nothing breaks, but tool descriptions point agents to the triad first. - A shared zod schema module exports the three input/output types so
cli,mcp, and any Nexus tool-def import the same contract; unit tests assert the output envelope shape for each tool.
Implementation notes
- Touch
packages/mcp/src/index.ts,packages/mcp/src/hosted.ts, and addpackages/mcp/src/triad.ts(orpackages/coreif shared with CLI) holding the zod I/O schemas + the composition functions. - MCP tool names cannot contain dots; use
contextmem_extract|recall|rememberas the MCP names and reserve the dottedcontextmem.extractform for the Talus/Nexus node identifiers (document the mapping). extractshould reusecreateRunId+ the run-dir layout already produced (manifest.json, llms.txt), and surfacechunkGraphDigest()/artifactDigest(frompackProofBundle) since those are the verifiable anchors the receipt hook (separate issue) needs.rememberstorage path is Node-only today (packProofBundleshellstar); on the hosted/Worker side return{ storedOnWalrus: false, reason }until the Worker-side R2 zip packer lands — keep the contract identical so the receipt hook is uniform.recallon hosted should delegate to the existingstore.recallMemory+searchContextplumbing already inhosted.ts.- Keep output JSON-only (the
text()/textResult()helpers) so Nexus can parse deterministically.
Sui Overflow angle
This triad is the agent-facing surface ContextMEM demos for Sui Overflow: a single, legible extract → remember → recall loop is what a Talus/Nexus DAG composes, and what the on-chain attribution receipt attaches to. Judges and agent builders see one clean Sui-native memory primitive instead of 20 verbs, and every node output already carries the digests that prove what was extracted/stored on Walrus.
Dependencies
None (foundational). Blocks: the on-chain attribution-receipt issue and the reference-agent-templates issue.
Part of the ContextMEM roadmap (#4) • Sui Overflow build.
Activity
harrymove-ctrl commentedon Jun 19, 2026
Implementation Plan
Approach
Introduce a single canonical agent triad —
contextmem_extract/contextmem_recall/contextmem_remember— as thin, well-documented MCP tools that wrap the existing fine-grained verbs already proven inpackages/cli/src/index.tsandpackages/mcp/src/index.ts. The key design decision is a two-layer split: (1) a runtime-agnostic, zod-only contract module in@contextmem/core(triad-schema.ts) thatcli,mcp(local + hosted), the Worker, and future Nexus tool-defs all import as the single source of truth; and (2) Node-only composition functions inpackages/mcp/src/triad.tsthat orchestratecrawlWebSite/scrapeWebPage/resolveWalrusTarget→buildAgentReadableSite(+ facts) →packProofBundle/uploadProofBundle→MemWalMcpClient. The ~20 existing tools stay registered as advanced aliases; their descriptions are reworded to point agents at the triad first. Hostedextract/rememberreturn a typedunsupportedenvelope (the Worker can't shelltar) while hostedrecallcomposes the existingstore.recallMemory+searchContextplumbing — keeping output shapes identical across both servers so the downstream receipt hook is uniform.Prerequisites & dependencies
chunkGraphDigest+artifactDigest(the receipt anchors).contextmem.extract|recall|rememberNexus node identifiers are reserved/documented only; the actual Nexus tool-def wrapper is a later issue (roadmap §2/§6). Confirm the Nexus schema/ABI before freezing struct names, but the MCP I/O contract can ship now.remember/recall:MEMWAL_PRIVATE_KEY(Ed25519 hex seed) +MEMWAL_ACCOUNT_ID(already required byMemWalMcpClient, seepackages/memwal/src/index.ts:88-89).rememberwithstoreOnWalrus:true:TATUM_API_KEY(already used byuploadProofBundle).recall: existing per-request MemWal headers/secrets viamaybeWithMemWalRecall(apps/api/src/worker.ts:3609). No Vectorize/Harbor/Sui wallet needed for this issue."zod"topackages/core/package.jsondependencies (root already pinszod ^4.2.0;mcpalready uses it).Step-by-step
Create the shared contract module
packages/core/src/triad-schema.ts.ExtractInput = { target: string; mode?: 'auto'|'web'|'walrus'; maxPages?: number; namespace?: string }ExtractOutput = { namespace; runId; target; pageCount; chunkGraphDigest; artifactDigest; summary }RememberInput = { runDir?: string; runId?: string; namespace?: string; storeOnWalrus?: boolean; whatChanged?: string }RememberOutput = { namespace; writeMode: 'snapshot'|'storage'|'snapshot+storage'|'unsupported'; written: number; receipt?: WalrusStorageReceipt; memoryRef?: {...}; supported: boolean; reason?: string }RecallInput = { namespace: string; query: string; topK?: number; includeSources?: boolean }RecallOutput = { namespace; query; answer?; sources: Array<{...}>; memory }TRIAD_TOOL_NAMES = { extract: 'contextmem_extract', recall: 'contextmem_recall', remember: 'contextmem_remember' }constant and aNEXUS_NODE_IDS = { extract: 'contextmem.extract', ... }mapping (document that MCP names cannot contain dots; the dotted form is the Talus/Nexus node id only).unsupportedRemember(namespace, reason)helper that returns the typedunsupportedenvelope.packages/core/src/index.ts(appendexport * from "./triad-schema.js";). Add the barezodimport; do not importwalrus/memwalhere (core is the lowest layer — keep it dependency-free upward).Create the local composition module
packages/mcp/src/triad.ts(Node-only; imports core+walrus+memwal whichmcpalready depends on).extractContext(input, { runsDir }): Promise<ExtractOutput>:ExtractInputSchema. Resolve mode:'walrus'(or'auto'+ 0x/preview target) →resolveWalrusTarget+materializeWalrusSite(mirrorindex.ts:120-131); else'web'→crawlWebSite(target, { maxPages, maxDepth: 1, includeImages: true })(mirror CLIindex.ts:395) orscrapeWebPagefor single-page.const runId = createRunId('mcp-extract')(utils.ts:10);outputDir = path.resolve(runsDir, runId).buildSiteFacts(target, pages, buildChunks(pages))(facts.ts:374) wrapped in try/catch (LLM optional).buildAgentReadableSite({ runId, target, outputDir, pages, facts, ...walrus })(package.ts:61). This already writescontext/manifest.json,llms.txt,chunks.ndjson, andcontext/snapshot.json(which carriesartifactDigest+chunkGraphDigest,package.ts:153-162).context/snapshot.jsonforartifactDigest/chunkGraphDigest; computenamespace = input.namespace ?? namespaceForTarget(...)(utils.ts:123);summary = summarizeSnapshot(...)(memwal/index.ts:302). Return validatedExtractOutput.rememberContext(input, { runsDir }): Promise<RememberOutput>:absRunDirfromrunDirorpath.resolve(runsDir, runId); readcontext/manifest.json. Resolvenamespacelikeindex.ts:212-213.SiteSnapshotfrom manifest (mirrorindex.ts:170-182) →rememberSnapshot→ counts aswritten.storeOnWalrus:packProofBundle(absRunDir)(storage.ts:231) →uploadProofBundle({...artifactDigest, wait:true})(storage.ts:183) → writecontext/storage.json→rememberStorageIndex({ namespace, target, runId, receipt, whatChanged })(memwal/index.ts:164); setreceipt,memoryRef,writeMode='snapshot+storage'.writeMode='snapshot'. Return validatedRememberOutputwithsupported:true.recallContext(input): Promise<RecallOutput>:memory = await new MemWalMcpClient().recallSiteContext(namespace, query)(memwal/index.ts:171). IfincludeSources, also callanalyzeSiteMemory(memwal/index.ts:175) and map intosources. Return validatedRecallOutput.Register the triad on the local server
packages/mcp/src/index.ts.server.tool(...)registrations at the top (beforescrape_markdown) usingTRIAD_TOOL_NAMES, the zod input schemas from core, rich descriptions ("Canonical agent interface — start here. Maps to Nexus node contextmem.extract"), delegating to thetriad.tsfunctions with{ runsDir }. Keep returning JSON via the existingtext()helper (index.ts:313)."Advanced/low-level — prefer contextmem_extract|recall|remember."(no behavior change, only.describe()/description strings).Register the triad on the hosted server
packages/mcp/src/hosted.ts.createHostedContextMemMcpServer, register all three via the existingregisterToolAliasespattern (hosted.ts:91/270), namescontextmem_extract|recall|remember, sharingnamespaceInputSchema.contextmem_recall: composeresolveNamespace→store.searchContext(or fallbacksearchHostedArtifacts) forsources→store.recallMemory?.(namespace, query)formemory(mirrorget_context_bundleathosted.ts:241-249), shaped toRecallOutput(setmemory.available:falsereason when MemWal headers absent).contextmem_extractandcontextmem_remember: return the typedunsupportedenvelope viatextResult(unsupportedRemember(namespace, "Hosted Worker cannot pack/upload — use the local @contextmem/mcp server or CLI"))— not an MCP error, so Nexus can parse it deterministically. (Wiring an enqueue path toprocessExtractionJobatworker.ts:1951is explicitly deferred to the Worker-side R2 zip-packer roadmap item.)Point CLI at the shared contract (lightweight): import the triad types from
@contextmem/coreinpackages/cli/src/index.tsso any futurecontextmem extract|remember|recallCLI verbs reuse the identical I/O — satisfies "cli imports the same contract" without rewriting the existing inline composition now. (Optional: thin CLI subcommands that calltriad.ts— note as a follow-up, not required by ACs.)Tests — see Test plan. Add
packages/mcp/src/triad.test.tsand extendpackages/mcp/src/hosted.test.ts.New / changed files
packages/core/src/triad-schema.ts— zod I/O schemas, types,TRIAD_TOOL_NAMES,NEXUS_NODE_IDS,unsupportedRemember.packages/mcp/src/triad.ts—extractContext/rememberContext/recallContextcomposition.packages/mcp/src/triad.test.ts— envelope-shape unit tests.packages/core/src/index.ts— re-export triad-schema.packages/core/package.json— addzoddependency.packages/mcp/src/index.ts— register 3 triad tools; reword 20 descriptions.packages/mcp/src/hosted.ts— register 3 triad tools (recall composed; extract/remember unsupported).packages/mcp/src/hosted.test.ts— assert hosted triad output shapes.packages/cli/src/index.ts— import shared triad types.Test plan
ExtractOutput/RememberOutput/RecallOutput.safeParse()accept the exact envelopes;unsupportedRemember()matchesRememberOutputSchemawithsupported:false, writeMode:'unsupported'.triad.test.ts): mockcrawlWebSite/buildSiteFacts/MemWalMcpClient/packProofBundle/uploadProofBundle(vitestvi.mock); assertextractContextreturns all 7 keys with a realrunId(mcp-extract_…),pageCount === pages.length, andchunkGraphDigest/artifactDigestread from the generatedcontext/snapshot.json; assertrememberContextreturnswriteMode:'snapshot'withoutstoreOnWalrusand'snapshot+storage'(withreceipt+memoryRef) when true; assertrecallContextreturns{namespace,query,sources,memory}.hosted.test.ts— instantiate the server with the existing mockHostedNamespaceStore; assertcontextmem_extract/rememberproduce a parseableunsupportedenvelope (notisError), andcontextmem_recallreturns theRecallOutputshape (sources fromsearchContext,memory.available:falsewhen norecallMemory).extractContext({target:'https://example.com'})→ feed itsrunDirintorememberContext({runId, storeOnWalrus:true})→ assert a real Tatum/WalrusWalrusStorageReceiptwithcertified:true+ ablobId, thenrecallContext({namespace, query})returns non-emptymemory— closing the extract→remember→recall loop.tsx packages/mcp/src/index.ts), list tools, confirm the triad appears first and the 20 advanced tools still register.bun-check.yml(typecheck + test + build) must stay green; verify@contextmem/coretypechecks with the new zod import.Risks & gotchas
packProofBundleshellstarviachild_process(storage.ts), unavailable undernodejs_compat. Hostedextract/rememberMUST return the typedunsupportedenvelope, never throw — identical contract so the receipt hook is uniform. Do not attempt Worker-side encryption/zip here (separate roadmap item).snapshot.json.artifactDigest(sha256 over per-filepath:sha256,snapshot.ts:100) vspackProofBundle.artifactDigest(sha256 over the.tgzbytes,storage.ts:243).extractsurfaces the snapshot digest (pre-upload);remember'sreceipt.artifactDigestis the tgz digest. Label clearly so the receipt issue anchors the right one.text()/textResult()so Nexus parses deterministically; never return prose.recall/rememberdepend on the relayer (already retried inmemwal/index.ts:254); missingMEMWAL_*throws in the constructor — guard localremember/recallso a missing-cred error becomes a clean message, and hostedrecalldegrades tomemory.available:false.restore_site_memory/recall_memoryaliases stay intact.rememberkeeps today's Tatum/mainnet path; do not reconcile Harbor/testnet in this issue.rememberStorageIndextext format is unchanged (no schema migration); no D1 changes.Acceptance-criteria mapping
contextmem_extractenvelope → Steps 1 (schema), 2 (extractContext), 3 (local register), 4 (hosted unsupported).contextmem_rememberunifies snapshot+upload+index → Steps 1, 2 (rememberContext), 3, 4.contextmem_recallunifies recall/query/bundle → Steps 1, 2 (recallContext), 3, 4 (hosted compose ofrecallMemory+searchContext).unsupported→ Steps 3 + 4 +unsupportedRemember.registerToolAliasesuntouched.Effort: M, ~3 person-days. ~1d schema + local composition (mostly re-wiring proven CLI/MCP code), ~0.5d hosted wiring, ~0.5d description rewording, ~1d tests + a real extract→remember→recall round-trip.
Implementation plan generated for execution (planning phase). Part of the roadmap (#4).