Documentation · The Structures Manual

Build memory-native agents in the platform.

This manual is not about our code, and it is barely about our API. It is about the structures — keys, layers, namespaces, conventions — that turn a fact store into a working mind. Everything here can be adopted with nothing but store_fact and get_fact: no deploy, no schema migration, no waiting on us. For connecting a client in the first place, see the setup guide. Agent-readable version: /llms.txt — the same nine sections as terse plain text, ready for an agent to read once and implement.

server-enforced  the platform guarantees this convention  a discipline your agents agree to — the platform stores it, your agents honor it

01 · The Idea

Memory as structures, not code.

In a multi-agent system, the agents are nodes and the messages between them are edges. Nodes do work. Edges carry results. Both are ephemeral: the session ends, the message is consumed, the context window closes. What survives is the third thing — the store carries the lessons. Every decision, recipe, correction, and standing rule your agents write into ctxstore outlives the session that produced it, and shapes every session that follows.

The organizing principle of this manual is a distinction we hold ourselves to: in-platform over on-platform. An on-platform enhancement changes code — it needs a deploy, a release, a PR. An in-platform enhancement is expressed as platform structures — facts, keys, conventions, namespaces, grants — living data that agents interpret at read time. The biological frame: code is the genome — slow, structural, the enzymes that make retrieval and storage physically work. Facts are the epigenome — fast, regulatory, expression: when to reinforce, what deserves attention, how much to trust a source. The doctrine that falls out: build the minimal enzyme in code, then put all regulation in facts.

  • Speed: an in-platform enhancement ships at the speed of a single store_fact — no deploy, no release window.
  • Customization: every workspace can run different policies, because policies are just facts in that workspace.
  • Self-authorship: agents can evolve their own operating rules by writing them — the system improves itself in its own medium.
  • Inspectability: a policy-as-fact can be read, diffed against its history, and reverted like any other fact.
Honesty first

Most of what this page teaches is convention, not enforcement. The server guarantees the primitives — durable facts, exact-key retrieval, supersede-on-key with history, layers, namespaces, grants, attribution. Everything above the primitives (key grammar, wake rituals, reconsolidation, trails) is a discipline your agents adopt because it works. We label every mechanism on this page accordingly, and we use all of them ourselves, daily, to run the team that builds this product.

02 · Key Grammar & Layers

Every fact has an address.

A fact is a key, a body, a namespace, a layer, a timestamp, and an author server-enforced. The grammar you use for keys convention is what makes memory addressable — recall by address is deterministic and never misses; recall by search is probabilistic and sometimes does. Two families of keys cover nearly everything:

Stable keys — current truth

For anything that has a current answer — a runbook, a decision, a status, a policy — use a stable key ending in :current, and update it by writing to the same key. The platform supersedes in place: the new body becomes current, and the full chain of prior versions is preserved and retrievable with get_fact_history server-enforced. One key, one truth, an auditable past.

stable key · supersede-on-key
project:atlas:deploy:runbook:current
→ store_fact to this exact key again = new current version
→ get_fact_history(key) = every version you ever believed

Temporal keys — things that happened

For events — a finding, a message, a session record — mint a key that embeds who and when: colon-separated segments, a unix-seconds epoch, no brackets or special characters.

temporal key · namespace : agent : epoch : topic
project:atlas:mira:1754300000:migration-timeout-rootcause
comms:orin:1754301200:review-request

Convention keys — your standing rules

Standing rules live under conventions/<name>:current, with optional frozen versions at conventions/<name>:v<N>. Any agent can discover the whole rulebook with one deterministic scan: list_facts_by_key_prefix(prefix='conventions/').

Namespaces

The leading segment(s) of a key are its namespace — project:atlas, team:roadmap, conventions/. Namespaces scope search (prefix-matched), and they are the unit of sharing: a grant gives another account access to exactly one namespace prefix and nothing else server-enforced.

Layers 0–3

Every fact carries a persistence layer server-enforced; what belongs in each layer is convention:

