contract read · 20-read.md · 21-memory.md · guide · card
Synthesized memory
Implementers use this page for stored claims, entity identities, relationships and prediction outcomes.
Memory uses the same tables, commit operations and access controls as ingested data.
The memory commands and tools write, revise and retrieve these records. Subject erasure uses
the disclosure contract’s erase operation.
Memory tables and the operations that write and read their rows:
declare
Memory table schemas, required columns, trust tiers, scopes and relation types.
canonical-columntestA table naming a shape and omitting one of its canonical columns raisesMemoryShapeColumnMissingwhen the declaration loads.Why
P1undeclared-relationtestFor arel_typeoutside the allowed union, synthesis writes the candidate edge to the dead-letter table and raisesMemoryUndeclaredRelation. No row uses the unknown type.relation-traitstestA[relation].declaredentry is a bare name, or a table ofname,direction,cardinalityandwalkable. A bare name or an omitted trait readsdirected,manyand not walkable. Reserved relations carry the reserved-traits table.Why
because a relation marked walkable by default lets a dense relation fan a walk across the storereserved-relationstestReserved relation types aresupports,contradicts,supersedes,aboutandderived_from. Deployments add types through[relation].declared.contextful memory relations renamerewrites stored edges before removing an old name.settlement-shapestestThepredictionsshape carries one column per registration field,deadline_atresolved for a horizon; theoutcomesshape carries one column per observation field. Both key onprediction_id.Why
because the label join reads these columns by name, and one row per prediction keeps one label per predictionavailabilitytestA shaped memory table accepts[table.available_time]underdeclarationandcontent_hash_columnundertable-block, carrying both into every read and landing.Why
because a conclusion’s publication time differs from its economic validity and shared availability bounds exclude later conclusions from historical recall
synthesize
Claim extraction, entity resolution, consolidation, evidence, deduplication, audit records and coverage gaps.
extract-attemptstestFor a schema-invalid response, the synthesis pass retries extraction with validation feedback. Each batch receives at most 3 attempts in total.dead-lettertestA batch exhaustingextract-attemptswrites the response, template hash and drop reason to the dead-letter table, raisesMemoryExtractExhausted, and leaves the cursor unadvanced.dead-letter-evidencetestEvery dead letter a synthesis batch writes carriesevidence, the{table, run, seq}reference of each source row the batch carries.Why
because a dead letter repeats its batch’s content, and erasure finds that content through the rows it came frompass-cursortestThrough the writing credential’s session, synthesis reads committed source rows absent from its cursor, in batches underprompt-bound. The cursor records each batch after its claims commit.prompt-boundtestA batch packs marked rows up to 32 KiB; a marked row over that bound travels in a batch of its own.Why
because one oversized row neither stalls the pass nor drags its neighbours past the boundrow-captestSynthesis marks each row, capped at 16384 chars undervalue-cap. Truncated rows reach the model; their batch commits and advances the cursor. The pass report counts these rows intruncated.Why
because a silent cut hides that a conclusion rests on a partial rowattributiontestEvery landed claim carriesgrant_id, the chain-final revocation identifier of the credential that wrote it,agent, that credential’s agent member, and_authored_by, its on-behalf-of principal.Why
because a reader weighs a conclusion by which grant could have produced itclaim-tainttestA source row carries its own_taint, elseingested:third-party. Every row a batch commits lands undertaintwith the least-trusted label among the batch’s rows.Why
because a row no label vouches for is trusted least, and a claim re-synthesized from claims stays as low as its origincadencetestBy default,contextful memory synthesizestarts synthesis. A scheduled[[job]]of kindsynthesizetargeting the claims table replaces that default. Each execution advances only throughpass-cursor.confidence-calibrationtestModel-emitted confidence carriesuncalibrateduntil a shape-and-predicate isotonic map trained on settled outcomes lowers held-out Brier score. A validated map supplies the reported calibrated confidence.
unsettled: Which consolidation merges two claims of one dedup key, and what record does a coverage gap leave? owner: read-path affects: read.synthesize
revise
Claim replacement within a validity interval, confidence decay, direct writes, timestamps, expiry, retention and promotion.
tiertestThe writer assignstier, aTier, from write method and grounding, excluding payload values: synthesis usesderived, direct writes usecurated, fetched results useresearched. Mixed grounding takes the lowest tier.supersedetestAn equal-or-higher-tier claim replaces a live prior matching subject, predicate and scope when both are open-ended or share valid-from, exceptunscoped-collision. The prior ends at the successor’s start and names the successor.direct-writetestThe direct write accepts claims alone. Namingmemory_episodes,memory_entities,memory_edgesormemory_preferencesraisesMemoryDirectWriteShapeRefused; an entity row enters throughentity-upsert.observed-attestcontextful memory write --observed-at <instant>lands the claim withvalid_fromat that instant; without it,valid_fromis the write’s own instant.dedup-keytest--dedup-key <key>derivesclaim_idfrom the key beside the subject, predicate, object and scope, and a write whoseclaim_idthe table already holds lands nothing.Why
because a retried write restates one observation, while one fact observed twice under two keys keeps two validity intervalsobserved-ordertestFor matching subject, predicate and scope with a different object, a write whosevalid_fromprecedes an unsuperseded claim’svalid_fromraisesMemoryObservationOutOfOrder. The write stores nothing.Why
because retiring the later claim at the earlier instant ends it before it starts, and it then answers at no instantcitation-livetestIf a cited keyed-table row reads through the writer’s session as a replaced key version, a direct write raisesMemoryCitationNotLive. The write stores nothing.Why
because such a claim rests on a replaced version from its first read, while a citation no readable row carries lands undigested and recall withholds itunscoped-collisiontestUnscoped direct writes sharing subject, predicate and valid-from but different objects record both claim ids in the dead-letter table. Neither claim replaces the other until an explicit revision resolves the conflict.retention-defaulttestClaims retain recorded validity until explicit expiry or erasure. Withdecay_half_life, ranked scores decrease by half per half-life sincevalid_from. Score decay neither deletes claims nor changes validity.served-writetestA writable HTTP server acceptsPOST /memory/claimsunder per-request network credentials and stores declared claims through the direct-write operation. The read MCP tool set remains closed.served-scopetestA claim write raisesMemoryClaimScopeRefusedifactordiffers from admittedon_behalf_of,sessiondiffers from admittedtask, orsessioncontains a colon. Accepted claims take scope from credential members rather than payload values.browser-requesttestAnOriginheader on the served claim-write route raisesMemoryClaimBrowserRefusedbefore credential admission and commits nothing.malformed-requesttestMalformed JSON, unknown fields, a client-selected claim scope, or an empty dedup key on the served claim-write route raisesMemoryClaimMalformedand commits nothing.served-deduptestA served claim write passes itsdedup_keytodedup-key, returnslanded: falseon restatement, and returns the bound scope on both outcomes.served-faulttestA served claim write whose store effect refuses raisesMemoryClaimWriteRefusedand lands nothing.
unsettled: What binds the asserted agent member to the writer? owner: authority affects: read.revise
unsettled: What declared bound expires a researched claim, and which served write promotes a claim to curated? owner: read-path affects: read.revise
recall
Ranked and keyed claim retrieval, read timestamps, tier ordering, scope and tombstone filters, and evidence checks.
evidence-unresolvedtestAn unreadable or masked source row, an unknown table, a reference into another memory row, or malformed lineage suppresses the claim and raisesMemoryEvidenceUnresolved.evidence-referencestestFor claims citing more than 256 entries of evidence, recall returns the first 256 in cited table, run and sequence order.evidence_omittedreports the remaining count.evidence-omitted-columntestevidence_omittedis a reserved projected column ofranked-arm, null for a row outside amemory_factstable underreserved-columns-project-null.evidence-gate-boundtestRecall suppresses a claim with more than 4096 entries of evidence as unresolved and raisesMemoryEvidenceOverflow.evidence-keytestClaim writes stamp each citation to a keyed table with a pepper-keyed digest of the cited row’s key. If that row is unreadable, recall resolves the digest’s live version.evidence-key-maskedtestIf the writer’s session masks or zone-nulls a cited key column, the citation carries no digest. Masked or nulled non-key columns leave the digest intact.Why
because a masked key cell digests a value no other session reads, while a cell outside the key plays no part in matching itevidence-no-valuetestA citation stored in a memory row carries the cited table, run, sequence and key digest, and no cell value of the cited row.evidence-staletestRecall counts a served claim understaleincontextful.recallif the key digest resolves while the cited run and sequence are unreadable. The block names no claim.ranked-armtestFormemory_facts,corpus.retrievereturns only claims withoutsuperseded_byand withvalid_tonull or after the read’s anchor. Each claim’s evidence passes the evidence check.recall-block-presencetestAcorpus.retrieveresponse carries thecontextful.recallblock exactly when one of its arms reads amemory_factstable, whichever ofprefixandtableschose the arms.Why
because the block counts what claim arms withheld, and a read with no claim arm withheld no claimsuppression-counttestA suppressed claim is absent from the rows; thecontextful.recallblock counts suppressions per error identifier and names no claim.Why
because a count discloses that a conclusion was withheld, never what it concludedkeyedtestmemory.recallacceptstable,subject,observed_at,as_of_ingestandlimit. Returned claims matchsubjectexactly, withvalid_fromandvalid_tocoveringobserved_atundervalid-as-of.cli-verbtestcontextful memory recallforwards arguments tomemory.recalland prints the tool’s JSON response. The command appends the call’s read entry underread-entry.keyed-clockstestas_of_ingestbounds reads underas-of; without that argument, reads use the latest committed state. Missingobserved_atdefaults to the call’s instant.contextful.boundsechoes supplied bounds by argument name with a per-nameinclusivemap.keyed-historytestKeyed recall returns a replaced claim forobserved_atwithin that claim’s validity interval. The query filters by validity alone, excludingsuperseded_byfrom filtering.Why
because the question is what held at that instant, and the retired claim is the answer its interval recordskeyed-gatetestEvery keyed claim passesevidence-gate-bound, and every evidence entry, served or omitted, passesevidence-unresolved; the response carriessuppression-count.keyed-windowtestKeyed recall checks claims in order and stops after retaining one claim beyond the row ceiling. The suppression count covers claims checked before that stop.Why
because a one-row recall over a subject with thousands of claims otherwise resolves every claim’s evidencekeyed-evidence-batchtestThe keyed read resolves a claim’s citations into one table and run with one statement, listed among the executed SQL underinternals: true.Why
because one statement per citation costs a claim at the gate bound one engine round trip per entrykeyed-ordertestKeyed claims order by tier,curatedfirst, thenvalid_from, newest first, thenclaim_id;limitand the table’s ceilings bound them underrow-ceiling.keyed-not-claimstestA grantedtabledeclaring a shape other thanmemory_facts, or no memory shape, raisesMemoryRecallNotClaims.Why
because a keyed read answers with a claim’s subject, validity and evidence, which no other table carriesconfidence-labeltestEach keyed claim’sconfidence_labeliscalibratedoruncalibratedunderconfidence-calibration. Fitting uses the declaredlabelstable, joiningprediction_idtoclaim_idthrough the caller’s session.stale-revisiontestA stale citation remains subject tokeyed-gate; only a newly committed source row advancingpass-cursorstarts synthesis against its live key version.usage-ledgertestmemory.recall,memory.explainresponses carrying claims, andcorpus.retrieveresponses containingmemory_factsrecord returned claim ids ascontextful.memory.claims. The read entry includes caller credential, subject and read instant.
Scenarios
read.recall.evidence-references: WHEN a claim cites 300 readable rows, THEN recall serves 256 entries withevidence_omitted = 44.read.recall.evidence-unresolved: WHEN entry 290 of 300 reads masked, THEN the claim is suppressed withMemoryEvidenceUnresolved.
resolve-entity
Entity matching, aliases, identity cards, location relationships, edges and ownership queries.
ambiguous-mentiontestIf a mention matches two canonical identities with no deterministic distinguishing key, resolution records an ambiguous skip, raisesMemoryEntityAmbiguousand dead-letters the candidate claim.edge-endpointtestIf a candidate edge’s source or target resolves to no identity, resolution dead-letters the edge and raisesMemoryEdgeEndpointUnresolved.graph-indextestExternal graph engines index admitted memory edges as derived data. Entity and ownership answers resolve through access-controlled store rows at the read frontier.ownership-answertestOwnership queries return targets of every caller-visibleowned_byedge from the artifact. Results sort by newest_ingested_at, then principal id for equal timestamps.knowledge-cardtestmemory.explainacceptstable,referenceand an optionalclaimstable. The tool resolvesreferenceagainsttable’s names and aliases and returns the identity’sentity_id,kind,nameandaliases.Why
because a caller holding a name or an alias in any script needs the canonical record without knowing its opaque idcard-claimstestA card namingclaimscarries the claimskeyedreturns at the call instant, underkeyed-gate, whosesubjectequals the identity’sentity_idor its canonicalname.Why
because synthesis keys a resolved claim by canonical name andclaim_idhashes the subject, so re-keying stored claims by id re-identifies every onecard-candidatestestIfreferencematches multiple identities without an exact canonical-name match distinguishing one, the card returns candidateentity_ids and no identity. With no matches, the card is empty.Why
because naming one of several matches guesses on the caller’s behalf, and an absent identity is an answer, not a faultcard-authorizationtestCards readtableandclaimsthrough the caller’s session. Without a registeredclaimsrelation, cards return only identity. Cards without claims omit the claims table’s name from the response and read entry.Why
because a grant on identities is narrower than a grant on what the workspace concludes about themcard-cli-verbtestcontextful memory explainforwards its arguments, including--internals, tomemory.explain. The command prints the tool’s JSON response and appends the same read entry.Why
because a read outside the audit chain is a read no owner can account forcard-not-entitiestestA grantedtabledeclaring a shape other thanmemory_entities, or no memory shape, raisesMemoryExplainNotEntities.Why
because only amemory_entitiestable carries the identity, kind and aliases a card returnsentity-upserttestcontextful memory entity upsert --table <entities> --id <id> --kind <kind> --name <name> [--alias <alias>]...writes one row to the first declaredmemory_entitiestable. Synthesis resolves against that table; upsert requires its read and write grants.Why
because the claim write refuses entity rows, and an operator still names the identities mentions resolve toupsert-fieldstestBefore comparison or storage, upsert trims id, kind, name and each alias. An empty trimmed value raisesMemoryEntityFieldEmptyand writes nothing.Why
because a blank identity or alias matches no mention, and surrounding whitespace lands a second spelling of a held nameupsert-mergetestFor an existingentity_id, upsert replaceskindandname, retaining stored aliases first. Supplied aliases follow, excluding those matching a stored alias after case-folding.Why
because an alias an earlier upsert recorded keeps naming the entity for mentions already written with italias-conflicttestAn upsert whose name or alias matches, case-folded, the name or an alias of another entity the session reads raisesMemoryEntityAliasConflict, and nothing lands.Why
because a mention two identities share resolves to neither: synthesis skips it underambiguous-mentionand a card lists candidates undercard-candidatesupsert-whole-tabletestAn upsert whose session read of the entities table returns truncated raisesMemoryEntityTableTruncated, and nothing lands.Why
because a merge over a cut read drops the held row’s aliases, and a conflict check over it misses the entity that carries the mentionupsert-serialtestUpsert holds the table’s upsert lock during reading, conflict checking and storage. The new timestamp follows the stored row’s_ingested_at. Concurrent upserts on one table execute serially.Why
because two upserts merging one snapshot lose an alias, and two checking one snapshot both claim one mentionupsert-availabilitytestAn upsert into an entities table declaringavailable_timeraisesMemoryEntityAvailabilityDeclared, and nothing lands.Why
because an operator’s identity carries no source availability instant, and a vintage table keeps the prior row beside the one an upsert replaces
unsettled: Which edges carry aliases and nearness’s parent closure? owner: read-path affects: read.resolve-entity
Scenarios
read.resolve-entity.knowledge-card: WHENreferenceis an alias written in another script, THENmemory.explainreturns the one identity carrying it.read.resolve-entity.card-candidates: WHENreferenceis an alias two identities share, THEN the card lists bothentity_ids and no identity.read.resolve-entity.card-authorization: WHEN the caller holds a read grant ontableand none onclaims, THEN the card carries the identity and no claims.
settle
Prediction registration, observations, required verdict citations and outcome label views.
registrationtestA registration requires one form (relative horizon, absolute deadline, open watch) and one source (metric,adjudicator,manual). Onlymetricrequires and permits a comparator. Invalid registrations raiseOutcomeRegistrationInvalid.source-mismatchtestAn observation raisesOutcomeSourceMismatchif itsmetricseries differs from registration, or its verdict’s resolution source or registered series is missing or differs. The error names each source by wire name.settling-citationtestAnadjudicatorormanualverdict without anhttporhttpssettling citation raisesOutcomeCitationMissing. The label view includes the citation.grace-windowtestThe label join keeps an observation from the prediction instant through the deadline plus an inclusive grace of 86400 s.calibration-reporttestCalibration reports group settled predictions by shape and predicate. Each group reports sample count, Brier score, expected calibration error over 10 equal-width bins, andconfidence-calibration‘s held-out score over every fifth prediction.statement-boundtestA registration’s optionalstatementholds the falsifiable claim and participates inregistration-conflict. Text exceeding 4096 B raisesOutcomeStatementTooLong.Why
because a registration reads on its own only with its claim text, and an unbounded text field lets one row outweigh the tablemetric-referencetestOptionalmetricuses<table>:<series_id>with non-empty parts and names the series without a store lookup. Malformed references or references under a source other thanmetricraiseOutcomeMetricReferenceInvalid.Why
because a registration precedes its series’ first landing, so a store lookup refuses every prediction made before its data existsdeadline-datetestA bareYYYY-MM-DDindeadline_atresolves underinstant-comparisonto the next UTC midnight. The entire named day precedes the deadline.Why
because one date spelling then means one instant on every clock the engine comparespredict-verbtestcontextful memory predict --into <table> --registration <json>validatesregistration. Under the admitted credential’s write grant, the command stores the registration in thatpredictionstable and printsprediction_id: <id>.Why
because a registration nothing lands leaves its outcome no prediction to settle againstobserve-verbtestcontextful memory observe --into <table> --observation <json>reads the stored prediction registration through the writer’s session. After checkingsource-mismatchandsettling-citation, the command stores the observation in thatoutcomestable and prints its label row.Why
because a verdict is checked against the source registered before it, never one the observation assertsverb-attributiontestBoth verbs admit a capability token ascontextful memory writedoes, and each landed row carries_authored_byfrom its subject underinjected.Why
because a settled outcome is weighed by who registered and who observed itprediction-unregisteredtestAn observation naming aprediction_idno row of the declaredpredictionstable carries in the writer’s session raisesOutcomePredictionUnregistered, and nothing lands.Why
because a verdict with no registration has no source to check it againstrestated-landingtestA registration or observation identical to the stored row of itsprediction_idlands nothing, and the verb succeeds with its usual output.Why
because a retried verb restates one registration, and landing it again commits a run that records nothing newregistration-conflicttestA registration whoseprediction_ida stored registration with any other field already holds raisesOutcomeRegistrationConflict, and nothing lands.Why
because rewriting a registration after the fact changes the deadline or source a verdict is scored againstoutcome-settledtestIf an observation differs from a stored verdict-bearing outcome for itsprediction_id, the write raisesOutcomeAlreadySettled. Observations replace stored outcomes without verdicts.Why
because a verdict rewritten after scoring silently changes the calibration it fedlabel-jointestThe label view of Shapes runs over a declaredpredictionsandoutcomestable pair as read through the reader’s session.Why
because the view’s grace window and self-rated filter then hold for every reader, not each reader’s own joincalibration-verbtestcontextful memory calibration --table <claims>joins verdict-bearing labels to claims byprediction_id = claim_idthrough the caller’s session. The command printscalibration-reportwith joined-prediction count, using JSON under--json.Why
because a prediction’s shape, predicate and emitted confidence are those of the claim it scores
traverse
Graph traversal from a seed entity, bounded by time, hop count and readable edges in the caller’s session.
calltestmemory.traverseacceptstable,seed,rel_types,hops,as_of_ingest,observed_atandavailable_as_of. Under one frozen anchor, the tool returns reached edges whosevalid_fromandvalid_tocoverobserved_at, usingone-projection.clockstestMissingas_of_ingestuses the latest committed state; missingobserved_atuses the call’s instant; missinghopsdefaults to 1.contextful.boundsechoes each supplied bound underkeyed-clocks.cli-verbtestThecontextful memory traverseverb passes its arguments throughcalland prints the same JSON response.enforced-edgestestEvery edge reads through the caller’s enforced session, and each edge’s citations passevidence-unresolved; an edge failing either is neither returned nor walked through.Why
because walking through a hidden edge reveals the node beyond itenginetestAn engine-backed walk answers throughgraph-index.directiontestA walk follows adirectedrelation fromsource_idtotarget_idand anundirectedrelation both ways. An absentrel_typeswalks every walkable relation.Why
because an undirected relation holds from both of its endsnot-edgestestAtabledeclaring nomemory_edgesshape raisesMemoryTraverseNotEdges.Why
because a walk over another shape has no edge columns to readopen-validitytestAn edge table declaring novalid_fromorvalid_tocolumn holds each edge at everyobserved_atand orders a node’s edges by edge id.Why
because thememory_edgesshape requires neither columnwalkabletestArel_typesentry whose declared relation is not walkable raisesMemoryTraverseRelationUnwalkable.hop-boundtestA walk follows at most 3 hops; a largerhopsraisesMemoryTraverseHopsExceeded.fan-outtestA node expands at most 64 memory-edges, ordered byvalid_fromnewest first then edge id; the rest count inedges_omitted.frontier-captestA walk stops at 512 memory-edges and counts unexpanded edges inedges_omitted; a non-zeroedges_omittedmarks the walk incomplete, andtruncatedstays withtruncation-cause.omitted-readabletestedges_omittedcounts only edges the caller’s session reads.Why
because a count of hidden edges discloses their existence, assuppression-countavoids naming claimscycletestTraversal proceeds breadth-first by hop and expands each node at most once.Why
because a revisited node adds no edge and a cycle otherwise exhaustsfrontier-cap
unsettled: Does a graph arm seeded by the query’s resolved entities fuse into fusion as a third leg, and at what weight? owner: read-path affects: read.traverse
Shapes
A declaration naming a shape and its declared relation types:
[[table]]
name = "team_memory"
shape = "memory_facts"
[[relation.declared]]
name = "reports_to"
direction = "directed"
cardinality = "one"
walkable = true
[[relation.declared]]
name = "owns_service"
direction = "directed"
cardinality = "many"
walkable = true
The reserved-traits table:
| Relation | direction | cardinality | walkable |
|---|---|---|---|
supports | directed | many | true |
contradicts | undirected | many | true |
supersedes | directed | many | true |
about | directed | many | false |
derived_from | directed | many | true |
The label view:
CREATE VIEW outcome_labels AS
SELECT p.prediction_id, p.subject, p.resolution_source,
o.verdict, o.signal, o.settling_citation,
epoch(CAST(o.observed_at AS TIMESTAMP)) - epoch(CAST(p.predicted_at AS TIMESTAMP)) AS lead_time_s
FROM predictions p
JOIN outcomes o ON o.prediction_id = p.prediction_id
WHERE o.observed_at IS NOT NULL
AND o.signal IS DISTINCT FROM 'self-rated'
AND epoch(CAST(o.observed_at AS TIMESTAMP)) >= epoch(CAST(p.predicted_at AS TIMESTAMP))
AND epoch(CAST(o.observed_at AS TIMESTAMP)) <= epoch(CAST(p.deadline_at AS TIMESTAMP)) + 86400;