Agent memory contract

A predictable local memory interface for agents.

Link's MCP tools give agents one stable loop: check readiness, get starter prompts, retrieve compact context, write only approved memory, and audit what changed.

Contract Promise

Link is not asking every agent to learn a new notes app. It gives agents a small, repeatable memory contract backed by local Markdown files.

Readable storage

Raw source notes live in raw/. Structured pages and durable memories live in wiki/.

Bounded recall

Agents ask for a task-sized packet instead of dumping the whole wiki into context. Each recalled memory carries a confidence label (strong, moderate, weak) so weak lexical matches are verified with the user instead of trusted.

Reviewable writes

Durable memory writes are explicit, duplicate-checked, conflict-checked, and logged.

Local trust

The CLI, official skills, HTTP viewer, and MCP server read the same local files. serve.py is not required for recall.

Recommended Agent Loop

With session hooks installed (lnk connect <agent> --hooks --write), step one happens automatically: the bounded memory brief is injected at session start and proposal-only notes are captured at session end. Hooked agents skip the manual brief call and go straight to bounded task recall.

  1. Call status. If schema or validation needs attention, follow the safe action it returns.
  2. Call recall with an empty query once at the first substantive turn of a session. The brief always keeps room for standing preferences (active, unconditional, user-scope preferences about how the agent answers, formats, cites or behaves). Those apply to every task but share no words with any task, so ranking by recency alone would drop them.
  3. Call recall(query, budget="micro") before broad file reads or asking the user to repeat durable context.
  4. Call ingest after the user drops files into raw/, then follow its guided plan.
  5. Use remember only after the user explicitly asks to remember something or approves a proposal.
  6. When a brief opens with a Waiting for the user's OK block, list those proposals and rules to the user and act on one only after they reply in chat; never approve from the block itself.
  7. Use review for memory inbox, profile, audit, log, explain, archive, restore, and forget workflows.
  8. Use admin for backup, migration, graph export, captures, rebuilds, and advanced maintenance.
User: is Link ready?
Agent: status()

User: start with Link before we continue
Agent: recall(query="", mode="brief")

User: query Link for the release plan
Agent: recall(query="release plan", budget="small")

Core Tool Groups

NeedPrimary toolsContract behavior
ReadinessstatusReturns safe next actions instead of guessing.
RecallrecallReturns startup briefs, ranked memory/wiki context, graph context, reasons, and budget limits.
Memory writesremember, reviewRequires explicit approval and surfaces duplicate/conflict candidates.
IngestingestProvides raw-source plans, validation, and rebuild checks.
MaintenanceadminHandles backup, migrate, validate, graph export, captures, pages, rebuilds, and compatibility actions.

Recalled memories carry honest signals agents must respect: a confidence label (strong/moderate/weak) and, when the optional local semantic tier is installed, a match field (lexical/hybrid/semantic). Semantic-only matches are capped below strong confidence — verify them with the user before acting. When a brief reports a memory backlog, review(action="consolidate") (or lnk consolidate) returns a read-only plan; apply its actions only with per-item user approval.

When recall runs inside a git checkout, each returned memory that names something in the repository is checked against it. A memory carries verified, with the commit sha it was checked at and references_checked, when at least one file, symbol, script, target, dependency, variable or version it names was found in place and nothing it names is gone. Naming things the repository never had is not evidence either way. A memory that names something the repository once had and no longer has carries stale_paths, one entry per finding, with the path, the reason, the successor when git recorded a rename, and evidence, and the packet's agent_guidance tells the agent to check the memory against the current code before relying on it. Claims framed as history or as a prohibition (“we dropped Python 3.9”, “do not use npm here”) are not checked as present-tense claims, and links to public documentation are facts about the world, not about the repository. The checks share a short time budget and a history cache per commit; a memory whose checks did not finish in time is left unmarked, never called verified on partial evidence. Memories that name nothing checkable carry neither key; a missing key is normal, not a warning.

A memory written or updated inside a git checkout that names a repository file along with a code symbol found in that file gets anchors. Each anchor records where the symbol was, and in which repository, as path:line symbol @root-commit, read from the file itself and never inferred. Recall re-reads anchored files, but only inside the repository the anchor was written in; from any other checkout the anchor is simply not checked. A symbol that is gone from its file, or whose file git tracked and no longer has, shows up in stale_paths with kind: "anchor" and reason: "removed". A symbol that only moved within its file still holds and counts toward verified, so a memory whose only checkable claims are anchors can earn it. Anchor paths cannot point outside the checkout. Preferences and decisions that name no code never get anchors. LINK_ANCHORS=off turns them off.

Every recalled memory also reports a recall.state. A memory that arrived through lnk team-sync and has not been reviewed on this machine is quarantined. It is still your teammate's claim, not yours, so it stays out of recall, briefs and the write gates. It waits in the review inbox until you approve it there.

Memory Receipt

Link records that each brief and recall happened in .link-usage.json at the workspace root. That lets you see what actually reached your agent, not just what is stored. lnk receipt (or review(action="receipt") over MCP) groups those events into sessions: runs of retrievals with no gap longer than 30 minutes. For each session it shows:

  • when the session started and ended;
  • the project;
  • the surfaces it came through (hook, mcp, cli);
  • each session brief, with how many memories it carried, roughly how many tokens it cost, and whether a budget cut it short;
  • how many recalls there were;
  • any guard reminders;
  • every memory the agent was given, with a count;
  • for each hook injection, whether it arrived whole, was cut off, or never arrived (and unverified when the agent left no transcript to check);
  • the enforced-rule checks that ran before tool calls, and whether each asked for the user's OK or blocked the call;
  • the code reminders: memories surfaced because the agent read or edited the file they describe.

