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_createwithoutsummarywill need updating.
Non-goals¶
- automatic or LLM-generated summarization;
- semantic or vector search over
tagsorsummary; - retroactively requiring
summaryon 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.