Skip to content

Memory

Steelframe has three memory layers. Do not conflate them:

LayerQuestionStore
Session memoryWhat happenedAgent WAL + SQLite index
Committed memoryWhat is trueGit (docs/memory/)
Config / credentialsHost paths and secretsConfig table

Committed memory outranks retrieve when they conflict.

The agent persists coordinator decisions, worker prompts/outcomes, and operator Chat turns as an append-only write-ahead log on local disk or S3 (RustFS). SQLite holds pointers for search only — summaries, links to issue/PR/SHA/paths, and FTS. Wipe the DB and the agent rebuilds the index from the WAL on startup if wal/head.json exists.

Blobs are redacted on write: PEM blocks, bearer tokens, ghp_, AKIA…, and the master key never land in a blob.

Storage config:

KeyDefaultMeaning
memory_s3_endpointemptyEmpty uses the file backend under the data dir; set it to RustFS in production
memory_s3_bucketsteelframe-memoryBucket
memory_s3_prefixderived from github_repoObject prefix
memory_embed_modelbge-small-en-v1.5off/empty disables embed live
memory_embed_cache_dir/opt/steelframe/modelsONNX model cache
memory_retrieve_limit8Default top-k chunks
memory_retrieve_candidates50Max session packs to fetch

Credentials for the store are the encrypted memory_s3 service (access key + secret); the file backend needs none.

Retrieve is semantic search over session memory. It is shared: workers inject hits as prior context, and Chat answers from them.

  • Derived vector packs live next to the WAL at vec/{model_id}/session/{session_id}. SQLite keeps only pointers; vectors stay in the packs.
  • The default embedder is local BGE-small-en-v1.5 (384-d). Weights download lazily on first embed after the agent is running — not during steelframe setup or agent install.
  • Retrieve filters sessions in SQLite first, then fetches a bounded candidate set, then ranks in-process with cosine. Pack GETs and the query embed share a short deadline.
  • Workers inject the top chunks as a Prior Steelframe context: block. Chat uses the same path with the ranking loop described in Chat.

If the embedder is unavailable, retrieve degrades to recency summaries (mode: prior_context) rather than failing a workflow. Workflows never fail because memory is down.

HTTP:

MethodPath
GET/api/memory/sessions?issue=&pr=&path=&q= (q is FTS)
GET/api/memory/sessions/{id}
GET/api/memory/sessions/{id}/blob (plain text)
GET/api/memory/retrieve?q=&issue=&pr=&path=&fts=&limit=

GET /api/memory/retrieve requires q (retrieve text, at most 2048 characters; empty or longer is 400). limit is capped at 32. mode is semantic or prior_context; fallback returns synthesized summary chunks with score 0.0.

Committed memory is operator-approved truth in the repository:

docs/memory/README.md # index, precedence
docs/memory/general.md # cross-cutting
docs/memory/delivery.md # pickup, PRs, promote
docs/memory/retrieve.md # session memory / Chat retrieve
docs/memory/harness.md # pack, overlay, converge

It changes only through a labeled issue and a PR:

  1. In Chat, assert a lasting fact or correct retrieve.
  2. Chat uses file_memory and files an issue with pickup labels + steelframe:from-tui + steelframe:memory.
  3. The body names the fact and the target file under docs/memory/.
  4. Pickup runs task_lifecycle; the implement worker loads the committed-memory skill, edits only docs/memory/, and opens a feature-branch PR.
  5. After merge, every worker sees it on the next clone.

Never store credentials, PEM, tokens, host paths, or STEELFRAME_MASTER_KEY here.

steelframe agent memory-rebuild replays the WAL into the SQLite index and rebuilds derived vector packs. Change the embed model means a new model_id prefix and a rebuild.