zirvDocs

DocsSupervision (zirv ctx)

Memory

README.md, lines 1002–1052, 7103–7257View source

zirv memory#

zirv memory manages this repository's memory bank without starting an AI session:

bash
zirv memory init --dry-run
zirv memory init
zirv memory init --source /path/to/docs --merge
zirv memory status
zirv memory list
zirv memory recall staging-db
zirv memory remember staging-db-creds "the staging DB creds live in 1Password under staging-db"
zirv memory remember deploy-cmd "cargo build --release" --importance high --confidence high --tag deploy --tag release
zirv memory forget staging-db-creds
zirv memory verify staging-db-creds

remember also takes --importance <low|normal|high>, --confidence <low|normal|high>, and a repeatable --tag <t> — all optional, unset by default. They land in the stored entry and feed zirv memory recall's ranking (retrieval::score_one); zirv ctx remember has no equivalent flags. --importance/--confidence reject any value outside the three listed.

memory init proposes a bounded set of durable shared entries from repository validation/toolchain surfaces and high-signal Markdown sections. --dry-run changes nothing, --source accepts a Markdown file or directory (including an Obsidian vault), and a non-empty shared bank is refused unless --merge is passed; merge mode adds missing keys and never silently overwrites curated entries. --max-entries and --max-bytes cap initialization.

Every verb defaults to the private (machine-local) bank; pass --shared to act on the shared, repository-owned bank instead — see Memory bank below for what the two scopes mean. list and recall respect each scope's own gate (memory.enabled / memory.shared_enabled): a disabled scope lists or recalls empty rather than showing what it holds. status never hides a disabled scope's counts — it marks the scope disabled but still reports its entry count and stored bytes, since a byte count is not the entry content the gate exists to withhold. forget and verify work even while a scope is disabled — disabling a scope must never trap data behind it. status never prints an entry's key or body, only scope availability, entry counts, stored bytes, and the configured injection budget. zirv ctx remember --key <k> --text <t> / zirv ctx recall / zirv ctx forget <k> (flag-based, private-scope only) are untouched and keep working exactly as before — zirv memory is a newer, scope-aware surface alongside them, not a replacement. forget on a missing key exits 0 (it is idempotent — "already gone" is success); verify on a missing key exits 1 (it is stamping a claim about an entry that does not exist, which is a real failure) — the asymmetry is deliberate, not a bug.

Memory bank#

Besides mail (session-to-session notes) and handoffs (task continuity across a restart), zirv keeps a third, longer-lived store per repository: a small bank of durable facts about the repository itself, independent of any one task or session.

bash
zirv ctx remember --key staging-db-creds --text "the staging DB creds live in 1Password under staging-db"
zirv ctx recall
zirv ctx forget staging-db-creds

See zirv memory above for a newer, scope-aware surface over this same bank (status/list/recall <query>/remember <key> <text>/ forget <key>/verify <key>, --shared for the repository-owned scope below) that works without starting an AI session — the two verbs above keep working unchanged alongside it.

The handoff-vs-memory boundary matters. A handoff is task continuity: what this specific task was doing, what remains, what to try next — written once per restart, and only ever useful to the session that picks that particular task back up. Memory is repository facts: things true regardless of which task is in progress — a build command, where a credential lives, a convention, a gotcha about how a dependency behaves. Task state does not belong in memory, and a repository fact does not belong in a handoff.

By default nothing is added to the bank automatically — zirv ctx remember is a deliberate act, by a session or a human. Set [memory] harvest = true (or ZIRV_CTX_MEMORY_HARVEST=true) to let zirv also try to extract durable facts on its own: right after a rot restart distills a handoff (exec or wrap, and only from a genuinely distilled handoff, never the mechanical fallback), one extra cheap-model call looks at the handoff's Gotchas learned and Files touched sections and proposes zero or more key: value facts. Harvesting stays off by default because a cheap model can be confidently wrong: an unreviewed guess landing in a bank every future session reads is a worse failure mode than simply not harvesting. A harvested entry replaces any existing entry with the same key rather than duplicating it, the same as an explicit remember does, and a distiller failure or timeout leaves the bank untouched.

Every entry has a Verified stamp alongside its Written one; an entry older than 30 days without being re-verified is flagged as stale wherever the bank is summarized (zirv ctx status, zirv ctx optimize). zirv ctx optimize's report includes a memory-bank summary block — count, total bytes, oldest/newest age, how many are stale, a duplicate-key check — but never quotes an entry's key or body: that content is repository-scoped, cross-session data with nothing to do with what optimize is reviewing, and it is read separately from (never folded into) the surfaces sent to the judgment model.

