Docs / Guides
contract read · 20-read.md · 21-memory.md · guide · card
Reading and memory
The problem the read face solves
A caller, such as an agent holding a capability token, asks questions of stored tables.
The caller sees only rows its grant admits, and no read writes a row. SQL queries,
ranked retrieval and memory recall all pass through the same registered relations and
return the same response shape.
The example agent wants the current status of one resource.
Naming what a caller reads
Each session connection registers one view per manifest table
(connection-viewsread.register.connection-views Each session connection creates or replaces one view per manifest table, scanning explicit-file-list. Statements reuse these views under session-pool. No view directory exists on disk. ). A bare table name in the caller’s SQL resolves
to that caller’s restricted view (bare-nameread.register.bare-name In caller statements, templates, ranking queries and file previews, a bare table name resolves to the caller's registered relation. That relation carries the caller's restriction. ). The face exposes a closed
tool set, so a caller reaches nothing outside it (tool-setread.register.tool-set The face exposes context.describe, context.query, context.reference, context.lineage, context.execute_query for templates, context.files, context.file, context.subscribe, corpus.retrieve, memory.recall, memory.traverse and memory.explain as its closed tool set. ).
The agent first calls context.describe and learns row counts, declared indexes and
the published row ceiling (describe-payloadread.register.describe-payload context.describe returns row count, size-estimate, schema fingerprint, description, column-hints, declared indexes, partition scheme, limits.max_rows, zone label, lexicon and example queries. ).
Which statements run
The guard reads the SQL engine’s own parse tree, so no keyword list decides admission
(engine-own-parseread.guard.engine-own-parse The guard checks the syntax tree serialized by the SQL engine. The tree determines whether the statement is read-only; no keyword blocklist determines admission. ). The text has to parse to exactly one read-only
SELECT (single-read-only-statementread.guard.single-read-only-statement Admitted text parses to exactly one read-only SELECT. Attach, copy, install, load, pragma, set, any schema change, any data modification and a piggybacked second statement raise StatementNotReadOnly. ).
The agent joins documents to a read_parquet call, and the guard refuses the table
function (table-functionread.guard.table-function A table function anywhere in the tree, and a schema-qualified reach into a system catalog, raise TableFunctionRefused. ). It retries with a join to a table it holds
no grant on; the error echoes only the requested name (unregistered-relationread.guard.unregistered-relation The guard rejects an unregistered base relation under ungranted-table. The error echoes the requested name and no relation belonging to another caller. ).
Operator statements run without that guard, and no network interface exposes them
(statement-provenanceread.guard.statement-provenance Local operator statements, template bodies and engine-composed reads execute without caller-SQL validation. The guard validates capability-token callers' SQL. No network interface exposes raw operator execution. ). The operator runs a raw statement with contextful query (operator-verbread.query.operator-verb contextful query --json [--project <name>] [--declaration <path>] [--limit <n>] [--incognito] <sql> runs one raw operator statement under statement-provenance. The command prints one-projection as one JSON line on standard output. ).
Sequence diagram
Sequence diagram. 5 participants, 9 messages.
Participants: agent (A), tool server (T), statement guard (G), caller views (V), store (S).
1. agent → tool server: context.query with a token
2. tool server → caller views: register one view per table
3. tool server → statement guard: parse and walk the statement
alt write, table function or unregistered name:
4. statement guard --> agent: error naming only the request
else one read-only SELECT:
5. statement guard → caller views: run over restricted relations
6. caller views → store: scan committed parts
7. store --> caller views: rows
8. caller views --> tool server: admitted rows and cells
9. tool server --> agent: one envelope, exact truncated flag
read
alt write, table function or unregistered name one read-only SELECT
agent agent
tool server tool server
statement guard statement guard
caller views caller views
store store
context.query with a token
register one view per table
parse and walk the statement
error naming only the request
run over restricted relations
scan committed parts
rows
admitted rows and cells
one envelope, exact truncated flag
The response
Every transport serializes one projection: columns, rows, truncated and optional
blocks (one-projectionread.respond.one-projection CLI JSON output, HTTP and the tool protocol serialize the same response: columns, rows, truncated, and optional restriction, resolved-build, provenance, retrieval and time-bound blocks. ). The serializer sets truncated from an over-fetched
probe row, not from comparing counts (truncation-is-exactread.respond.truncation-is-exact For a row ceiling, the serializer sets truncated exactly when the extra probe row exists. Comparing returned counts with requested limits does not determine truncation. ). Zero rows is a
success, not an error (zero-rows-is-successread.respond.zero-rows-is-success Fewer rows than the requested limit, zero included, is a success. ).
Ranked retrieval
The agent switches to corpus.retrieve with the query “resource status” and a filter on
resource_id (ranked-callread.retrieve.ranked-call corpus.retrieve({prefix, tables?, query, query_embedding?, filter?, kinds?, limit?, since?, min_score?, as_of?, valid_as_of?}) returns ranked rows across item and artifact genres under prefix and tables, each with a snippet and provenance, composing the query surface and memory recall, storing nothing. ). A table lacking resource_id
contributes no rows instead of running unfiltered (unsatisfiable-arm-dropsread.retrieve.unsatisfiable-arm-drops In filter, a scalar tests column equality; a value list tests membership. Rows match every condition. A table lacking a named column contributes no rows, including unfiltered rows. ).
Ranking fuses vector cosine with lexical BM25 scores (fusionread.rank.fusion Fusion computes w_vec · clamp(cosine, 0, 1) + w_lex · rrf(bm25). Weights are 60 percent and 40 percent. rrf maps BM25 rank r to 61 / (60 + r). Absent documents score zero per leg; identifiers break ties. ). A
question’s timeframe orders rows but drops none (question-window-is-a-tierread.rank.question-window-is-a-tier A question's timeframe projects a per-row in-window flag against the resolved publication column, and the flag leads the ordering. An out-of-window row sorts down and stays. ).
Each stored vector sits beside the name of the model that produced it
(model-identifierread.embed.model-identifier Every stored vector uses a binary-and-vector column beside embedding_model, which names the producing model. Rows alone determine index identity and provenance. ), so the rows themselves record which model embedded them.
To report that no document mentions a status change, the agent counts rows. A low top
score proves no absence (coverage-is-a-countread.respond.coverage-is-a-count A claim of missing corpus coverage requires a table count after the full-scan fallback, without recency truncation. The highest ranked score supplies no coverage conclusion. ).
Sequence diagram
Sequence diagram. 4 participants, 8 messages.
Participants: agent (A), retrieval (R), table arm (W), vector sidecar (X).
1. agent → retrieval: corpus.retrieve with query and filter
2. retrieval → retrieval: split query into content tokens
loop each registered table:
3. retrieval → table arm: recency-ordered candidate window
opt sidecar under its size cap:
4. table arm → vector sidecar: nearest neighbours
5. vector sidecar --> table arm: candidates re-joined through enforcement
6. table arm --> retrieval: candidates above the relevance floor
7. retrieval → retrieval: fuse cosine and BM25, window first
8. retrieval --> agent: ranked rows with the retrieval block
read
loop each registered table
opt sidecar under its size cap
agent agent
retrieval retrieval
table arm table arm
vector sidecar vector sidecar
corpus.retrieve with query and filter
split query into content tokens
recency-ordered candidate window
nearest neighbours
candidates re-joined through enforcement
candidates above the relevance floor
fuse cosine and BM25, window first
ranked rows with the retrieval block
Reads under one key reuse a pooled session and its connections
(session-poolread.cache.session-pool Face reuses sessions and connections while the key matches: admitted authority, token id, revocation epoch, request zone, bounds, pin map and table set. Each granted table contributes schema digest, pointer, run set and ledger files. ).
Reading at an earlier time
Every read tool takes three bounds: as_of freezes the corpus, valid_as_of picks what
was true, and available_as_of sets a simulated present. An ASOF JOIN matches each
row on availability (per-row-boundread.as-of-join.per-row-bound Per equality key, ASOF JOIN selects the right row with latest declared availability at or before the driving instant named by the inequality. If given, max_age bounds that row's valid-time from. ).
A read naming a published model build resolves that build, and an unknown build fails
instead of widening to the latest state (unknown-buildread.resolve-pin.unknown-build An unknown or collected build identifier, one collected-snapshot counts included, raises PinnedBuildUnavailable, naming the oldest identifier still pinnable. A pin never widens to the latest state. ).
A credential carrying a horizon caps the simulated present. A call asking for a later
available_as_of fails, naming the argument (exceeds-horizonread.horizon.exceeds-horizon A call supplying available_as_of or since later than its credential's horizon raises ReadBoundExceedsHorizon, naming the argument; as_of, valid_as_of and observed_at pass unbounded by it. ).
Sequence diagram
Sequence diagram. 4 participants, 9 messages.
Participants: agent (A), tool server (T), as-of join (J), committed parts (S).
1. agent → tool server: context.query with three bounds
2. tool server → committed parts: scan snapshots committed by as_of
Note over tool server,committed parts: as_of freezes the corpus for the whole eval
3. tool server → committed parts: keep rows valid at valid_as_of
4. tool server → committed parts: keep rows available by available_as_of
Note over tool server,committed parts: available_as_of is the simulated present
5. tool server → as-of join: driving rows and right relation
6. as-of join → committed parts: right rows available by each driving instant
7. committed parts --> as-of join: latest available right row per key
8. as-of join --> tool server: joined rows
9. tool server --> agent: rows with the three bounds echoed
read
store
agent agent
tool server tool server
as-of join as-of join
committed parts committed parts
as_of freezes the corpus for the whole eval
available_as_of is the simulated present
context.query with three bounds
scan snapshots committed by as_of
keep rows valid at valid_as_of
keep rows available by available_as_of
driving rows and right relation
right rows available by each driving instant
latest available right row per key
joined rows
rows with the three bounds echoed
context.reference resolves one stored citation, and an erased source answers
erased, distinct from missing (erased-verdictread.reference.erased-verdict An admitted reference in the independently verified cumulative erasure index returns available: false and reason: erased. An absent reference outside that index returns reason: missing without source payload. ).
context.lineage answers which tables, models and steps feed one another
(callread.lineage.call context.lineage accepts an optional table, direction and depth, and returns nodes, edges, truncated and withheld from the latest applied graph of dataflow-edges. ). The caller’s session narrows the graph: a table it cannot
read leaves the answer and remains only as a count (withheld-countread.lineage.withheld-count A table or model the session does not register leaves the graph, one joined to a returned node counts in withheld, and a step joined to no registered relation leaves uncounted. ).
Notifications
A subscription notifies each committed run once, in commit order, with no row content
(commit-orderread.subscribe.commit-order Notifications of one table arrive in ascending commit_seq, the order commit-order makes runs readable, and each committed run notifies exactly once per subscription. ). A resume position naming no committed run fails
and names the oldest one available (position-unavailableread.subscribe.position-unavailable An after position naming no committed run of its table raises SubscriptionPositionUnavailable, naming the table's oldest resolvable position. ).
Memory
Memory is claims about entities, kept in ordinary store tables with reserved relation
types (reserved-relationsread.declare.reserved-relations Reserved relation types are supports, contradicts, supersedes, about and derived_from. Deployments add types through [relation].declared. contextful memory relations rename rewrites stored edges before removing an old name. ). A synthesis pass extracts claims from
source rows and retries an invalid response a bounded number of times
(extract-attemptsread.synthesize.extract-attempts For a schema-invalid response, the synthesis pass retries extraction with validation feedback. Each batch receives at most 3 attempts in total. ). An exhausted batch goes to a dead-letter
table, and the cursor holds (dead-letterread.synthesize.dead-letter A batch exhausting extract-attempts writes the response, template hash and drop reason to the dead-letter table, raises MemoryExtractExhausted, and leaves the cursor unadvanced. ).
Sequence diagram
Sequence diagram. 5 participants, 9 messages.
Participants: synthesis pass (P), model endpoint (M), memory tables (T), recall (R), agent (A).
1. synthesis pass → model endpoint: extract from rows since the cursor
2. model endpoint --> synthesis pass: extracted claims
alt invalid after bounded attempts:
3. synthesis pass → memory tables: dead-letter, cursor held
else valid:
4. synthesis pass → memory tables: commit resolved, consolidated facts
5. agent → recall: recall about a vendor
6. recall → memory tables: read candidate facts
7. memory tables --> recall: facts with evidence references
8. recall → recall: read evidence through the caller's session
9. recall --> agent: facts whose evidence the caller reads
run
store
read
alt invalid after bounded attempts valid
synthesis pass synthesis pass
model endpoint model endpoint
memory tables memory tables
recall recall
agent agent
extract from rows since the cursor
extracted claims
dead-letter, cursor held
commit resolved, consolidated facts
recall about a vendor
read candidate facts
facts with evidence references
read evidence through the caller's session
facts whose evidence the caller reads
A mention matching two identities dead-letters its claim instead of guessing
(ambiguous-mentionread.resolve-entity.ambiguous-mention If a mention matches two canonical identities with no deterministic distinguishing key, resolution records an ambiguous skip, raises MemoryEntityAmbiguous and dead-letters the candidate claim. ). A writable server accepts operator claims
through one direct-write route (served-writeread.revise.served-write A writable HTTP server accepts POST /memory/claims under per-request network credentials and stores declared claims through the direct-write operation. The read MCP tool set remains closed. ).
The agent asks memory what the workspace concluded about the resource. One fact cites a
row masked for this caller, so recall suppresses that fact and answers with the rest
(evidence-unresolvedread.recall.evidence-unresolved An unreadable or masked source row, an unknown table, a reference into another memory row, or malformed lineage suppresses the claim and raises MemoryEvidenceUnresolved. ).
An edge walk follows only edges the caller reads, with citations that resolve
(enforced-edgesread.traverse.enforced-edges Every edge reads through the caller's enforced session, and each edge's citations pass evidence-unresolved; an edge failing either is neither returned nor walked through. ). A prediction registers through
contextful memory predict and settles against later observations
(predict-verbread.settle.predict-verb contextful memory predict --into <table> --registration <json> validates registration. Under the admitted credential's write grant, the command stores the registration in that predictions table and prints prediction_id: <id>. ).