Skip to content

ADR-0002: Bounded continuity index via required summary and tags

  • Status: accepted
  • Date: 2026-09-18

Context

continuity_context bounds the number of memory, task, and receipt rows it returns (LIMIT clauses of 10/20/20 by default, clamped to 50/50/100), but it returns each row's full text — memories.body, tasks.description, and every handoff_* field — with no per-field size limit. Measured live on this project: one continuity_context call returned 163,429 characters (roughly 41,000 tokens, about 4% of a 1,000,000-token context window), because several memory bodies and one task description already exceed 10 KB.

This contradicts the service's own description of continuity_context as "a bounded recovery index... not a replacement for an unbounded conversation transcript" (see docs/agents/continuity-contract.md). The startup contract requires every fresh session to call continuity_context before doing anything else, so this cost is paid on every session start, and grows with the project's history rather than staying bounded.

A related, separately-learned lesson (task 8dd2d19b, the write_context description fix) is that documentation and repo-level convention files (AGENTS.md, CLAUDE.md, Claude Skills) only reach the clients that read them. AGENTS.md is not read by ChatGPT or Cursor connecting over MCP; Claude Skills are not read by Codex. The only layer every MCP client sees, regardless of harness or provider, is the tool's own schema and description as returned by tools/list. A behavior that must hold for every client has to be enforced there, not only documented elsewhere.

Decisions

summary is a required, length-capped field, not free text

memory_add and task_create require a summary argument: a plain string, 1–280 characters, no markdown. It is the human/agent-authored answer to "what happened and why it matters," written at the moment of writing the full body/description — not a truncation of it, and not machine-generated. task_update accepts summary as optional; when supplied it replaces the existing value, when omitted the existing value is kept unchanged.

280 characters mirrors a single short paragraph: long enough to be useful, short enough that ten summaries together stay under roughly 3,000 characters (under 1,000 tokens), independent of how large the underlying body or description grows.

tags is optional, normalized, and bounded

memory_add, task_create, and task_update accept an optional tags argument: at most 5 entries, each 1–30 characters, lowercase ASCII letters, digits and hyphens only (^[a-z0-9]+(-[a-z0-9]+)*$). The server normalizes (lowercases) and de-duplicates on write rather than silently accepting inconsistent casing that would make keyword search unreliable.

continuity_context becomes an index, not a transcript

continuity_context, and the row previews in memory_search/task_list, return id, title, summary, tags, and the existing structural fields (kind/status, timestamps, version) — never the full body/ description/handoff_* text. Full content remains exactly one call away, unchanged, via memory_get/task_get by id, matching the protocol's existing "follow the returned IDs for more detail" design.

Enforcement lives in the tool contract, not in a convention file

The summary requirement is validated by the server and rejected before any row is written, the same way write_context already is. AGENTS.md, CLAUDE.md, and a Claude Skill may exist on top of this to help an agent write a good summary, but none of them are the reason a summary exists — the required, validated parameter is.

No automatic summarization

Summaries and tags are always supplied by whoever calls memory_add/ task_create/task_update — a human or an agent, deciding at write time. MVP.md explicitly defers "automated summaries" and "embeddings and vector search" until the base continuity hypothesis is validated; this decision does not pull that forward. tags-based filtering in memory_search/ task_list is exact-match keyword filtering, not semantic search.

Backward compatibility

Rows written before this change have no summary. They are not retroactively required to have one to remain valid; a follow-up backfill (mechanical placeholder derived from the first ~200 characters of the existing text, clearly marked as such, plus hand-written summaries for the handful of entries most likely to be read again) closes the gap without blocking this change from shipping.

Consequences

  • continuity_context's cost no longer grows with the size of individual memory bodies or task descriptions — only with row count, which is already bounded.
  • Every MCP client, regardless of provider, is guaranteed to have supplied a summary for any entry it wrote after this change, without needing to read any documentation, AGENTS.md, CLAUDE.md, or Skill first.
  • Keyword filtering (tags) becomes possible without introducing embeddings or a search index.
  • Writers must think of one more short field per write; existing tests and any script that calls memory_add/task_create without summary will need updating.

Non-goals

  • automatic or LLM-generated summarization;
  • semantic or vector search over tags or summary;
  • retroactively requiring summary on rows written before this decision;
  • treating AGENTS.md/CLAUDE.md/a Skill as sufficient enforcement on their own — they are a quality layer on top of the required field, not a substitute for it.