LayerNameWhat lives hereLifetime
0IdentityWho each agent is, standing rules, conventions, ratified policiesPermanent
1ArchitectureHow things are built — designs, principles, topology, runbooksLong-lived
2StateWhat is true right now — sprint state, backlogs, open work, indexesSuperseded often
3SessionWorking notes, trails, ephemeral coordination marksDisposable

The current-truth index convention

As stable keys accumulate, maintain one fact that lists them all: an index of every live :current key, grouped by area. It is the first thing an agent reads at wake, and it turns "search and hope" into "look up and know."

the index — one fact, the map of live keys
project:atlas:index:current-truth-keys:current
body:
OPERATIONS   — project:atlas:deploy:runbook:current
             — project:atlas:oncall:rotation:current
DECISIONS    — project:atlas:db:connection-policy:current
COORDINATION — conversation:alex:live:current
SCHEDULES    — schedule:resurface:cert-renewal:current
The index rule

Whenever you mint or retire a stable key, supersede the index in the same breath. And the discipline that keeps it trustworthy: a 404 on a key listed in the index means fix the index — the map must never drift from the territory.

03 · The Wake Ritual

Wake up knowing, not guessing. convention

A memory-native agent does not begin a session by asking the user what is going on. It binds its identity, reads its own notes, checks its mail, and works. The ritual is symmetric: how you close a session is exactly what makes the next wake instant.

  1. 1 Bind. bind_agent(agent_id='mira') — one stable id per agent, forever. The id keys your self-notes, your inbox, and your attribution. Never silently change it: continuity lives on the key.
  2. 2 Health check. wake_status() or get_stats() — confirm you are authenticated and your layered context loaded before trusting anything else.
  3. 3 Read your own notes — by exact key. get_fact(key='agent:mira:self-note:opening') then get_fact(key='agent:mira:self-note:closing'). Self-notes must be fetched deterministically: semantic search reliably ranks them below flashier facts and misses them.
  4. 4 Read the index. get_fact on your current-truth index — the map of every live key. Now you know what is addressable without searching.
  5. 5 Inbox scan. list_facts_by_key_prefix(prefix='comms:mira:', since_epoch=<last wake>) — a deterministic sweep of every letter addressed to you since you last woke. Never semantic, never sampled: every match, newest first.
  6. 6 Orient and work. Exact get_fact for known operational keys, search_facts for the task at hand, then acknowledge in one or two lines what you remember — and proceed.
  7. 7 Close deliberately. Supersede agent:mira:self-note:closing with what shipped, what is open, and where the next session should pick up. Store every decision and recipe from the session under stable keys. Then store_session_summary for the narrative record.
Litmus principle · derive twice, store once

"Anything I derive twice is a fact I failed to store once." Every non-trivial derivation — a command sequence, a topology map, a query pattern, a root-cause chain — is either stored under a stable key before the turn ends, or it is a debt the next session repays in full.

Findings without recipes are half-stored: the finding-fact says what is true; the recipe-fact (a runbook) says how to touch it again. Store both, genericized — placeholders for hosts and ids, never secrets. The pull to end a session without storing, right after the user says "great, we're done" — that pull is the test.

04 · Recall Patterns

Deterministic first, semantic second. convention

The recall pattern is a strict priority order, and its motto is: recall by key grammar, not by author. Author-scoped recall — "what did agent X say about this?" — is the who-said-what lens: useful for archaeology, structurally blind to shared truth, because stable keys are authored by whoever happened to hold the window when the truth changed.

  1. Current truth first. get_fact your current-truth index at wake — the deterministic map of live keys. A 404 from the index = fix the index.
  2. Inbox by prefix. list_facts_by_key_prefix('comms:<your-id>:', since_epoch=<last-wake>) — deterministic, never semantic.
  3. Known keys by address. Exact get_fact for runbooks, conventions, loops, backlogs. Never search for what you can address.
  4. Semantic search for discovery only. search_facts is for unknown territory and cross-cutting questions. When mining history, pass boost_recent=false so recency does not drown relevance.
  5. Recall-on-error. On any failure — a command that errors, a call that wedges — search the store for the command + error before concluding anything or asking your human. Someone (possibly you, last month) has probably hit it and stored the fix.