toml
[memory]
enabled = true
harvest = false          # off by default; see above
max_entries = 50
max_entry_bytes = 512
max_injected_bytes = 2048       # superseded by core_max_bytes; kept only so an old config does not error
shared_enabled = true
core_max_bytes = 2048           # cap on the merged private+shared core layer folded into every session
retrieval_max_bytes = 2048      # cap on `zirv memory recall`; reserved for future context-aware session retrieval
retrieval_max_entries = 6       # max number of recalled entries, independent of bytes

There are two memory scopes, gated separately but not independently: enabled is a master switch that disables both scopes, and shared_enabled is a second, shared-only toggle underneath it -- enabled = false always wins, so an operator who turned memory off before the shared scope existed does not silently start receiving repo-controlled prompt content on upgrade. The private bank still lives under the state dir, never in the repo (the same "a checkout is not the operator" reasoning as handoffs and mail). The shared bank is the opposite by design: <repo>/.zirv/memory/ is untrusted repository content, one key-addressed file per entry, meant to be committed, reviewed, and hand-edited like any other file in the checkout -- that is the whole point of a Git-friendly shared memory bank. What stays forbidden either way is the configuration, not the content: every [memory] key, including shared_enabled, is repo-forbidden outright -- a checkout may not set any of them to any value at all, so it can neither switch either scope's own gate on or off for itself, raise its own caps, nor turn on automatic harvesting. Only ~/.zirv/ctx.toml, ZIRV_CTX_*, or flags (the operator) may. See Trust boundary above.

Consistent sessions#

When zirv starts an agent through wrap, exec, loop or resume it injects a small system prompt so sessions behave the same way every time. Three layers concatenate, in order:

  1. A shipped default baked into the binary: respect repo conventions, use tools deterministically, report failures honestly.
  2. Your own additions, by session role: ~/.zirv/system-prompt.md for an interactive session you drive yourself (chat, wrap, resume), or ~/.zirv/system-prompt.worker.md for a delegated headless worker (agent, exec, loop). Both are optional, and neither role reads the other's file — if you had worker-facing instructions in system-prompt.md, copy them into system-prompt.worker.md.
  3. <repo>/.zirv/system-prompt.md, the repository's additions (one file, both roles).

The repo layer is untrusted input, treated the same way ctx.toml's repo layer is: it is capped in size, labeled in the composed prompt as coming from the checkout, and stated not to override anything above it. A repository cannot turn its own layer on or raise its own cap: prompt.enabled, prompt.repo_layer and prompt.max_repo_bytes are all rejected from a repo config. Set them in ~/.zirv/ctx.toml, or with ZIRV_CTX_PROMPT, ZIRV_CTX_PROMPT_REPO and ZIRV_CTX_PROMPT_MAX_REPO_BYTES.

toml
[prompt]
enabled = true
repo_layer = true
max_repo_bytes = 4096

prompt.verbosity (issue #427; "minimal" | "standard" | "verbose", default "verbose") picks how much of the Orchestrator-only meta-harness orientation layer (zirv's own explanation of itself, the checkpoint/mail habit, delegation mechanics, lifecycle sizing, the design-approval gate, the review policy) is injected. "verbose" is today's full text, byte for byte -- the default changes nothing for an operator who never touches this key. "standard" drops only the purely descriptive framing and self-discovery bullets; every bullet that changes what the session does is kept verbatim. "minimal" drops those two plus the roster pointer (the harness roster itself, if enabled, is still appended regardless of tier), and compresses everything else to its shortest behaviour-changing form: it still teaches delegation mechanics, that an undirected zirv ctx send is claimed by exactly one session while --all fans out, that durable facts persist via zirv ctx remember/recall, that substantial work starts a zirv workflow, the design-approval gate, and the review policy's hard stop after 2 fix rounds. It is REPO_FORBIDDEN: set it in ~/.zirv/ctx.toml or with ZIRV_CTX_PROMPT_VERBOSITY.

toml
[prompt]
verbosity = "verbose"

Issue #772 also tiers the shipped engineering-standard floor itself (DEFAULT_PROMPT/DEFAULT_PROMPT_WORKER, not a [prompt] key -- there is nothing to configure): an Orchestrator/SubOrchestrator session gets the full standard, while a delegated Worker/Single session -- already handed a bounded, pre-sized task -- gets a compact variant that keeps every rule that changes behaviour (verify with evidence, one focused test per behaviour change, no slop, deliver exactly what was asked, honest report) and drops the full sizing taxonomy's own long-form explanation and the UI/design-thinking bullet, the same role split the standing skill index already draws (prompt. skill_index) for the identical reason: a delegated worker re-reads this layer on every headless turn, so its cost is not amortised the way an interactive session's is.

Pass --simple to any of the four verbs to start the agent with no zirv text at all, shipped default included. Supervision, pacing and hooks are unaffected. Whether a prompt was injected, and from which layers, is recorded in the decision log at every session start.