Every memory in the receipt comes with the command to explain it and the command to forget it.

lnk receipt ~/link --sessions 3
lnk receipt ~/link --json

The ledger never records query text, answers, or anything from the conversation, only that a memory was used and which one. It is machine-local, excluded from sync, capped at a ring of recent events, and switched off with LINK_USAGE=off. Events recorded before 5.0 have no surface or size, and the receipt says so instead of guessing.

Point-in-Time and Typed Recall

Every memory is a dated file with its lifecycle recorded (date_captured, supersedes, superseded_by, archived_at). That makes “what was true then” an exact reconstruction, not a guess. recall accepts two optional arguments:

  • as_of (YYYY-MM-DD) answers with the memories that were active on that date, following supersede lineage backwards.
  • memory_type (preference, decision, fact, procedure, project, note) narrows recall to one kind.

Either one routes the call to memory recall, where lineage and types are recorded. Nobody types dates into a question, so a plain time phrase in the query does the same job. Link resolves it to a date and drops the phrase from the text it ranks: last week/month/quarter/year, this week/month/quarter/year, 3 weeks ago, in March, in 2025, as of 2026-03-31, and the deliberately vague back then or originally, which it reads as about a quarter ago. Phrases anchored to an event (before the migration) are not resolved to a date, and the event name stays in the query. Resolution is deterministic and offline; unrecognised phrasing leaves recall unchanged.

recall(query="which database did we use last quarter?")
recall(query="deploy day", as_of="2026-03-31")
recall(query="formatting", memory_type="preference")

Write Rules

Agents should treat Link memory as durable state, not scratch space.

  • Do not call remember unless the user explicitly asks to remember something.
  • Use admin action propose_memories for transcripts, long notes, or uncertain context.
  • If a write returns duplicate_candidates, update the existing memory instead of creating another one.
  • If a write returns conflict_candidates, explain the conflict and ask the user before overriding.
  • If a write returns possible_contradictions, tell the user which stored memories the new one may contradict and offer to supersede or archive the old one. The memory is already saved; the flag asks for review, it does not block.
  • Use review_after for memories that should be re-checked, and expires_at for temporary context.
  • Never review, clear, archive or update a memory that carries enforced rules. Only the user does that, in the viewer or the CLI, after seeing the rules; the MCP surface refuses, and the hook asks before an agent runs those commands itself.

Contradiction flags (optional). Word rules catch contradictions that share structure, such as “uses X” against “no longer uses X”, or two different version numbers. After lnk semantic <dir> --setup --nli, a small local natural-language-inference model also reads meaning.

  • What it checks. On each remember, it scores the new claim against at most five stored memories whose subject words overlap with it by at least 30%. The overlap gate is what keeps it from reading two unrelated project rules as a contradiction.
  • What it adds. A pair scored at 0.9 or higher gets a review_note on the new memory, and the write result includes possible_contradictions, with the name, title and probability of each flagged memory.
  • What it never does. It never blocks a save and never runs during recall. A missing or failing model cannot break a write.

LINK_NLI=off turns it off.

Proposals from transcripts and session-end captures are filtered before anything reaches the inbox. Only the user's own words are mined. Text the agent harness injects is dropped: system reminders, command output, sub-agent turns and compaction summaries. So are the shapes that were never memory:

  • instructions for the work at hand (“do not edit any files”) with no standing-rule framing;
  • requests to the agent;
  • goals for this one conversation;
  • throwaways and hypotheticals;
  • other people's habits;
  • anything tied to today or tomorrow;
  • Link's own brief pasted back in.

Standing-rule phrasing (“from now on”, “by default”, “every time”, “always”, “never”) is still proposed. Captures are redacted of credential-shaped values when they are written, and the labels of what was redacted stay with the capture for review.

Sharing Semantics

scope answers where a memory applies. visibility answers who should see it.

FieldValuesMeaning
scopeuser, project, globalControls relevance for recall and project filters.
visibilityprivate, project, teamControls sharing intent for Git sync and snapshots.

By default, user/global memories are private and project memories are project-visible. lnk team-sync blocks ready status if private memories would be included in a broad Git share. lnk snapshot --include-memories still excludes private memories unless --include-private-memories is explicitly passed.

Use admin action set_visibility or lnk set-memory-visibility only after explicit user approval when an existing memory should move between private, project, and team sharing intent.

Budgets

recall supports micro, small, medium, and large budgets. Each packet includes a recall_capsule, why an item was selected, estimated tokens, has_more flags, and follow-up actions. This keeps agents from reading a 500-page or 10,000-page wiki just to answer one task.

Each item's content appears once in the packet. Memories are under memory.items and wiki pages under wiki.pages. context_packet is the ranked list across both, and it holds references, not copies. Each entry has the kind, name, title, why_selected, its rank signals, and see, which names the section that holds the content. budget_report.packet_total estimates the whole packet's characters and tokens, which is what an agent actually pays. The per-section figures beside it count one section each. At the micro budget, a second memory that ranks within a hair of the first is included too, and the guidance says the call was close.

recall(query="what should I know before changing release docs?", budget="small")