Agent Memory System: High-Level Design¶
This document explains the system-level design and its major decisions. agent-memory-lld.md specifies the implementation, components.md explains the parts through examples, and utility-aware-memory-architecture.md is authoritative for ambient profiles and host-issued memory decisions.
1. Goals and non-goals¶
What the system must do¶
- Turn interactions into durable, attributable memory records without polluting the store with guesses.
- Return the right records to the right agent at the right moment, and return nothing when nothing applies.
- Keep every retrieval observable: which query, which scope, which candidates, which survived, and why.
- Stay out of the prompt prefix so prompt caching keeps working.
- Be model-, provider-, and framework-neutral at the contract level.
- Answer a
memory_searchcall fast enough that an agent can afford to call it liberally.
What it will not do yet¶
- No image, audio, or file-content memory. Text only.
- No multi-tenant hardening, sharding, or distributed storage. Single process, single database file.
- No automatic cross-user entity merging.
- No per-turn mutation of the system prompt. The evaluation-gated ambient profile is bounded and fixed at session start.
- No self-optimizing policies (GEPA and friends come after there is a fixed contract and an evaluation set).
- No periodic cross-session consolidation. New writes still reconcile with existing records through the ingestor; a whole-store consolidation pass remains a later, evaluation-gated phase.
Priorities, in order¶
- Correctness: faithful records, correct scope, correct supersession, nothing fabricated.
- Non-pollution: a bad record is worse than a missing one, and an irrelevant retrieval is worse than an empty one.
- Latency: search must be cheap enough to call on most turns.
- Everything else.
2. System in brief¶
Memory uses SQLite as its source of truth and three ways to find the same durable records: a vector index for similar meaning, SQLite FTS5 for words and identifiers, and entity links for exact identities. Agents interact with it through five tools: memory_search, memory_get, memory_write, memory_revise, and memory_forget. In the default tool_only mode the model reaches conditional memory only through a search it chose to make. The utility-aware host path, a bounded session-stable ambient profile plus gap-conditioned, draft-relative admission for host-issued conditional memory, is implemented, validated offline on scripted blind splits, and disabled by default; a host may serve it only with a bundle whose fitness result is recorded. The decision boundary is specified in utility-aware-memory-architecture.md; the evidence is in usefulness-gate.md and acceptance-report.md. memory_write and memory_search are synchronous tool calls; session extraction, candidate review, and due temporal review run asynchronously.
Store, vector index, and FTS¶
In RAG terms, the store is the canonical document and metadata database. The vector index and FTS index are two derived ways to retrieve candidate record ids quickly. They are part of the same local memory system, but they do not play the same role.
| Component | RAG analogy | Current design |
|---|---|---|
| Store | The canonical document store plus metadata database. | SQLite persists records, scopes, source data, lifecycle state, entity links, events, logs, and each record's embedding blob. It is the source of truth. |
| Vector index | The vector-retrieval part of a vector database. | An in-memory matrix and record-id map loaded from SQLite's embedding blobs. It performs exact cosine search, applies a durable cross-process delta before each search, and can be rebuilt from the store. |
| FTS index | A lexical or keyword retriever, similar to BM25 in a hybrid RAG pipeline. | A SQLite FTS5 virtual table containing record content, subject, and entity aliases. SQLite queries it in the same process; it is not a separate search service. |
On a write, the system persists the record and embedding in SQLite, updates FTS5 and entity links, then updates the in-memory vector matrix. SQLite increments a durable records version in the same transaction, so another process refreshes its own matrix before its next search. On startup, a process rebuilds its matrix from the stored embeddings.
How memory enters the store¶
There are only two write paths.
- An agent deliberately saves something now. For example, after the user says, “I prefer concise answers,” the agent calls
memory_write, supplies that quote as evidence, and asks to create a semantic preference record. The tool checks that the agent may write to the intended scope, checks for duplicates or contradictions, assigns a status, embeds the text, and returns the new record id. - At the end of a session, a separate extraction model rereads the transcript and suggests possible memories: facts, decisions, outcomes, and an episodic summary of what happened. Every suggestion must carry an exact supporting quote from the transcript. Before persistence, a separate reviewer accepts, rejects, or narrows each candidate using the quote, surrounding turns, and relevant live records. Accepted candidates then pass through the same evidence, scope, entity, duplicate, contradiction, and lifecycle rules as explicit writes.
Time-sensitive candidates may also carry explicit validity bounds and a review_at time derived from their evidence. A background maintenance pass flags a record when that time arrives, but it does not infer that a planned event occurred or silently rewrite an evidence-backed fact. The flag and its reason are auditable inputs to later agent or user revision.
The source controls the initial status. A direct, evidenced user statement, trusted system fact, or tool result is confirmed. A conclusion drawn by an agent or extractor is provisional. A provisional record is useful but treated cautiously: it expires after 30 days unless later evidence reinforces it. The system always writes one episodic session summary at session end, even if no durable fact is extracted.
How memory is recalled¶
When an agent calls memory_search, the system follows the same sequence every time. Dense, lexical, and entity candidate generation run sequentially inside that one synchronous call; the tool returns only after the pipeline has produced its final results or an empty response. A later benchmark may justify concurrent generators.
- It optionally rewrites the raw retrieval request into a standalone search query. This stage is specified for the demo but disabled by default. When enabled, it receives the raw query and host-supplied current-turn context, then logs both the raw and rewritten queries.
- It removes records the agent is not allowed to see, records that have expired or been superseded, and records outside any requested type or time window.
- It finds candidates with three methods in order: dense search for similar meaning, lexical search for matching words and identifiers, and exact entity search for known people, projects, repositories, or organizations.
- It combines the three ranked candidate lists with reciprocal-rank fusion, which rewards a record that appears near the top of one or several lists without pretending that cosine and BM25 scores are on the same scale.
- It gives episodic records a recency adjustment, then returns no memory at all if every candidate is weak and no exact entity match exists.
- It collapses near-duplicates, optionally reranks the survivors, and returns only as much information as fits the context budget. It logs every decision so the result can be explained later.
Dense retrieval uses cosine similarity over bge-m3 dense embeddings in the in-memory vector matrix. Lexical retrieval uses SQLite FTS5 with BM25 over content, subject, and entity aliases. Entity retrieval uses exact alias matches within an authorized scope, ordered by recency; it does not do fuzzy entity resolution or automatic entity merges. The optional reranker is bge-reranker-v2-m3. It is built, measured, and disabled by default: in both placements, after the RRF floors and in place of them, it removed the expected record from the judge's candidate pool on about one turn in five, so it costs recall it does not repay; refer to usefulness-gate.md. Its measured stage cost is about 165 ms at p50 and up to 2 s at p95 on a laptop.
3. Memory types and how each is treated¶
The four CoALA categories are used as engineering categories with different rules. Working memory is not stored by this system; the host framework owns the live conversation. The memory layer stores the other three.
| Type | Stored as | Who can create it | Decays? | Retrieved by | Example |
|---|---|---|---|---|---|
| Semantic | A short declarative statement about a subject, plus entity links. | An agent recording an evidenced user statement, a trusted system or tool fact, a session extractor with evidence, or an explicit agent inference. The source and evidence determine its status. | No. Superseded by newer statements about the same subject. Provisional ones expire if never reinforced. | Dense, lexical, and entity. | “The user prefers concise technical answers.” |
| Episodic | A dated account of what happened, what was decided, and why, with an event time. | The end-of-session extractor, which always writes a session summary and may write notable decisions, or an agent explicitly recording a meaningful event. | Yes. Recency weighting on event time. Never superseded, only appended. | Dense and lexical, with time filters. | “On 3 September, the team chose SQLite because the system is single-process.” |
| Procedural | A named, versioned procedure: when it applies, the steps, and known pitfalls. | A human author, or an agent explicitly recording a reusable lesson after a task succeeds or fails. Automatic promotion from episodes is not allowed in the current design. | No. Versioned. A new version supersedes the old. | Lexical on name and trigger, dense on description. | “When changing the embedding model, re-embed the store and recalibrate retrieval gates.” |
Decision: procedural memory is stored but kept small initially. Automatic promotion of episodes into procedures is out of scope. The agent can write a procedure explicitly, and the evaluation will test whether it retrieves and follows it.
Decision: the utility-aware architecture adds an explicit ambient activation tier for stable response-shaping preferences and keeps every other record conditional by default. Ambient activation is trusted or reviewed, the profile is bounded and fixed at session start, and the feature remains disabled until its implementation phase passes the documented acceptance gates. See utility-aware-memory-architecture.md.
4. The durable record¶
Every record, regardless of type, carries the same envelope. The content varies by type; the envelope does not.
| Field group | Fields | Meaning | Example |
|---|---|---|---|
| Identity | id, type, version |
Identifies the record, its memory category, and its revision in a lineage. | mem_0142, semantic, version 2 |
| Content | content, subject_entity_id, attribute, subject |
Holds the text the model may read and its system-owned current-fact identity. subject is derived from the entity ID and normalized attribute. |
Content: “The user prefers concise technical answers.” Attribute: answer_style |
| Scope | scope_kind, scope_id |
States who owns the memory. The separate grant table determines which agents may access that scope. | user, user_123 |
| Source and evidence | source_kind, source_ref, creator_agent_id, evidence |
States what supports the record, where that support can be found, and which agent created it. evidence is a verbatim source quote. |
user_statement, session_456, research_agent, “Please keep answers concise.” |
| Time | created_at, event_at, expires_at, valid_from, valid_until, review_at, review_flagged_at |
Separates storage and event time, lifecycle expiry, explicitly stated world-validity bounds, and scheduled temporal review. Validity bounds do not assert that a planned event occurred. | Created 4 September; planned event in July; review after July |
| Trust | confidence, status |
States the stored lifecycle confidence and whether the record is active, provisional, superseded, expired, or deleted. Current retrieval does not rank on confidence. | Confidence 0.95; status confirmed |
| Lineage | supersedes_id, conflicts_with |
Connects a changed fact to the record it replaces and identifies records that disagree. | Supersedes mem_0091, which said the user preferred detailed answers |
| Links | entity links, tags | Supplies explicit handles for exact identity matching and useful filtering. | Linked to the person:user_123 entity; tag communication-preference |
Source kinds are ranked. A record can only supersede a record of equal or lower source rank.
| Rank | source_kind |
Meaning | Initial status |
|---|---|---|---|
| 4 | user_statement |
The user said it explicitly. | confirmed |
| 3 | system |
Injected by the host application from an authoritative store. | confirmed |
| 2 | tool_result |
Observed from a tool output. | confirmed |
| 2 | session_summary |
The end-of-session summary of a completed transcript. | confirmed |
| 1 | agent_inference |
The agent or extractor concluded it. | provisional |
Lifecycle states: provisional, confirmed, superseded, expired, deleted. Only provisional and confirmed are retrievable by default. Superseded and expired records stay in the database and indexes for audit and historical search. Deleted records become tombstones: the service removes their FTS entries, marks their in-memory vectors dead, and sends physical content and embedding erasure through the controlled deletion path.
5. Scope and access¶
Scope answers "whose memory is this". Access answers "which agent may see it". They are separate.
Agents are principals that act for users. A record has one ownership scope: agent, user, project, or org.
Access is a grant table: which agent ID may read or write which scope.
The only implicit scope is agent:<agent_id>/<user_id>, which is private to one agent-user pair.
Agent and user IDs cannot contain /, so no two principal pairs share that encoded scope.
A plain agent:<agent_id> scope and all user, project, and organization scopes require a host-provisioned grant.
The host refuses grants on private agent scopes, and policy ignores any direct-store private-scope grant whose user suffix differs from the current principal.
Model-facing write inputs never receive these identifiers.
Instead, the host supplies session-specific symbolic write targets, such as personal and, where applicable, current_project; the handler resolves those labels to writable scopes.
The default personal target resolves to user:<user_id>, so the host must grant each permitted agent access before it starts a session.
The private scope is not offered by default.
Every memory_search request carries the requesting agent ID and principal user ID; the pipeline computes readable scopes before candidate generation.
Scope filtering is a hard SQL predicate, never a ranking signal.
Decision: scopes do not inherit. A user grant does not imply a project grant. This requires an explicit grant for each shared scope and makes leakage tests direct to reason about.
6. Ingestion¶
Two write paths, and only two.
Path A: explicit agent write¶
The agent calls memory_write with a type, content, attribute, optional symbolic write target, source kind, and entity mentions.
The handler resolves that target to a trusted scope after checking the current principal's permissions.
In the principal's user scope, it resolves or creates the principal's person entity when a semantic or procedural write lacks an about mention.
Other scopes require an about mention.
The tool runs dedup and contradiction checks against the store, sets the initial status from the source kind, embeds the content, and returns the record ID.
The agent cannot claim user_statement without an evidence quote that the tool can locate in the current session transcript. For direct user and tool claims, the service also checks that the quote entails the stored content. A missing, unsupported, or non-entailing quote downgrades the source kind to agent_inference.
Path B: session extraction¶
When the host framework signals session end, the extractor reads the full transcript and proposes candidate records. Each candidate must include a verbatim evidence span. Before any candidate reaches the store, a separate reviewer sees the candidate, its evidence, surrounding turns, and relevant live records and returns accept, reject, or revise. A revision may narrow content or temporal metadata but may not strengthen the source kind, invent evidence, change scope, or change the primary entity. The validator then:
- Rejects any candidate whose evidence span is not found in the transcript.
- Rejects candidates that are near-duplicates of existing records (reinforces the existing record instead).
- Detects contradictions with existing records on the same subject and applies the supersession rule.
- Assigns status from source kind rather than from reviewer confidence.
- Validates temporal metadata against expressions in the evidence, resolving relative dates against the turn timestamp.
- Writes an episodic session summary record regardless of whether any facts were extracted.
Extraction is asynchronous and has no latency budget. It uses a separate, cheap structured-output model, not the serving model.
The reviewer fails closed. If it times out or returns invalid structured output, the extraction run writes neither candidates nor summary and remains retryable through the extraction claim timeout.
Decision: extraction runs after a session ends, rather than after each message. Per-message extraction multiplies cost and increases the chance that a half-formed inference becomes durable. Session-end extraction plus explicit agent writes covers the intended cases, and the evaluation will show what it misses.
Path C: time-driven review¶
The extractor may attach valid_from, valid_until, and review_at only when the supporting turn contains the corresponding temporal expression, interpreted relative to the session timestamp when necessary. A scheduled worker claims records whose review_at is due and review_flagged_at is empty, sets review_flagged_at, and appends a record.review_due event. The event flags the dated claim for reconsideration; it does not rewrite content, promote an inference, or claim that a plan happened. Repeated workers are harmless because the claim is atomic.
Decision: the first version observes time-driven staleness without automatically changing semantic truth. Automatic temporal rewriting requires evidence that due-review flags are useful and a policy for cancellations, delayed plans, and historical queries.
Reinforcement and expiry¶
A provisional record expires 30 days after creation unless reinforced. Reinforcement happens when a later extraction produces the same fact again, when the agent revises it, or when a user confirms it. Reinforcement raises confidence and extends expiry. Two independent observations promote a provisional record to confirmed.
7. Retrieval¶
Section 2 gives the plain-language overview. This section records the fixed pipeline and the decisions that make it safe and inspectable.
flowchart TD
start["memory_search: raw query"] --> rewrite{"Query rewriting enabled?"}
rewrite -- "yes" --> rewritten["Rewrite with current-turn context"]
rewrite -- "no" --> scopes["Resolve readable scopes"]
rewritten --> scopes
scopes --> filter["Hard filter: scope, lifecycle, type, and time"]
filter --> dense["Dense candidates: bge-m3 cosine"]
filter --> lexical["Lexical candidates: SQLite FTS5 BM25"]
filter --> entity["Entity candidates: exact aliases"]
dense --> fusion["Reciprocal-rank fusion"]
lexical --> fusion
entity --> fusion
fusion --> freshness["Episodic freshness adjustment"]
freshness --> gate{"Any candidate passes the gate?"}
gate -- "no" --> empty["Empty result with explanation"]
gate -- "yes" --> dedup["Collapse near-duplicates"]
dedup --> rerank{"Reranker enabled?"}
rerank -- "yes" --> reranked["Rerank with bge-reranker-v2-m3"]
rerank -- "no" --> budget["Fit the token budget"]
reranked --> budget
budget --> explain["Build result explanations"]
empty --> log["Log query, decisions, and timings"]
explain --> log
log --> response["Return results or an empty response"]
The scope filter runs before any candidate generator. Dense, lexical, and entity search run sequentially inside the same synchronous tool call, with up to 30 candidates each. The gate can return nothing; the reranker is optional and disabled by default; the final context budget is 1,500 tokens. Generator concurrency remains a benchmark-gated decision.
Decisions that matter¶
The agent decides to search; retrieval owns optional query rewriting. The agent sends a raw retrieval request and may provide entity hints. A query-rewrite stage belongs inside the retrieval pipeline because it is a retrieval concern, not a burden on the serving agent. It is specified behind a feature flag and disabled by default. When enabled, the retrieval service gives the rewriter the raw request and host-supplied current-turn context, then logs both forms of the query. The evaluation compares raw and rewritten queries on follow-up-question cases before the team makes rewriting a default.
Fusion by rank, not score. Cosine similarity, BM25, and recency have incomparable scales. Reciprocal rank fusion avoids calibrating them against each other, and it degrades gracefully when one generator returns nothing.
Empty is a first-class answer. The gate returns no results when the best candidate is weak on every signal: below the cosine floor on dense, below the BM25 floor on lexical, and not an entity match. The floors are configuration values, calibrated on the evaluation set and re-calibrated whenever the embedding model changes.
A reranker is built, measured, and disabled by default. bge-reranker-v2-m3 sits behind reranker.enabled, reranks the surviving candidates after duplicate collapse rather than the whole store, and can be placed after the RRF relevance floors or in place of them (reranker.mode). A stage timeout falls back to the RRF order and records the outcome in the search log. Measured on the blind splits, either placement cut weak candidates by an order of magnitude and lost explicit recall below the 90 percent gate, because the judge already rejected the weak candidates and the cross-encoder cut expected records too; refer to usefulness-gate.md. It stays off until a floor calibrated on planner queries or a fine-tuned pair scorer changes that result.
Results carry explanations. Each returned record includes an explanation object containing the raw and, if enabled, rewritten query; the generators that matched it; its rank and score from each generator; fused rank; any freshness adjustment; whether reranking changed its rank; its source kind, status, dates, and entity links; and why it survived the gate, duplicate collapse, and token budget. The response-level explanation also records why a search returned nothing. The agent can reject a record on that basis, and the user can inspect it.
Prompt policy and the retrieval trigger¶
Conditional durable memory remains external to the prompt prefix. The prefix contains system instructions, tool definitions, and a short memory-use policy. The evaluation-gated ambient tier adds a bounded profile assembled once at session start; it never rewrites the prefix during a session. See utility-aware-memory-architecture.md.
Who calls memory_search is a separate decision from everything above, and it is the weakest point of a purely tool-mediated design: models search reliably when the user points at the past and unreliably when a stored preference should silently shape an answer. The design therefore treats the trigger as an adapter setting with three modes, all running the identical pipeline.
| Mode | Who searches | Status |
|---|---|---|
tool_only |
The model, through its tool. | The default. Right for task agents whose work signals when memory matters. |
auto |
The host, once per user turn (never on assistant or tool turns), appending any non-empty result as a tool-result message. The search tool is not registered. | An experimental control. |
hybrid |
Both: the host's search once per user turn for the silently relevant cases, and the model's tools for targeted follow-ups. | The production candidate for assistants. |
The relevance gate alone did not make auto and hybrid safe: Phase 9a found overlapping score distributions for ordinary and memory-needed turns. The evaluation-gated replacement generates missing-information gaps, retrieves conditional candidates against those gaps, and admits a subset only when it improves a baseline draft. Conditional results are appended rather than edited into the prefix. hybrid becomes the recommended default only after the utility-aware path meets its ordinary-injection, conditional-recall, and safety gates.
8. Entities¶
Entities are a modelling layer over records, not a fifth memory type. An entity has a kind (person, project, org, repo, product, other), a canonical name, a scope, and a set of aliases. Records link to entities through a join table.
Decision: entity resolution uses exact alias matches within scope. An extractor that mentions "Aditya" links to the person entity with that alias in a readable scope, or creates a new provisional entity in the writer's scope if none exists. There is no automatic merge of two entities. Merges are an explicit memory_revise operation with an audit event. A merge repoints record links and subject_entity_id, then rewrites the derived subject and FTS subject text in the same transaction. A false merge can leak data across users, so the system does not automate it.
9. Components¶
| Component | Responsibility | Talks to |
|---|---|---|
| Store | The SQLite source of truth: schema, migrations, durable records, embedding blobs, FTS5, entity links, grants, and logs. | Everything. |
| Vector index | A rebuildable in-memory projection of active SQLite embedding blobs. It maps record ids to vectors and performs exact cosine search with id filtering. | Store, Retriever, Ingestor. |
| Embedder | Turns text into vectors. Versioned. One model per index. | Vector index. |
| Ingestor | Validates and writes records. Dedup, contradiction, supersession, entity linking. | Store, Vector index, Embedder, Extractor. |
| Extractor | Reads a transcript and proposes candidates with evidence. A structured-output model behind an interface. | Ingestor. |
| Retriever | Runs the search pipeline. | Store, Vector index, Embedder, optional Reranker. |
| Policy | Resolves grants, source ranks, expiry rules, thresholds. Pure functions over config. | Ingestor, Retriever. |
| Tool surface | The five tools as plain JSON-schema definitions plus handlers. | Retriever, Ingestor. |
| Adapters | Translate a framework's tool and session model to the tool surface and ingestion hooks. One per framework. | Tool surface, host framework. |
| Log | Records every write, search, and lifecycle change. The evaluation harness reads this. | Store. |
10. Model choices¶
| Role | Default | Why | Alternative |
|---|---|---|---|
| Embedding | bge-m3, dense head only, 1024 dims, run locally |
Open, multilingual, strong on short declarative text, no network call in the search path. | A Nomic Embed Text model for a smaller footprint. Any hosted embedding through the same interface. |
| Extraction | A small hosted model with reliable structured output (Claude Haiku 4.5 or equivalent) | Extraction runs off the hot path; quality of evidence-grounded output matters more than speed. | A local instruction-tuned model. |
| Reranker | bge-reranker-v2-m3, built behind a disabled feature flag |
Same family as the embedder; small enough to run locally. Measured at about 165 ms p50 per search; disabled because it cost recall on the blind splits, as recorded in usefulness-gate.md. | None. |
| Serving | Whatever the host framework uses | The memory layer never calls the serving model. | n/a |
Every embedding row stores the model name and version. Changing the embedder is a migration that re-embeds the whole store and re-calibrates the gate floors. It is never silent.
11. Latency budget¶
Targets on a laptop, single process, store of up to 50K records.
| Operation | Target p50 | Target p95 | Notes |
|---|---|---|---|
memory_search, no reranker |
40 ms | 120 ms | Embedding the query dominates. Keep the embedder warm. |
memory_search, with reranker |
150 ms | 400 ms | Reranker on 30 candidates. |
memory_get |
2 ms | 10 ms | Primary key lookup. |
memory_write |
60 ms | 150 ms | Embed plus dedup search plus insert. |
| Session extraction | none | none | Asynchronous. |
The in-memory vector matrix and same-process SQLite FTS5 queries are what make these numbers possible. A network hop to a separate vector database would consume much of the budget on its own.
These were design targets. Phase 15 measured them on a laptop with the real embedder and judge: warm memory_search p50 23 ms and p95 28 ms on a 1K-record fixture, and p50 74 ms and p95 78 ms on a 50K-record store with a fixed 25 ms embedding cost; memory_write p50 26 ms, p95 471 ms when the write reaches the NLI judge; the first search after opening a store 2.6 s, which is the model load. The final measurements are in acceptance-report.md, and the repository's benchmarks/README.md explains how to reproduce them.
Per-stage benchmark instrumentation¶
Every benchmark run records total latency, p50, p95, mean, corpus size, candidate count, model version, feature-flag configuration, and whether the process was cold or warm. It must also break the operations down into the stages below.
| Operation | Timed stages |
|---|---|
memory_search |
Optional query rewrite; readable-scope resolution; hard filter construction; query embedding; dense candidate generation; FTS5 BM25 candidate generation; entity lookup; reciprocal-rank fusion; episodic freshness adjustment; empty-result gate; duplicate collapse; optional reranking; token-budget assembly; response explanation assembly; log write. |
memory_write |
Permission check; evidence validation; duplicate search; contradiction and supersession check; entity link resolution; content embedding; vector-index update; SQLite transaction; event-log write. |
| Session extraction | Transcript preparation; extractor-model latency; candidate validation; duplicate and contradiction checks; each accepted write; session-summary write. |
The benchmark report must show dense, lexical, entity, and reranker timing independently, not only end-to-end search time. It must also measure how their latency changes with store size and how reranker latency changes with its candidate count.
12. Framework neutrality¶
The contract is the record envelope, the scope and grant model, the tool schemas, and the ingestion events (session_started, turn_completed, session_ended). Adapters own everything framework-specific: how tools are registered, how agent and user ids are recovered from the run context, and how tool results are formatted for the model.
Decision: the first two adapters are Deep Agents and CrewAI, as the research notes specify. They differ enough in session and identity handling that a contract surviving both is evidence of neutrality. Both are built; both pass one shared contract suite, and the equivalence test shows they leave the same semantic records from the same conversation without any change to the core contracts.
13. Observability¶
Every search writes one log row: raw request; rewritten query and rewrite status when applicable; resolved scopes; per-generator candidate ids, ranks, and scores; fused ranking; freshness adjustment; gate decision; dropped duplicates; reranker changes; final ids; result explanations; and per-stage latency. An empty result records why no candidate passed the gate. Every write logs the candidate, validation outcome, status, any supersession, and per-stage latency. The evaluation harness is a reader of this log. If a behaviour cannot be reconstructed from the log, the log is incomplete and that is a bug.
14. Failure modes and their defences¶
| Failure | Defence |
|---|---|
| Agent guess becomes a durable fact. | Source rank. Inferences start provisional and expire unless reinforced. |
| Record leaks across users or agents. | Scope is a hard filter computed from the grant table before any retrieval. Tested with adversarial cases. |
| Stale fact returned after the user changed their mind. | Supersession on subject. Superseded records excluded from default retrieval. |
| Irrelevant memory distracts the model. | Gate with calibrated floors. Token budget. Explanations let the agent reject results. |
| Two people merged into one entity. | No automatic merge. Exact alias resolution only. |
| Prompt cache broken by memory. | Nothing from the store enters the prefix. |
| Agent never searches. | Measured directly by the evaluation. The memory-use policy in the prefix is the lever; it is a prompt, not a system change. |
| Embedding model swap silently changes behaviour. | Model version on every embedding row. Swap is a migration with re-calibration. |
15. Decisions deferred to experiments¶
| Question | Current default | Experiment that settles it |
|---|---|---|
| Does query rewriting help? | Built, measured, off. | Settled for now: the planner's retrieval queries are already standalone, so rewriting changed 2 to 5 of about 39 searches per split and cost 1.7 to 1.9 s each, as recorded in usefulness-gate.md. Revisit if model-written queries in tool_only show follow-up misses. |
| Does the reranker earn its latency? | Built, measured in both placements, off. | Settled for now: it cost explicit recall on both blind splits (8n, 8p). Revisit with a floor calibrated on gap queries or a fine-tuned pair scorer. |
| Are the gate floors right? | Swept on a labelled 1K fixture in Phase 15. | The configured semantic floor of 0.45 sits inside the band whose F1 is within 90 percent of the best (0.44 to 0.66); resweep after any embedder change. |
| Is per-message extraction worth it? | Session-end only. | Compare recall of mid-session facts against pollution rate. |
| Should anything be ambient? | Ambient activation is built, audited, and validated on the promotion split; the profile is off unless a host enables it. | Production evidence from a consuming host. |
| Who triggers retrieval? | tool_only. |
Production metrics from the utility-aware path in a consuming host decide whether hybrid becomes recommended; the offline gates are met. |
| Is the gate strong enough for host-issued searches? | No. The relevance gate is a candidate filter; the utility-aware path makes the decision. | Passed offline: the supported bundle meets the injection, recall, and safety gates on scripted blind splits and the shadow harness. Real-traffic validation is pending. |
| Can a similarity floor answer "does this turn need memory"? | No. Measured in Phase 9a: the score distributions for ordinary and memory-applicable turns overlap almost completely. | Settled. The replacement is specified in utility-aware-memory-architecture.md. |
| Exact vs approximate vector search? | Exact. | Only revisit above 200K records. |
16. What the evaluation will need from this design¶
The benchmark work comes later, but the design is shaped so it can be built. The log gives the harness every intermediate decision. The store can be snapshotted and restored, so scenarios start from a known state. The tool surface can be driven directly without an agent for retrieval-only tests, and through an adapter for end-to-end tests. Scenario categories the design anticipates: desired write, prohibited write, desired retrieval, prohibited retrieval, stale record, conflicting records, entity ambiguity, cross-scope boundary, search versus no-search, and downstream task effect.