Symbolic memory for TypeScript, MCP, residents and humans
Start with memory workflows for a runnable example, task resumption, recipes, scoped helpers and change notifications.
Marina’s durable memory is text, evidence, typed claims and versioned state. Embeddings are
optional indexes. Exact symbolic queries and graph traversal work with --embeddings none,
without model downloads or inference. See the service guide for provisioning,
API contracts, operating limits and reproducible checks.
TypeScript and JavaScript
Section titled “TypeScript and JavaScript”Build the package with bun run build:memory. In this checkout or a package containing that
build, marina/memory exports a fetch-only client and public TypeScript types. It runs under
Node, Bun and browser bundlers with fetch and AbortSignal.any/timeout support. This source change has not been published to npm.
Keep server credentials in server code; a browser client must receive its own appropriately
scoped credential and have network/CORS access.
import { MarinaMemoryClient } from "marina/memory";
const memory = new MarinaMemoryClient(url, token);const evidence = await memory.capture(spaceId, {tool: "test", result: "3 pass"}, "task:123", "evidence-1");const saved = await memory.remember(spaceId, { content: "The test suite passed for this revision.", claim: {subject: "task:123", predicate: "test:status", object: {kind: "literal", value: "passed"}}, source_ids: [evidence.id],}, "status-1");const results = await memory.query(spaceId, {subject: "task:123", predicate: "test:status"});await memory.remember(spaceId, { content: "Task 123 belongs to Marina.", claim: {subject: "task:123", predicate: "project", object: {kind: "entity", id: "project:marina"}},}, "task-project-1");const graph = await memory.graph(spaceId, {subject: "task:123", max_depth: 2});await memory.saveCheckpoint(spaceId, "work", 0, {next: "review", record: saved.id}, evidence.seq, "checkpoint-1");Query filters are conjunctive and exact. 1, "1", true, null, and an entity named "1"
are distinct objects. Symbols are case-sensitive, without stemming, alias resolution or Unicode
normalization. Use stable namespaced IDs shared by your applications. Numbers use JSON’s
JavaScript numeric representation; use strings for identifiers and exact large integers.
A query page contains records, generation and next_cursor. Evidence or access changes invalidate existing
cursors with 409 query_changed; restart to avoid silently mixing states. Checkpoint-only saves
do not invalidate new cursors. Graph paths contain
record IDs, and each edge contains the full current record. truncated means at least one reachable assertion was omitted by the edge or depth budget.
The boundary is checked, so a terminal node or fully visited cycle does not produce a false flag.
For a question or task, use one bounded retrieval call:
const result = await memory.retrieve(spaceId, { task: "deployment rollback procedure", max_results: 6, max_bytes: 8192, source_bytes: 2048,});for (const item of result.evidence) { if (item.kind === "source") { console.log(item.id, item.text_hash, item.start, item.end, item.text); } else { console.log(item.id, item.version, item.content); }}console.log(result.diagnostics);retrieve combines inspectable planning, lexical/symbolic discovery and original source reads.
It needs no embedding or model call by default. It searches captured originals even when no
summary or authored record exists, then returns UTF-8 windows near the search matches. Source
IDs, hashes and byte ranges can be verified with sourceRange and quoted by runMemoryTask.
Pass steps for a custom read program; use_model:true instead requests the configured planner.
The default budgets are 6 evidence items, 8192 serialized evidence bytes and 2048 text bytes
per source. Limits are 20 items, 65536 evidence bytes and 8192 bytes per source. Metadata,
plans and diagnostics are outside max_bytes. status distinguishes evidence, empty
and budget_exhausted; truncated and diagnostics.next_actions explain omissions.
One sparse all-term source search may be supplemented by matching any term; set broaden:false
to disable this and inspect diagnostics.broadened before using matches. This helps when a
recent journaled question matches more query words than the older answer. Phrase and symbolic constraints
are never broadened. This does not provide automatic semantic paraphrase recall.
Records must be current and valid at valid_at (UTC milliseconds, default request time).
Pass time at the top level; it applies to every symbolic step. Original documents remain
readable historical evidence and may contain conflicting or obsolete assertions. Retrieval
does not certify relevance, truth, completeness or answer sufficiency. A 409 plan_changed
requires a fresh call; evidence is not returned from a partially invalidated plan.
MCP for external coding agents
Section titled “MCP for external coding agents”Provision and start the memory service; no world login is needed:
bun run memory init --db data/memory.db --name coding-agent --credentials data/coding-agent.jsonbun run memory serve --db data/memory.db --embeddings noneThe stdio bridge uses only the HTTP URL and a scoped memory credential:
bun run scripts/memory-mcp.ts --url http://127.0.0.1:3301 --credentials /absolute/path/to/data/coding-agent.jsonIt exposes memory_workflow, memory_retrieve, memory_service, memory_assist, memory_remember, memory_query and memory_graph. Start
with memory_retrieve arguments {task:"deployment rollback procedure"} for citable evidence.
The generic
service tool accepts {operation, space_id?, id?, input?, key?}. Its operations match the HTTP
client: capabilities, usage, me, spaces, create_space, space, remember, get, revise,
query, graph, search, context, capture, capture_batch, sources, source_search, source_range,
retrieve, plan, execute_plan, vocabulary, save_vocabulary, checkpoint, save_checkpoint,
grant, forget, export, job, reindex, review, reaffirm, cache_get, cache_put,
cache_delete, export_bundle, import_bundle, federation_mounts, federated_search,
federated_read, acknowledge. id is the record, checkpoint name or job ID
as appropriate; input is the HTTP body, or GET options such as version, after, limit.
Capture uses input: {content, session_id?}; checkpoint writes use
input: {expected_version, source_cursor, source_ids?, data}. Space omission uses the configured default.
Tool replies include readable text plus structuredContent: {ok, space_id, result} or
{ok:false,error:{code,message,status}}, and isError. Mutations accept an idempotency key;
reuse it after an ambiguous failure. The bridge does not emit tokens to stdout. It fails
startup if the credential cannot read the configured space.
Claude Code supports stdio MCP servers via its mcp add command:
claude mcp add --transport stdio marina-memory -- bun run /absolute/path/to/Marina/scripts/memory-mcp.ts --url http://127.0.0.1:3301 --credentials /absolute/path/to/data/coding-agent.jsonThis configuration follows Claude Code’s MCP documentation. For Codex, add a server entry to your Codex configuration:
[mcp_servers.marina_memory]command = "bun"args = ["run", "/absolute/path/to/Marina/scripts/memory-mcp.ts", "--url", "http://127.0.0.1:3301", "--credentials", "/absolute/path/to/data/coding-agent.json"]The command/args configuration follows Codex MCP documentation.
Use an absolute Bun executable path if the application cannot find it. Alternatively, set
MARINA_MEMORY_TOKEN, MARINA_MEMORY_SPACE and MARINA_MEMORY_URL in the bridge’s environment;
keep the token out of checked-in configuration. These recipes are documented configurations;
protocol tests run an MCP SDK client, not the actual Claude or Codex applications.
Skills
Section titled “Skills”The portable marina-memory skill teaches evidence capture,
symbolic recall, checkpoint recovery and explicit correction. Copy its folder into the target
project’s .agents/skills/marina-memory for Codex or .claude/skills/marina-memory for Claude.
Those discovery locations follow the Codex skill documentation
and Claude skill documentation. Configure MCP separately;
a skill does not provide credentials or create storage by itself. No global configuration is
changed by this implementation.
Residents and human use
Section titled “Residents and human use”Residents and humans can run memory retrieve <task>. Resident agents also use
marina_memory_service with {operation:"retrieve",input:{task:"..."}}; both return the same
evidence and diagnostics as HTTP and MCP, subject to the caller’s space permissions.
The full world exposes /v1/memory on its HTTP/WebSocket port (normally 3300). World MCP
is a different listener (normally 3301, /mcp), with login/auth and the same four service
tools. The standalone memory server also defaults to 3301; use different ports if running both.
The stdio bridge connects to the memory HTTP listener, not the world MCP listener.
World commands bind to the logged-in durable world user principal and lazily create a private
resident space. This applies to human and agent world accounts; existing agent-runtime
principals are not silently merged into those accounts. Binding inherits the world’s login
policy: passwordless world login is not a secure external identity provider. Standalone API
credentials have their own audience and cannot authorize world operations.
memory servicememory claim project:marina status "active"memory relate task:123 project project:marinamemory query {"subject":"project:marina"}memory graph task:123memory show RECORD_IDmemory sources incident Kestrelmemory source SOURCE_IDmemory plan Aster migration approvalmemory vocabularymemory review {"kind":"competing"}memory resolve RECORD_ID evidence_weighted {"competing":["OTHER_ID"],"rationale":"two independent sources"}memory api {"operation":"checkpoint","id":"work"}Human verbs translate into the same service requests; responses retain full content, IDs and provenance. They do not run an LLM to guess the intended mutation. An assistant using the skill can translate natural language into explicit claims. There is no new dashboard editor.
Resident TypeScript clients can call MarinaClient.memoryService(request), which waits for a
correlated reply rather than relying on the short command-output drain window. Full-profile
residents receive marina_memory_service; compact profiles discover the commands in their
command roster. MCP commands await completion and serialize per session to keep concurrent
replies separate. All service reads/writes retain the shared service’s permission checks.
Use explicit grants to share a space between an external service principal and a world account.
Read the principal IDs through each interface’s me operation. Existing memory set/get,
note, recall and pools retain their numeric interfaces. Schema migration 138
converts their stored assertions to canonical records automatically.
Numeric verbs as canonical adapters
Section titled “Numeric verbs as canonical adapters”Numeric commands (note, skill, reflect, pools and /mem) are synchronous
adapters over the canonical repository. A numeric handle keeps its ID and world
permissions, while its text resolves directly from the current or pinned historical
record version. All fact-like producers, including skill import/compose and DB-level
writers, use this path. Adopted reflections reuse their canonical record.
Create, correction, sources, verification and relationships commit atomically.
note delete retires a record without cascading erasure; explicit durable forget
removes its history and handles. A successful response needs no asynchronous replay.
Migration 138 converts existing data and pending intents before removing the queue.
Source URLs remain provenance and cannot establish a write binding. Creating a
human account never claims an accountless namespace’s system-owned durable space.
reflect files a reflector job when a memory-reflector is running. Under the LOCAL ungated
trust profile, when none runs and a runtime can serve one (provider keys present), reflect
spawns Reflector (marina/default, role memory-reflector, budget 40) on the caller’s behalf,
waits for its world account, and files the job; without a serving runtime it prints the
deterministic template with the spawn hint. Shared and public profiles never auto-spawn.
Resident checkpoints and completed-message journals use the private durable service. The runtime
awaits capture of each completed user, assistant and tool-result message before advancing.
Read checkpoint.data.journal.manifest_source_id and follow previous-manifest links for recent
messages. Every lossy context transform also awaits
capture of the complete original message array before returning a compacted view. The archive
uses ordered, UTF-8-safe source parts (json-utf8-parts-v1) plus a SHA-256 integrity hash in the
resident checkpoint. Each archive also captures an immutable manifest with
source_ids, sha256 and previous_manifest_source_id; the checkpoint exposes its
manifest_source_id. Follow the manifest chain to reconstruct earlier conversations after
successive compactions. Message boundaries let growing histories reuse uploaded parts. The
checkpoint validates that all referenced sources still exist. After restart, the resident sees
its intent, archival summary and source IDs; it can read the originals through source_range.
A failed capture or checkpoint acknowledgment aborts compaction and retains local history.
Forgetting invalidates checkpoints. A running resident that has observed a checkpoint will
refuse to recreate it after invalidation; restart that resident before further checkpointing.
This prevents automatic re-archival of its old local buffer. It does not erase context already
held by an external client. Unfinished streaming output can still be lost on abrupt process
death; external tool effects are not transactional with their result capture. Raw archives
remain private. Existing compactionPool configuration still shares a bounded summary after durable
capture; this explicit opt-in is best-effort and does not publish the raw archive.
For revision-aware dependency review, bounded batch/retry contracts and operational snapshots, see the reliability guide.
Storage usage is available through memory.usage() (TypeScript/Python), the MCP service operation
usage, or the human command memory usage. Limits aggregate all spaces owned by the current
principal and include history and retry receipts. See storage admission and recovery.
TypeScript callers can use memory.withSignal(signal) and retryMemoryOperation(..., {signal})
to stop local waits and retries. The memory-only MCP bridge forwards protocol cancellation;
resident journaling uses the agent runtime signal. A sent write can still commit after abort:
reuse its original request key to recover the receipt.
