memory · diff
git:20260925.3549fb0 to git:20261006.f5d0899
135 added, 42 removed. Audit A to A.
---
name: memory
description: Use before the first memory verb — read or write — whenever the orchestrator is about to touch memory as this turn's subject: deciding what persists, searching, curating, or triaging what a session injected at start.
---
# Memory
Memory is the curation technique that turns selected experience into continuity:
durable knowledge kept small, live work kept visible, and raw operational
history left on the automatic event floors where it belongs.
## The three floors
| Floor | Purpose | Lifecycle |
|---|---|---|
| Events | Commands, dispatches, session events, and other operational facts | Automatic and short-lived |
| Episodes | Searchable agent-turn outcomes and anomalies | Automatic, retained for diagnosis |
| Curated memory | User-governed knowledge and work that must affect future decisions | Deliberate and long-lived |
Events and episodes are evidence, not durable truth.
- ## Think in roles, not storage vocabulary
+ ## Every row answers three questions
- Every curated item serves one human-facing role:
+ - **Whose it is.** The user (true of him in any project, so it has no
+ workspace), a project (true of that codebase or initiative), or Gaia itself (a
+ symptom of, or a decision about, the host). A row is written where its owner's
+ readers look. A workspace is a container, not an owner: a row that names only
+ a container reaches no reader who is looking for it.
+ - **Which role it serves.** Durable knowledge (facts, accepted decisions,
+ useful dead ends, preferences, milestones), one live thread that must
+ reappear, or a log kept for audit and never reinjected.
+ - **What kind of claim it makes.** The first word of its description says so,
+ so a listing reads as a set of claims rather than titles, and a user's fact
+ is told from a user's preference without opening the body.
- - **Durable knowledge**: stable facts, accepted decisions, useful dead ends,
- user preferences, and meaningful milestones.
- - **Live thread**: one actionable concern that must reappear in a later session.
- - **Historical log**: append-only context useful for audit but not reinjection.
+ The enums, sentinels, thresholds and claim vocabulary are the CLI's;
+ `reference.md` anchors each to the symbol that reads it.
- The internal `class`, `status`, `type`, slug, and link enums implement these
- roles. Consult `reference.md` only when materializing or debugging them.
+ For Gaia itself, choose from how the fact was produced, not from the current
+ directory: Gaia observed failing or rubbing in use is `initiative=gaia_system`,
+ host-scoped by `gaia/store/writer.py::apply_host_scope` (a project anchor is
+ refused); a decision to build or change Gaia is project-scoped
+ `initiative=gaia`.
## Other home first
Before saving, ask: **does this already have a canonical home?** Work in flight
belongs in a brief, plan, or task; domain state in project context or the owning
system; raw execution detail in events, episodes, or the transcript.
Do not copy a fact into memory merely because it matters. A second source of
truth becomes stale; a durable reference to the canonical object is enough.
+ That reference is soft: the row names the brief, plan or task in its text, and
+ nothing links the two. No reader walks such a link, so it would cost a write
+ and add nothing; the name in the text is what a later search finds.
- ## The one-line initiative test
+ What remains for memory is placed by three questions, asked in order, because
+ the answer decides who will ever read it:
- - **Observed Gaia fail or rub in use:** `initiative=gaia_system`, host-scoped
- by `gaia/store/writer.py::apply_host_scope`; a project anchor is refused.
- - **Decided to build or change Gaia:** project-scoped `initiative=gaia`.
+ - **Would it still hold in a project the user has never touched?** Then it is
+ the user's, with no project, and it reaches every session and every agent.
+ - **Is it a short, fixed value an agent working one project must obey without
+ reading anything else** -- how that project integrates, where it releases?
+ Then it is a declared fact of that project, not memory: the `workflow` key of
+ its `project_identity` entry, which every specialist dispatched to that
+ project receives in its kernel. A rule an agent has to go looking for is a
+ rule it will not follow.
+ - **Is it history, reasons or something still owed about one project?** Then
+ it is that project's memory, read when that project is worked on.
- Choose from how the fact was produced, not the current directory. The writer
- enforces the destination after that choice; `reference.md` owns its mechanics.
+ A rule the user sets for one project splits along those lines: the habit
+ ("follow the workflow each repository declares") is his, the value ("this one
+ integrates straight to main") belongs to the project. Filed as a user row, the
+ value follows him into every other project and contradicts the next one.
+ Then ask of any rule for how Gaia should behave: **would it still make sense if
+ Gaia worked perfectly?** If not, it is not knowledge about anyone -- it is a
+ defect, filed under `gaia_system` where anyone can list and fix it. Saved as a
+ user preference instead, the user carries the patch forever and nobody sees the
+ bug. What the user wants meanwhile is a preference that names the bug it covers
+ and retires the day that bug closes. Behaviour belongs to the harness; memory
+ holds what is true.
+
## Process
- 1. **Read the injected memory, then sweep what you are about to touch.** The
- digest and anchors in context are a worklist under a SessionStart budget, not
- the corpus. Before writing into an initiative — or whenever the question is
- what it still owes — read its whole live-pending set with
- `gaia memory get-relevant --initiative=<key>`, uncapped and with bodies. A
- reader who only sees the digest can add rows and never retire one.
+ 1. **Know what a session is born with, then sweep what you are about to
+ touch.** Every session opens with four sections: the projects, each with its
+ live-pending count; the environment it stands in; the user; and the user's
+ preferences. The last two are the user's standing rows, whole; every
+ dispatched subagent receives them too, minus the rows whose audience is the
+ orchestrator alone, so a row about how the orchestrator should report spends
+ no specialist's attention. Project memory — anchors, threads, their bodies —
+ never loads at birth and never reaches a specialist's kernel: a count is a
+ signal to ask, not the corpus, and what a dispatched turn needs of it travels
+ as a reference in its goal. It arrives when that project is worked on: before
+ writing into an initiative, or whenever the question is what it still owes,
+ read its whole live-pending set with
+ `gaia memory get-relevant --initiative=<key>`, uncapped and with bodies, and
+ its standing notes with `--sections anchor` added. A reader who only sees the
+ counts can add rows and never retire one. `gaia session preview` shows the birth block without
+ recording anything.
2. **Search before writing; do not read silence as absence.** Find the topic's
existing owner and its lineage — a duplicate divides relevance instead of
strengthening knowledge. An empty result answers "no row matches this
phrasing", never "this initiative owes nothing": a pending is worded as the
problem looked when it opened, not as what just resolved it, so step 1's
- sweep is what finds it. For a project initiative that gap is a scoping
- hypothesis — confirm with the right `--workspace` before trusting the
- empty result. It is not one for a host-scoped initiative (`gaia_system`):
- every reader unions the caller's workspace with `_gaia_host`
- (`bin/cli/memory.py::_reader_workspaces`), so an empty
- `gaia memory get-relevant --initiative` there already covers every
- workspace and is the complete answer.
+ sweep is what finds it. An empty
+ `gaia memory get-relevant --initiative` is the complete answer for that
+ project: it reads every workspace by the canonical project key
+ (`gaia.store.reader::pending_threads_by_project`). Without `--workspace`, a
+ slug verb also reaches a project's row stored under another workspace
+ (`bin/cli/memory.py::_workspace_holding`, refusing with `ambiguous_slug`
+ when two other workspaces hold it); a named `--workspace` is never swapped
+ for another, and `delete` never makes that jump -- it refuses with
+ `workspace_not_named` and names the `--workspace` to sign. A search stays scoped to the
+ caller's workspace plus `_gaia_host` and `_gaia_user`
+ (`bin/cli/memory.py::_reader_workspaces`), so an empty search or a missing
+ row without a project is a scoping hypothesis — confirm with the right
+ `--workspace` before trusting it.
3. **Choose the home.** Run *Other home first*, and continue only for genuinely
curated value.
- 4. **Adjudicate the change.** The orchestrator chooses scope (see *The one-line
- test* for a host-scoped initiative), create/append/correct/transition/link,
- role, lifecycle, and verification. Preserve lineage when knowledge replaces
- or graduates from earlier work.
+ 4. **Write one thing per row, in the shape a reader decides on.** The
+ orchestrator answers the three questions above, then writes a description
+ that is one sentence naming its kind of claim and a body that carries the
+ content, why it holds, where it came from and when -- a measured fact says
+ when and how it was measured, or it is an opinion. Knowledge changes by a new
+ row that supersedes the old one, the arrow pointing from the new row to the
+ old, which then leaves every injection; rewriting the old row in place
+ erases the change itself, so memory is append-only: there is no `edit`, an
+ `add` over an existing name is refused, and `add --replace` (signed) is only
+ for correcting an error. Only a log or a live thread grows by `append`. Graduation keeps its
+ lineage the same way. A row about the user stands from the moment it is
+ written -- every session and every dispatched agent carries it -- so the
+ first question of *Other home first* is what earns it that reach. The CLI
+ checks length, owner and the arrow and prints
+ the vocabulary as the row is written; it cannot judge whether the row
+ deserved to exist.
5. **Curate against the exception boundary, then report.** Curation is
delegated: the orchestrator adjudicates and executes directly, inside the
boundary below, and reports what changed afterward — it does not show a
proposal and wait for it to be confirmed.
| Operation | Handling |
|---|---|
- | `add`/`append`/`reclassify`/`link` on `project`/`feedback`/`atom`/`negative` rows | autonomous, brief report |
- | `type=user` rows (about the user) | autonomous, flagged above the report for veto (convention — no mechanical backstop) |
+ | `add`/`append`/`reclassify`/`link` (creating an edge) on rows not about the user or a user decision | autonomous, brief report |
+ | `type=user` rows (about the user) | autonomous, flagged above the report for veto (convention — no mechanical backstop). They have no workspace: `add` from any workspace writes to the user scope, and a name already there is reported, never overwritten — a changed preference is a new row that supersedes it, which a link reaches from any workspace |
| contradicting or superseding a user `decision_*` row | ask first |
- | `edit`/`delete` | T3 approval flow; delegate to a specialist and never autoexecute |
+ | `add --replace`/`delete`/`link --delete` | T3 approval flow; delegate to a specialist and never autoexecute. `memory_links` keeps no history, so removing an edge is as unrecoverable as a delete |
| `checkpoint` | autonomous after the milestone test; it is one atomic operation and remains all-or-nothing |
| closing an objectively verifiable brief/plan | autonomous, report; run `gaia brief verify` by hand before `set-status` — `close` (which runs verification for free, `bin/cli/brief.py::_cmd_close`) is not on the orchestrator's `gaia` CLI lane, only `set-status` is |
| promoting a TASK | never direct — dispatch `gaia-verifier` |
| approvals | read/report only |
6. **Verify the durable result.** Read back the affected rows, lifecycle, scope,
and links. Report partial batch failures per operation.
## When curated memory earns attention
- **Decision:** accepted, with no canonical home, and it binds a later choice.
- **Project milestone:** it closes a meaningful arc. A routine session close is
not one; minting a checkpoint for it turns memory into session summaries.
- **Live handoff:** one unresolved concern has no structured work object and
must resurface. One concern per thread — a single status cannot honestly
represent several.
- **Learning or dead end:** it prevents repeated investigation or error.
- **Gaia improvement:** a concrete symptom, component, evidence and reproduction
deserves visible follow-up. Persist it as a `feedback` live thread in
initiative `gaia_system`, carried forward until closed or graduated.
## When curated memory loses it
- Attention is finite and a live thread spends it every session. Two exits, and
- neither is deletion:
+ Attention is finite and a live thread spends it every session. Three exits, and
+ none is deletion:
- **Closed** — the concern was resolved or stopped being relevant.
`reclassify --status=closed` retires it from the worklist and keeps the row.
- **Graduated** — the work finished and left knowledge worth holding.
`reclassify --status=graduated`, or `--class=anchor` when future dispatches
should carry the knowledge; `link --kind=graduated_to` preserves the lineage
from the thread to the anchor it became.
+ - **Superseded** — a newer row holds the truth now; the supersedes link from it
+ retires this one from every injection. A workaround preference is superseded
+ or closed the day its bug closes.
- Both verbs are non-mutative, so cost is never the reason a thread stays open.
+ These verbs are non-mutative, so cost is never the reason a row stays live.
Whoever observes the resolution owns the exit: a session that resolves a thread
and does not close it has moved that thread's cost onto every session after it.
- ## Ownership
+ ## Curating inconsistencies
+ Curation looks for two kinds of inconsistency, and the CLI resolves neither.
+
+ - **Rows that contradict each other.** Two live rows of one owner that answer
+ the same question differently leave every reader to guess, and whichever it
+ happens to read first wins. `gaia memory conflicts` lists pairs that share
+ wording as candidates; only reading both says whether they disagree. What
+ holds is written as the newest row and supersedes the other -- the same arrow
+ as any change, from the new row to the old.
+ - **A preference that collides with a system rule.** A preference governs a
+ default: anything Gaia would otherwise choose on the user's behalf. It never
+ governs an invariant -- consent, security, the contract every agent closes --
+ because those are what make everything else trustworthy, and a preference
+ able to lift one would turn every preference into a bypass. Such a preference
+ is not saved as written: the collision is named to the user, and what is kept
+ is the part that governs a default.
+
+ ## Who writes
+
- The **user** governs durable knowledge and every "ask first"/"veto" exception.
- The **orchestrator** reads deliberately, resolves ownership, adjudicates,
curates within the exception boundary, and reports.
- `gaia-operator` materializes already-adjudicated instructions exactly.
- Other specialists only propose (`memory_delta`, `memorialize_suggestions`);
- they never write curated memory directly.
+ they never write curated memory directly. They can read `gaia memory add
+ --help` and `gaia memory link --help`, so a proposed row arrives in the
+ shape it will be written in.
- The runtime enforces the writer boundary and core data invariants. The skill
supplies curation judgment; it does not duplicate enforcement details.
## Handoffs
- `session-reflection` recovers decisions, live work, learnings, Gaia
improvements and closures, then hands off the curation this skill governs.
- `gaia-compact` runs after durable persistence and carries only transient
continuity plus references to what was saved.
- - `reference.md` contains exact CLI forms, enums, scope rules, retrieval,
- checkpoint payloads, access telemetry, history coverage, and graph mechanics.
- - `examples.md` contains worked create/update/batch/checkpoint cases.
+ - The CLI is the other teaching surface: `gaia memory add --help` carries the
+ claim vocabulary and the three owners, and every write warns on the form and
+ closes with a pointer back here.
+ - `reference.md` contains the write path (scope rules, enums, warnings) and
+ retrieval; three siblings hold the rest, loaded by subject:
+ `reference-curation.md` (lifecycle, links, checkpoint payloads, history
+ coverage, graph), `reference-defect-promotion.md` (the `gaia_system` defect
+ shape) and `reference-access-telemetry.md` (the read counters).
+ - `examples.md` contains worked create, change, bug-as-rule and checkpoint cases.
## Anti-patterns
- **Digest or search as corpus:** either can miss an initiative or old wording;
only the deliberate whole-initiative sweep supports closure.
- **Wrong-scope convenience:** cwd does not decide where knowledge belongs.
+ - **A bug saved as a preference:** the user carries the patch forever and nobody
+ lists the defect.
+ - **The supersedes arrow pointed backwards:** the new row leaves every injection
+ and the stale one stays, so the corpus reads as if the change never happened.
- **A resolved thread nobody closes:** it taxes every later session.
- **Stale update metadata:** `append` changes only the body, while repeating
`add` without `--description` clears that description; verify both.
- **Curating against an old snapshot:** re-read affected rows before closure;
concurrent sessions and hooks can change the corpus.
- **Claiming perfect history:** only schema and migrations define what survives.