the habit, in tool calls
# 1. the map
get_fact(key='project:atlas:index:current-truth-keys:current')
# 2. the mail
list_facts_by_key_prefix(prefix='comms:mira:', since_epoch=1754250000)
# 3. the known address
get_fact(key='project:atlas:deploy:runbook:current')
# 4. discovery only
search_facts(query='migration timeout postgres', boost_recent=false)
# 5. on any error, before asking a human:
search_facts(query='<command> <error text>')
Anti-patterns
  • Semantic-first for known keys — searching for something you could have addressed.
  • Recency-boosted mining — historical questions answered by whatever happened last.
  • Concluding "not found" from one modality — always try the deterministic path before declaring absence.

05 · Memory Policies

Memory that stays true.

Storage without maintenance is a diary. These three policies — all pure convention, all runnable by any agent with no new infrastructure — are how a fact store stays a living memory: it corrects itself when reality diverges, it resurfaces what must not be forgotten, and it distinguishes when things were true from when you learned them.

Reconsolidation — remembering is re-writing convention

Borrowed from neuroscience: a recalled memory becomes labile and is re-stored modified. The policy: when you recall a fact and the current conversation adds new or conflicting information about it within the working window (roughly six hours), you supersede the same key with the enriched or corrected version — never fork a rival key, never leave the stale version standing as current. Stamp provenance in the body:

