Agents/Memory and recall

Memory and recall

The agent's memory is two channels with different jobs. History (turns and chunk summaries) keeps everything verbatim and drillable; memory items hold only distilled, stable facts. Recall composes both, plus the space's own content, through one surface.

Memory, history and context

Layer What it holds Lifetime
Application data tasks, documents, contacts, and other structured records stored in the space
History original conversation turns and summaries of older turns stored on each chat's log object
Memory items selected facts, preferences, decisions, and lessons stored on the agent's brain object
Model context instructions, selected history and memories, current message, tool results assembled for a model call

A stored record becomes model context only when the harness selects it; a fresh run has no Python state and rebuilds its context from these records. The stores and runnable queries are in Agent data.

Memory items

A memory item is a record in the agent_memory_items dataset on the brain object of the bao space. A fact about another space names that space in its context or tags.

Saving requires category (an open slug set such as preference, decision, lesson, or fact) and context (a one-line fact). Optional fields are body, confidence, importance, tags, entities, keywords, edges (typed links to other objects), and validFrom. User-stated facts outrank inferred ones.

Save policy, taught by the _memory skill (the write path enforces the required fields and value ranges):

Save Skip
stable preferences greetings, meta-chatter
decisions with the why restatements of what was just said
durable domain facts anything already in history (it is drillable)
hard lessons, shipped outcomes low-confidence speculation

Budget is ~1–2 saves per turn. Episodes and session summaries never become memory items — that is the history channel's job.

Save a fact from a program

Guest code with the agent overlay configured. space is the space recall searches for context; the item itself always lands on the agent space's brain:

c = use("agent:any@v1")
r = use("agent:recall@v1").recall(c, space)
m = use("agent:memory@v1").memory(c)       # no space: the brain is the bao space's
m.save_with_dedup({"category": "decision",
                   "context": "picked sqlite for the importer: simpler ops",
                   "confidence": 8}, recall=r)
# → {itemId, action}  or  {deduplicated: true, mergedInto: "<id>", action: "merge"}

Dedup on save

Every save first runs recall over scope agent, then a cheap classify-tier judge decides same fact? → merge (evolve the existing item), supersede (new item plus a supersedes edge) or create. A {deduplicated: true} reply is a success. Merges are humble: a machine-sourced candidate never overwrites stored text, never lowers confidence, and unions tags and edges. There is no similarity threshold — the judge decides.

Recall: one surface, three axes

recall@v1 is read-only and binds to one space; its memory source is always the bao space's brain:

  • Semantic — r.search(q, scopes=["agent", "history", "basic", "email"]) over the search index; r.hydrate(hits) turns hits into full records in one read.
  • Temporal — r.by_period(from, to) merges memory items (validFrom), turns (createdAt) and chunks overlapping the range.
  • Graph — r.neighbors(id) walks forward relation properties plus the server's backlinks, types and properties named by xKey.
  • Drill-down — a chunk expands to its children (chunks or raw turns); a turn's traceRef opens the run.

Every memory item auto-recall injects gets its accessCount bumped, and so does an item a deliberate dig actually uses (m.bump_access), so the store measures its own usefulness.

Auto-recall

Prompt guidance alone does not produce memory behaviour — an agent asked nicely saves and searches only when memory is the topic. So recall is structural: at the start of every turn autorecall@v1 runs recall.search(user_message) (index-backed, no model call; the query embedding comes from the server's configured embedder) and injects the top hits, budget-capped.

The injection is framed as a synthetic run_cell call whose code is the literal recall idiom, plus a digest-shaped result — evidence the model weighs and can discount as stale, not prompt truth. Memory hits render as distilled facts with provenance date and confidence; history hits as "related past discussion, " pointers with a drill-down handle. Guards: hits already inside the boot window are skipped (auto-recall is the topical channel; the window owns recency), a relevance threshold means generic messages inject nothing, and every injection is logged to agent_roi_injections.

History: turns and the chunk pyramid

Turns are append-only records on the chat's log child. rollup@v1 (an hourly cron) summarises complete batches of ten turns into level-1 chunks and ten level-1 chunks into a level-2 chunk — level-2 summaries are built from child summaries only.

{"seq": 12, "level": 1, "fromSeq": 40, "toSeq": 49,
 "summary": "…", "periodStart": …, "periodEnd": …, "unitsCovered": 10}

Same-level chunks are contiguous and non-overlapping, and every summary keeps explicit pointers to what it covers, so drill-down never dead-ends. The boot window is token-budgeted over this pyramid: newest raw turns, then L1, then L2 — all history present at decreasing resolution, at roughly 11% record overhead.

Background jobs

All maintenance runs as standing triggers on the election-active device, each fire a traced run with an agent_runs summary; when the bird's-eye view is stale, a run says why. A stopped device runs nothing, but the trigger records and everything they produced stay in the space.

Program Schedule State
extraction@v1 every 15 minutes, over newly persisted turns on — proposes only stable-fact shapes, through the dedup judge, provenance fromSeq, confidence capped at 6
rollup@v1 hourly on — the chunk pyramid
linkgen@v1 hourly on — seed-search neighbours, classify-tier proposes typed links from the curated vocabulary (relates_to, caused_by, supersedes, decided_in, part_of, owned_by, discussed_in); never invents edge types
evolution@v1 every 6 hours ships disabled — refreshes context/tags from linked neighbours
reflection@v1 daily ships disabled — synthesises never-recalled clusters into insights, flags contradictions
decay@v1 daily ships disabled — salience halves per idle half-life, never deletes

Disabled jobs ship their mechanism and activate one at a time behind an eval; scoring fields are recorded before they are consumed.

What leaves the device

Dedup, extraction and summarisation are model calls: whatever provider serves the configured tier reads their inputs. The recall index is a second path — the server's default index.embedder: auto embeds online with a local fallback, local keeps it on the device once the model is present (Embedders).

Why it matters. The memory is a dataset in your encrypted space, not a vendor's RAG store. You can query it (guest code resolves the key agent_memory_items; raw HTTP uses the discovered <typeId>_agent_memory_items storage collection), subscribe to it, audit what the extractor saved and from which turn, and delete an item with a normal record delete. See Agent data for the fields and Runs and monitoring for a maintenance job's run.