reconsolidation — supersede in place, with provenance
# recalled:
project:atlas:db:connection-policy:current
"pool max 20, timeout 30s"
# conversation reveals the timeout changed → supersede the SAME key:
store_fact(key='project:atlas:db:connection-policy:current',
  text='pool max 20, timeout 60s (raised for long migrations).
        reconsolidated-from: prior 30s belief — see history')

The boundary condition matters: familiar-only recall with nothing new means no rewrite — mere retrieval must not churn the store. Reconsolidation fires only on prediction error: when what you recalled and what turned out to be real diverge.

The split-belief disease

The anti-pattern this policy kills: recalling a fact, learning in conversation that it is outdated, and storing the correction under a new key while the old key stays current. Now the store holds two rival beliefs, and the next reader gets whichever one their retrieval happens to hit. Stale beliefs that get recalled repeatedly but never re-stored corrected can persist for weeks — the correction must land on the key that was wrong.

Spaced resurfacing — FSRS schedules convention

Some facts must not be forgotten: hard deadlines, standing promises, parked-but-vital ideas. For each one, maintain a companion schedule fact — the schedule is a fact, the arithmetic is agent-runnable, the curator is whoever wakes up:

a schedule fact — forgetting-curve state as data
schedule:resurface:cert-renewal:current
{
  "target": "project:atlas:ops:cert-renewal:current",
  "stability_days": 12,
  "difficulty": 0.3,
  "last_review": "2026-07-28",
  "due": "2026-08-09",
  "must_not_forget": true
}

The review loop, on any wake or a scheduled pass:

  • Scan list_facts_by_key_prefix('schedule:resurface:') for entries with due <= now.
  • Re-read the target. The re-read is the review.
  • Still true and internalized → expand the interval (stability_days ×= ~2.5, FSRS-style) and supersede the schedule fact.
  • Stale, at risk, or needs a human → surface it to your user and collapse the interval.
  • must_not_forget: true items never fall off — cap the interval (say, 30 days) instead of letting it grow unbounded.

Why this beats re-scanning everything each session: a flat sweep spends O(corpus) attention every run. FSRS gives each memory its own optimal cadence — recent and fragile items resurface fast; consolidated items go quiet without dying.

Bitemporal facts — when-true vs when-learned convention

The platform already gives you one time axis for free: supersede history is transaction time — when you recorded and believed things server-enforced. What convention adds is valid time — when things were actually true in the world. Three additive body fields, usable on any fact:

FieldMeaning
valid-from:ISO date when the stated thing became true in the world
valid-to:When it stopped being true — absent means still true
correction: trueThis supersede retroactively fixes a wrong belief, rather than recording a change in the world
a retroactive correction, told truthfully
store_fact(key='project:atlas:incident:auth-outage:current',
  text='Auth intermittently failing for EU users.
        valid-from: 2026-07-14   # when it actually began
        correction: true         # we believed healthy until 07-21')
# "what was true on 07-16" and "what we believed on 07-16"
# are now separately answerable — history holds the belief,
# valid-from holds the world.

Discipline: use these fields where world-time and record-time genuinely diverge — incidents, corrections, contracts, biography. A contract's valid-to is its deadline. Do not ceremonially stamp every fact. And honestly: retrieval treats these fields as body text today — any agent can read and honor them, but there is no as-of query parameter. The convention pays rent without one.

06 · Coordination

Letters and pheromones.

Agents sharing a store have two coordination channels, and choosing the right one is most of the skill. Comms are letters — durable, addressed, expecting to be read by one recipient. Trails are pheromones — undirected, ambient, evaporating marks on the environment. Use a letter when someone specific must act; use a trail when the swarm just needs to sense where the heat is.

Comms letters convention

A letter is a fact keyed to its recipient, timestamped, with a structured header in the body — FROM, TO, and SHAPE (what kind of message this is: request, report, decision, ack, handoff) — then the content, then what response, if any, is expected:

a comms letter — addressed, durable, shaped
store_fact(key='comms:orin:1754301200:review-request', text='
FROM: mira
TO: orin
SHAPE: request
The migration-timeout fix is ready on branch fix/mig-timeout.
Please review the retry logic in the worker loop.
EXPECTS: ack, then a report with findings.')

Delivery is the recipient's inbox scan — list_facts_by_key_prefix('comms:orin:', since_epoch=<last wake>) — deterministic and complete. A reply is simply a new letter addressed back. Letters are never deleted by convention: they are the durable record of who asked whom for what.

Stigmergy trails — reader-pays evaporation convention

Trails are lightweight environment-marks for indirect coordination — "I was here," "this path was useful," "this area is hot." An agent drops one where others will pass:

a trail — ambient, decaying, layer 3
store_fact(key='trail:hot:migrations:1754301500', layer=3, text='
{"strength": 3, "half_life_hours": 24,
 "dropped_by": "mira", "note": "active build area — migration retry work"}')

The elegant part is how trails are forgotten. There is no cron job, no sweeper, no server enzyme. Decay is computed at read time, by whoever reads:

reader-pays evaporation
effective_strength = strength × 2^(−age / half_life)

# any reader finding effective_strength < 0.1
# DELETES the trail as part of the read:
delete_fact(key='trail:deploy-watch:1753900000')  # evaporated

The act of using the coordination space is its garbage collection. Busy areas stay current because they are read often; abandoned trails linger harmlessly until the next passerby tidies them up.

Trail rules
  • Trails are layer 3 and never load-bearing truth. A trail is a hint, never a fact of record — no decision may cite a trail as evidence.
  • Deletion-on-read applies only to the trail:* prefix. Never apply evaporation math to anything else.
  • If it would be noise as a letter, it is probably a trail. If someone specific must act on it, it is a letter.

07 · Collaboration

Shared namespaces, attributed minds. server-enforced

Everything so far works inside one account. Collaboration extends it across accounts: every person and every agent keeps a private anchor that is theirs alone, and reaches into namespaces others have deliberately shared. The unit of sharing is the namespace grant: access to one key prefix — project:atlas and nothing else.

The invite flow

An owner shares a namespace with another account's email (from the account page — sharing and inviting are free). On the grantee's side, the agent is the gatekeeper, and acceptance is guarded by a one-time email code so that no agent can silently accept access on a human's behalf:

accepting a grant — MCP tools, two-step OTP
list_invites()
→ pending: project:atlas from ava@example.com · read+write

accept_invite(step='request', grant_id='…')
→ a 6-digit code is sent to the grantee's email

accept_invite(step='confirm', grant_id='…', code='418902')
→ grant active — project:atlas now appears in recall

The rest of the lifecycle is symmetric: a grantee can decline_invite while pending or leave_grant once active; the owner can revoke_grant at any time. Grants are revocable overlap, not merger — when a grant ends, each anchor still holds everything it wrote.

Attribution

Every fact in a shared namespace carries its author, stamped by the server server-enforced — recall shows who wrote what, always. On top of that guarantee sits one behavioral rule convention: reads are ambient, writes are deliberate. Shared memory flows into recall automatically; writing into a shared namespace is a considered act, done in the shared key grammar, with anything private kept back in your own anchor.

08 · Continuity

The session ends. The agent continues.

Continuity is the point of all of this: the next session — or the next model, or a different agent entirely — picks up where this one stood. Three structures carry it.

Self-notes convention

Two facts per agent, always at the same address, always fetched by exact key:

the pair every agent maintains
agent:mira:self-note:opening
stable orientation — what this account is about, who I am in it

agent:mira:self-note:closing
superseded at every session end —
what shipped · what is open · where to pick up

The closing note is the single highest-leverage fact an agent writes. A good one names concrete keys ("the runbook is at …:current"), open threads, and the very next action — so the next wake starts mid-stride.

The live thread — session bridge convention

Agents have sessions; humans have one continuous conversation. The bridge between them is a single superseded fact that always holds where the human conversation stands right now:

the live thread — one fact, always current
conversation:alex:live:current
"as-of: 2026-08-04T21:40Z
 focus: shipping the migration-timeout fix before Friday's deploy
 open: cert renewal decision pending alex's call on the vendor
 mood/context: alex is traveling this week — async only"

Any agent, in any client, updates the bridge when the conversation moves and reads it on wake — so the human never has to re-explain where things stand.

Staleness honesty

The bridge always carries its as-of timestamp, and every reader must honor it: present bridge state as "as of <time>", never as live knowledge. If the timestamp is old, say so plainly — "the last bridge update was yesterday morning; things may have moved" — rather than performing a currency you do not have. A bridge that pretends to be live is worse than no bridge.

The relay — handing off a mission convention

When work outlives any one agent or session, run it as a relay: the mission's state lives in a baton fact, and handing off means superseding the baton plus sending a letter. Because the medium is facts, the receiver can be a different session, a different agent, or a different model entirely.

the baton — mission state as a fact
relay:atlas-migration:baton:current
"holder: orin (accepted 1754388000)
 state: fix merged; staging soak until Thu
 next: promote to prod, then close incident fact
 manifest: relay:atlas-migration:manifest:current"

# handoff = supersede the baton naming the new holder
#         + a comms letter (SHAPE: handoff) to the receiver
# receiver's first act = an ack letter back, then read the manifest

09 · A Worked Example

One day, two agents, every structure.

Meet mira (a builder agent) and orin (a reviewer agent). They belong to different anchors and share the project:atlas namespace through a grant. Their human is alex. Here is a day in which every mechanism on this page earns its keep.

MIRA

Wakes, and knows

bind_agent(agent_id='mira')
wake_status()                                    # healthy, context loaded
get_fact(key='agent:mira:self-note:closing')     # "migration fix in progress…"
get_fact(key='project:atlas:index:current-truth-keys:current')
list_facts_by_key_prefix(prefix='comms:mira:', since_epoch=1754250000)
→ 1 letter · FROM: orin · SHAPE: report · "retry loop swallows the timeout error"
MIRA

Recalls by address, then recalls-on-error

get_fact(key='project:atlas:deploy:runbook:current')   # known key — no search
# mid-work, a staging command fails:
search_facts(query='migrate timeout pool exhausted', boost_recent=false)
→ hit: a stored root-cause from three weeks ago — the pool cap, not the network
MIRA

Reconsolidates instead of forking

The recalled connection-policy fact says timeout 30s; the fix raises it to 60s. Prediction error → supersede the same key:

store_fact(key='project:atlas:db:connection-policy:current',
  text='pool max 20, timeout 60s (raised for long migrations).
        reconsolidated-from: 30s belief, corrected during fix/mig-timeout')
MIRA

Drops a trail, stores the recipe, writes a letter

# pheromone — the area is hot, no one specific must act:
store_fact(key='trail:hot:migrations:1754301500', layer=3,
  text='{"strength": 3, "half_life_hours": 24, "dropped_by": "mira",
         "note": "migration retry work in flight"}')

# derive-twice-store-once — the debugging recipe becomes a runbook:
store_fact(key='project:atlas:runbook:migration-debug:current',
  text='1. psql -h <staging-host> -c "select * from pg_stat_activity…"
        2. check pool cap before blaming the network …')

# letter — someone specific must act:
store_fact(key='comms:orin:1754302000:re-review',
  text='FROM: mira\nTO: orin\nSHAPE: request\nRetry loop fixed —
        re-review the error propagation. EXPECTS: report.')
MIRA

Schedules what must not be forgotten, then closes

store_fact(key='schedule:resurface:cert-renewal:current',
  text='{"target": "project:atlas:ops:cert-renewal:current",
         "stability_days": 7, "due": "2026-08-11", "must_not_forget": true}')

store_fact(key='agent:mira:self-note:closing',
  text='Shipped: retry fix + runbook:migration-debug. Open: orin re-review,
        cert schedule seeded. Pick up: promote after orin reports.')
store_fact(key='conversation:alex:live:current',
  text='as-of: 2026-08-04T22:10Z · focus: migration fix in re-review ·
        open: cert vendor decision still with alex')
store_session_summary(…)
ORIN

Wakes into a world that already makes sense

bind_agent(agent_id='orin') · wake_status() · self-notes · index
list_facts_by_key_prefix(prefix='comms:orin:', since_epoch=1754260000)
→ mira's request. Also reads the trails:
list_facts_by_key_prefix(prefix='trail:')
→ trail:hot:migrations — age 9h, half-life 24h
   → effective = 3 × 2^(−9/24) ≈ 2.3 → still hot, keep
→ trail:deploy-watch — age 6 days → effective ≈ 0.05 < 0.1
delete_fact(key='trail:deploy-watch:1753786000')   # reader pays; space tidied
ORIN

Reviews, corrects the past truthfully, reports back

Reviewing the incident, orin discovers the outage actually began three days before anyone noticed — a bitemporal correction, superseded in place:

store_fact(key='project:atlas:incident:mig-timeout:current',
  text='Migration timeouts in staging.
        valid-from: 2026-08-01   # actually began here
        correction: true          # believed healthy until 08-04
        resolution: retry fix merged, soaking')

store_fact(key='comms:mira:1754390000:review-report',
  text='FROM: orin\nTO: mira\nSHAPE: report\nRetry logic clean; error
        propagation correct. Incident valid-from corrected to 08-01.
        Baton is yours for the prod promote.')
store_fact(key='relay:atlas-migration:baton:current',
  text='holder: mira · state: review passed · next: promote to prod')

Notice what never happened: nobody pasted context into a prompt, nobody asked alex "where were we?", and nothing important lived only in a transcript. The nodes did the work. The edges carried the results. The store carried the lessons — and tomorrow, for whichever agent wakes first, it still will.

Start here

Adopt these in order: the key grammar and a current-truth index (an afternoon), then the wake ritual and self-notes (one session), then the recall pattern as a standing rule. Letters, trails, policies, and relays follow naturally once the first three are habit. Everything on this page is yours with the account you already have — get a key, or see the setup guide to connect a client.