Skip to content

Validation & CLI maintenance

This page covers how mono-agent validate verifies memory configuration and liveness and how mono-agent memory audits and safely repairs the configured backend from an agent folder. It also explains the memory.llm provider choices (ollama vs agent-host) that the validator inspects. The standalone memory-bujo binary that used to run out-of-band maintenance against a memory root has been removed — every maintenance operation now runs config-aware through mono-agent memory.

The memory subsystem never silently downshifts: invalid tier prerequisites and managed index-identity problems fail validation, while operational provider liveness problems appear explicitly as waiting in the Memory section. Run mono-agent validate before cutover, after changing the tier/provider/model/dimension, and after loading or pulling any model.

mono-agent memory — config-aware preview

Section titled “mono-agent memory — config-aware preview”

mono-agent memory is the operator preview for the memory configured in the current agent folder. It loads the same mono-agent.config.json and .env resolution path as the app, so it sees the active memory mode, local root path, and embeddings provider without a separate root argument.

This operator surface remains available when memory.recallTool.enabled is false. That setting removes both live memory-read tools (MemoryRecall and, on local tiers, MemoryJournal) from the agent; it does not disable explicit operator inspection or maintenance. Preview and live recall share the same local store, embeddings, and credential resolution, with bypassing the live-tool gate as the only preview-specific behavior.

Coverage: cli.

Terminal window
# High-level memory configuration and local-store counts
mono-agent memory stats
# Today's or a specific daily markdown file
mono-agent memory today
mono-agent memory show 2026-07-06
# Recall through the configured backend
mono-agent memory search "release checklist"
# Highest-salience local memories
mono-agent memory top --limit 10
# Detailed local operator telemetry (may include local paths/source locations)
mono-agent memory audit --json
# Closed, content-free health contract for automation/fleet checks
mono-agent memory audit --strict --json
# Inspect durable completed-turn intake without printing its payloads
mono-agent memory inspect --json
mono-agent memory inspect <64-character-id> --json
# With the matching agent stopped: make all or one selected item due
mono-agent memory retry --json
mono-agent memory retry <64-character-id> --json
# Explicitly abandon one item
mono-agent memory resolve <64-character-id> <reason-slug> --json
# Build and atomically activate a fresh index from canonical files
mono-agent memory rebuild --json
# Swap back to the retained prior generation
mono-agent memory rollback --json
# One-time explicit TOFU for a stopped legacy managed or unmanaged BuJo
# index whose replay-owned SQLite state predates the canonical sidecar
mono-agent memory adopt-replay --json
# One-time BuJo cleanup: estimate without model calls, then generate a private plan
mono-agent memory curate prepare --plan ./curate-plan.json --limit 120 --dry-run
mono-agent memory curate prepare --model openai-codex:gpt-6-sol --plan ./curate-plan.json
mono-agent memory curate review --plan ./curate-plan.json --accept drop:generic-advice,label:*
mono-agent stop
mono-agent memory curate apply --plan ./curate-plan.json --json
# Only while no intervening store write has occurred
mono-agent memory curate restore --backup /path/returned/by/apply
# Entity identity: list names held by several ids, then merge them explicitly
mono-agent memory entities --duplicates --json
mono-agent memory curate prepare --plan ./identity-plan.json --limit 0 --merge person:morgan-2=person:morgan
mono-agent memory curate review --plan ./identity-plan.json --merge-file ./merges.txt --allow-cross-type
mono-agent stop
mono-agent memory curate apply --plan ./identity-plan.json --json
# Owner backfill: link old owner-subject lines to person:owner (no model call)
mono-agent memory curate prepare --plan ./owner-plan.json --limit 0 --owner-backfill --json
mono-agent memory curate review --plan ./owner-plan.json --reject id:<memoryId>
mono-agent stop
mono-agent memory curate apply --plan ./owner-plan.json --json
# Person links: link old lines to the one person they name (no model call)
mono-agent memory curate prepare --plan ./people-plan.json --limit 0 --link-people --json
mono-agent memory curate review --plan ./people-plan.json --reject associate:person-name
mono-agent stop
mono-agent memory curate apply --plan ./people-plan.json --json
# Tasks to notes: turn open task lines into dated history notes (no model call)
mono-agent memory curate prepare --plan ./tasks-plan.json --tasks-to-notes --before 2026-09-01 --capture-only --json
mono-agent memory curate review --plan ./tasks-plan.json --accept retype:* --reject id:<memoryId>
mono-agent stop
mono-agent memory curate apply --plan ./tasks-plan.json --json
# Explicit, reversible removal of selected BuJo memories
mono-agent memory forget prepare --ids-file ./forget-ids.txt --reason noise_cleanup --plan ./forget-plan.json --json
mono-agent stop
mono-agent memory forget apply --plan ./forget-plan.json --json
# Optional only while no durable post-cleanup change has occurred
mono-agent memory forget restore --backup /path/returned/by/apply --json
# Machine-readable output for scripts
mono-agent memory stats --json

If memory is disabled or missing from config, the command exits successfully and says no memory backend is configured. For local BuJo/journal/lite memory, stats reports the configured tier, write mode, recall-tool state, memory root and active database paths, daily-file counts, markdown/database sizes, record/status/type counts, latest capture/access timestamps, and top entities. today / show <date> print daily markdown when present, and top ranks local memories by salience.

Plain audit is the detailed local operator report: its JSON contains counts, store bytes, exact-duplicate ratio, vector coverage, access concentration, vector backlog, active-generation identity, rebuild policy/source fingerprint, and explicit source-migration accounting. The latter distinguishes indexed items from raw-audit records, unstructured records, missing-identity records (with source locations), recognized legacy-source records (with source locations), and Journal duplicates. It never includes memory text, query text, entity names, queue keys, or source content, but it can include configured filesystem paths and source locations. Use the strict audit below—not plain audit—as a closed fleet or monitoring contract.

While the configured store runs, it atomically publishes a coalesced metadata-only snapshot at .index/runtime.json (plus a 30-second heartbeat). audit uses that snapshot for queue capacity/backlog/high-water/drain/failure/discard counts and embedding/LLM call counts since that store start. It marks a closed, dead-process, invalid, or older-than-90-seconds snapshot as stale. Monetary cost, tokens, and search percentiles remain null unless another telemetry surface records them; audit does not guess them from memory content.

search uses the same recall path as the MemoryRecall tool. When local semantic embeddings are configured but unavailable, it prints a warning and falls back to FTS-only recall instead of pretending semantic search succeeded.

The CLI’s today and show <date> commands remain local operator inspection: they print raw daily Markdown and are not the model-facing chronological API. The configured host instead offers bounded MemoryJournal pages for Lite, Journal, and BuJo. Validation reports local chronology as supported or disabled when recallTool.enabled is false. A restrictive policy that mentions MemoryJournal while its memory capability is absent also reports that mismatch; no state is represented as a successful empty journal.

For an existing running agent, stop it before planning so the reviewed snapshot stays stable. Use a new private plan path outside the memory root each time; prepare refuses to overwrite a previous plan. Keep plans and backups private.

Terminal window
mono-agent stop
mono-agent memory curate prepare --plan ./curate-plan.json --limit 120
mono-agent memory curate review --plan ./curate-plan.json
# Inspect the private plan; accept only proposals you intend to apply.
mono-agent memory curate review --plan ./curate-plan.json --accept drop:generic-advice
mono-agent memory curate apply --plan ./curate-plan.json --json
mono-agent memory audit --strict --json
mono-agent start

If apply fails, check its reason and restored result before restarting. If recovery could not be verified, leave the agent stopped. To undo a successful apply, stop the agent and run mono-agent memory curate restore --backup <dir> using the exact backup path returned by apply, before any intervening store write; audit again before starting. Restore refuses a changed store.

Memory is not a task list: task lifecycle belongs to the agent’s task tools, and automatic capture no longer writes open tasks. Stores that already hold open task lines ([ ]) can convert them with curate prepare --tasks-to-notes. The pass is rule-based and calls no model. It proposes one retype for every open task line, and apply changes only that line’s type to note (–). The id, text, date, salience and labels stay, so nothing is re-embedded beyond the usual post-apply rebuild. Done, migrated, scheduled, dropped and superseded tasks are untouched.

  • --before <date> limits the pass to tasks created before that ISO date (UTC midnight) or instant. An impossible calendar date such as 2026-02-30 is a usage error, not a later day.
  • --capture-only limits it to lines minted by automatic capture (C- ids), which leaves older or operator-written tasks as they are.
  • --dry-run reports the counts (openTasks, proposed, outsideWindow, notCapture) without writing a plan.

Proposals start not accepted. Accept them with review --accept retype:* and reject single lines with --reject id:<memoryId>. The pass is its own plan: it cannot be combined with --limit, --select, --owner-backfill, --link-people or merges. A plan holds one proposal per line, so a retype can never meet a drop of the same line. Apply rechecks that each accepted line is still the same open task, and curate restore undoes it like any curate apply.

memory labels --limit N reads up to 1000 indexed labels per invocation (default 200); truncated means more remain. It is read-only and safe while an agent runs.

memory curate prepare --plan <private-file> reads at most 8192 selected live canonical lines (120 by default). The default --select recent,repeated,risky,oldest reserves bounded shares for recent lines, near-duplicates, credential/instruction-like lines, and the oldest lines; --select oldest restores the former oldest-first order. A custom comma-separated mix uses only its named buckets; prepare and dry-run report selected counts per bucket. Mixed selection caps the scanned live inventory at 65536 lines and fails explicitly above that bound; --select oldest keeps only its bounded oldest slice even on larger stores. Model-free --limit 0 calls no LLM: besides operator merges and owner backfill it proposes deterministic coarse fact labels for lines already associated with a person, at most 1024 per pass and within the private plan size cap, walking oldest first (or newest first with --select recent; other buckets are rejected). Lines that already carry a fact for that person are skipped, so repeated prepare/apply passes cover a large store; prepare reports coarseLabels.more while lines remain. A line that cannot form a valid label becomes an invalid-label discard instead of failing the run. Prepare adds bounded adjacent-line and exact-name graph hints from the same store only, and sends batches of 12 to the configured memory LLM or an explicit --model provider:model override. --dry-run reports estimated calls/tokens without calling the model or writing a plan. Monetary cost is unknown when there is no trustworthy price. A successful prepare creates a new owner-private 0600 JSON plan outside the memory root containing original lines, proposals and source/root fingerprints; treat it as private memory data. Recheck the proposal count and examples with review before making changes. A checksum binds immutable proposals while permitting only accepted edits; other plan edits are rejected. Proposal defaults are rejected; review --accept drop:generic-advice,label:* flips matching booleans in one step, --reject id:<id> wins over a matching category, and manual editing of accepted is supported. Model output cannot authorize user attribution, a verified lesson, a legacy preference scope or identity. Curate has no user turn, so every label it proposes is an assistant-inferred person fact (an owner fact only on an explicit user/owner line); it never mints user-stated labels, preferences or lessons. A rewrite keeps preference and lesson labels, and each existing fact label exactly as stored while the new text still names its subject (and states its structured value), dropping it otherwise; it never re-derives or downgrades attribution, and a legacy relationship fact becomes a coarse fact on the same person with the same attribution. Re-prepare curate plans created by an older version. Agent-host curation calls use strict structured output (a single completion plus its structured-output finalization, at most three model turns) and no tools, MCP servers, process jobs, or agent-root lease; text-only providers may return either a proposal array or its { "proposals": [...] } wrapper; a running agent’s root lease does not block read-only prepare. Each batch gets one retry for transport/model/JSON-envelope failures, then its lines are discarded as model-error or invalid-response and later batches continue. Authentication/configuration failures abort without writing a plan, and so does a failed first model batch or two consecutive model-error batches before any batch has succeeded; an unavailable endpoint cannot yield an all-discarded success. Once a batch has succeeded, later failed batches are discarded and the run keeps its paid work. Individually invalid suggestions are discarded with bounded validation reason codes (action, fields, reason, text, label, merge, or generic proposal) listed in the private plan and review, while valid siblings survive the paid run. Prepare reports discard counts by reason without model error text. Both estimate and prepare report counts of canonical lines skipped as raw host observations, unstructured bullets, missing-identity bullets, legacy-source records, or terminal bullets; raw host observations excluded by rebuild are never sent to the model, even when they precede the selection limit. Keep durable user facts, dated values, plans and user-specific assistant findings; drop raw turn-log envelopes, process chatter and generic advice. Rewrites are only for supported attribution or date repairs, not shortening or guessing truncated text. Apply still strictly refuses any invalid or stale selected proposal before mutation.

Stop the agent before apply. A rejected-all plan is a no-op; otherwise apply rechecks each accepted source at its original file and line (including status, text, date, and references) and validates merges against the current graph before backup. Unrelated captures and status changes do not stale the plan; newly added lines are not curated. The plan’s source fingerprint records the preparation snapshot, while the current canonical fingerprint binds the durable backup and mutation. Plans prepared before status-pinned sources were introduced must be prepared again. Apply refuses selected IDs absent from the active index before creating a backup. If a transaction fails, the error retains its original cause internally; the CLI reports only a bounded tool-owned reason when safe to do so, without echoing memory text. Apply takes a durable verified root backup, uses forget semantics for drops, keeps/clears/replaces labels with rewrites, and reprojects from canonical daily/graph sources after accepted same-type exact-name entity merges. It seals only after safe rebuild and health/parity checks; any failed transaction restores the original tree. restore --backup requires the exact post-apply tree and refuses intervening changes. A backup is root-bound and participates in the same managed retention budget as other stopped-store backups. This is a one-time operator operation, not an automatic capture or background agent task. No external conversation or second memory store is used as context; configured capture.focus/only only guide the model’s suggestions (not a host-enforced filter).

Capture can record one real person or thing under several entity ids. memory entities --duplicates is read-only and lists every case-, accent- and whitespace-folded name held by more than one id, busiest first, with each id’s type and association count (--limit N, default 20; --json). It prints entity ids and names, so treat its output as private.

Model merges stay narrow: same type, same folded name, supported by the line. The operator can decide more. curate prepare and curate review accept --merge <fromId>=<toId> (repeatable) and --merge-file <file> (one fromId=toId per line, # comments). These operator merges may join different names. They may join different types only with --allow-cross-type. They enter the private plan pre-accepted and are listed as merge:operator; review --reject id:<fromId> or --reject merge:operator turns them off. prepare --limit 0 prepares only operator merges and makes no model call. Several ids may merge into one target; chains (a=b, b=c), unknown ids, merges that would turn a graph relation or a relationship fact label into a self-relation, and merges that would move a person fact label onto a non-person id are refused before any backup. References and labels that become identical on one line are collapsed to one. Apply then uses the same checks, backup, root swap, rebuild and restore as every other curation. Merged ids disappear. Their associations, relations, daily references and fact labels move to the target, and the target keeps its name.

Owner turns use one canonical host-owner id, person:owner. Merge any older owner ids into it. Apply creates person:owner if capture has not yet minted it; no other target may be missing. Only host-verified owner turns and operator merges bind person:owner: on any other turn capture drops a model-emitted person:owner entity, its relations, references and labels. When capture sees several known ids with one name, it marks the most-associated id as the one to reuse.

curate prepare --owner-backfill scans live canonical lines and proposes a person:owner association only when an existing structured fact label explicitly names person:owner, or an agent-scoped preference label is attributed user-stated. This language-neutral, model-free scan never guesses a subject from legacy prose. Newly captured owner statements are linked at capture time; unlabelled older statements require separate operator review. The plan records each line’s id, text hash and reason (owner-label); proposals start accepted and can be rejected in review with --reject associate:owner or --reject id:<memoryId>. For compatibility, prepare still reports notProven and bare counts as zero. A drop in the same plan wins over an owner association. Apply refuses changed, unqualified, dropped or already-linked lines before backup, and preserves name-derived links during the root swap and rebuild. Re-prepare plans created under the former prose grammar; they cannot authorize associations after this change.

Coarse person labels are only derived for lines that are already associated with a person. Older lines often name a person without that association. curate prepare --limit 0 --link-people scans every live note and event line (not tasks, not credential-like lines) and proposes an associate:person-name link to every person: entity whose full display name the line contains, as a whole word with the same capitalisation. Matching uses only the entity names already in the graph; there are no word lists and no grammar rules. Only proper names count: every word capitalised, letters and inner hyphens only, at most four words. A first name alone does not link to a longer display name. A name held by more than one person entity, after the plan’s operator merges, is ambiguous and never linked. A name used as a line-leading speaker label (Name: ..., as in a pasted log) is not a mention. person:owner is never linked this way, not even through a name the same plan merges into it; owner links come from --owner-backfill.

No model is called. Links enter the plan pre-accepted. review --reject associate:person-name or --reject id:<memoryId> (every link of that line) turns them off. Lines already associated with that person, counting operator merges in the same plan, are skipped, so repeated passes are idempotent. Each daily file is parsed once, a pass proposes at most 1024 links (a line’s links stay in one pass unless the line alone names more people than that; the rest then come in the next pass), and the whole plan must still fit the private plan size cap. prepare reports counts only: live, linkedBefore (lines with any person association), alreadyLinked, proposed, newlyLinkedLines, ambiguousNames, ambiguousLines, and more when another pass is needed. A dropped line wins over its person links, as for owner associations. Apply refuses a link whose line changed, no longer names that person or is no longer eligible (also after a rewrite or an operator merge in the same plan), or is already linked, before any backup. It then uses the same backup, root swap, rebuild and restore as every other curation. Coarse labels for the new links come from the next --limit 0 prepare.

memory forget is an operator-selected cleanup path, not an LLM classification pass. Put at most 32 existing memory ids, one per line, in the ids file. prepare reads only canonical BuJo sources, rejects missing, duplicate, or already-terminal ids, and creates a new 0600, single-link JSON plan outside the memory root. The plan contains ids and binding metadata but no memory text. Its root fingerprint, canonical source fingerprint, reason slug, timestamp, and checksum make stale or accidentally edited plans fail closed. The checksum is edit detection inside the trusted same-user operator boundary; it is not authentication against another process running as that same OS user.

Run apply only after stopping the configured agent. A package-owned stopped-store coordinator acquires the authoritative memory-writer lease, finishes any prior durable recovery, rechecks the plan and active managed index, and then makes a fully fsynced, verified sibling backup before the first semantic change. A stable sibling transaction record blocks every normal writer until apply or recovery commits. Each selected item uses the ordinary durable migration-forget transaction: its canonical bullet becomes dropped, the exact terminal timestamp enters replay authority and SQLite, and a safe managed rebuild refreshes index and graph projections. No chat LLM is called; the configured embeddings provider is used by the bounded durable update and rebuild. JSON and human output contain only metadata, counts, fingerprints, plan/backup paths, and fixed failure codes—never memory text or raw package errors.

If apply fails after the transaction record exists, the coordinator closes the index, restores the complete snapshot, verifies canonical/index parity and its tree fingerprint, and reports whether recovery succeeded. A process death leaves the fsynced transaction and deterministic backup discoverable; normal store startup refuses until a later apply/restore invocation completes recovery. An explicit restore prevalidates the snapshot, rechecks the exact post-apply tree at the commit boundary, then atomically renames the current root into retained quarantine and consumes the sibling snapshot as the restored root. It never makes a third full copy. A failed activation rolls the quarantine back; an unverified quarantine is never deleted. Only the exact active SQLite coordination files are checkpointed and removed while the writer lease is held. Every unrelated file—including arbitrary names ending in -shm or -wal—participates in freshness and blocks overwrite.

The app’s startup and hourly artifact-retention sweep uses one shared three-slot budget for completed forget backups, completed import backups, automatic capture-clock-repair backups, and conventional .mono-agent/operator/forget-* snapshots; it is not three backups per operation type. Snapshots also expire after 30 days. The sweep covers root-bound managed siblings such as .mono-agent/.memory-forget-backup-*, .mono-agent/.memory-import-backup-*, and .mono-agent/.memory-capture-clock-repair-backup-*, shares the stopped-store maintenance lease, defers while recovery is pending, never follows symlinks, and inherits artifacts.memoryRetention.dryRun. Before deletion it atomically renames each selected directory to another reserved retention name, so an interrupted sweep leaves a claim that the next sweep can discover and finish. Copy any backup that must outlive this rollback window outside every reserved managed backup or retention-claim name.

Portable memory bundles: export and import

Section titled “Portable memory bundles: export and import”

memory export and memory import move a BuJo corpus between agents — for backup and machine migration, or to seed one agent from another. Both are built-in BuJo only.

The bundle is a directory, not an archive, so no archive dependency enters the runtime; compress or copy it yourself. It contains manifest.json plus a source/ tree shaped exactly like a memory root, holding only the canonical corpus: dated daily/*.md (including the supported root-legacy layout), graph.jsonl, and .replay-projection-v1.json. Managed generations, capture intake and outbox, and the derived index.md/future-log.md are never exported. --include-extras copies audit/, monthly/, and legacy/ alongside for the operator; import never reads them, and monthly/ in particular is excluded because a pending migrate decision replayed into another store would wedge its migration protocol.

Export preserves each file’s permission mode and modification time at millisecond precision; both values are committed by the bundle tree fingerprint. Move a bundle with an attribute-preserving copy such as cp -a or rsync -a. A plain recursive copy can change those values and make verification fail. If an archive is required, use a pax-capable tar in pax format and preserve modes and times when extracting: traditional tar formats cannot represent subsecond mtimes, so they can destroy the millisecond identity the manifest verifies.

Because source/ is root-shaped, verifying a bundle is literally “parse it as a memory root”: the same canonical fingerprint and the same rebuild planner run against it unchanged.

Export does not require stopping the agent. It takes no writer or maintenance lease and never opens SQLite. Every canonical read is identity-pinned and re-checked after reading, so a torn read is structurally impossible, and consistency is proven by fingerprinting the store before and after the copy and re-deriving the canonical fingerprint from the copied bytes. A racing write is retried up to three times before failing closed. Export refuses unreplayed durable work unless --allow-pending (which stamps pendingWork into the manifest), and refuses a destination inside the memory root. Siblings named <bundle>.tmp-<pid>-<uuid> are reserved export staging paths. A later export reclaims one only when the name is exact, its PID is proven dead, and its owner-private tree passes bounded type, ownership, device, depth, entry-count, byte-count, and inode checks; live or ambiguous paths are preserved.

Import merges. Restoring into an empty store is simply the degenerate case, and importing a store’s own bundle is a no-op. Memory ids are deduplicated by exact bullet bytes, which makes re-importing the same bundle idempotent. A differing id is a genuine conflict, never evidence of the same fact — capture ids are sha256(runId) plus an ordinal and the rest are ULIDs — so the default is --on-conflict fail; skip keeps this store’s version and drops incoming graph associations plus replay terminals, supersedes, or threads whose endpoints reference the rejected version. Ids are never renamed, because an id is a foreign key from every graph association and all three replay entry kinds. Imported bullets always land in a dated daily/YYYY-MM-DD.md (or root-legacy YYYY-MM-DD.md); a non-dated name would index fine but could never afterwards be migrated or forgotten.

Entity slugs collide by design. --entity-conflict target (the default) keeps this store’s curated name/type/summary and reports what it discarded; source re-asserts the incoming record through the append-log fold so the earlier createdAt is preserved rather than regressing. Relations and associations are set-unioned, and association provenance is carried verbatim.

Replay merging is delegated to the replay authority itself, so every lifecycle conflict already fails closed: a conflicting authority for the same key, a duplicate supersede destination, a terminal/supersede topology conflict, a supersede cycle that exists only after the union, thread fan-out above five, and the entry and byte caps.

One consequence is easy to miss and is therefore measured and gated. The graph projection derives a legacy-name-match association for any memory that has no canonical association but whose text contains a unique entity name. Importing an entity can therefore attach it to memories you never imported — and importing a second entity whose normalized name collides with an existing one disables that whole name group, removing associations this store previously derived. Both are deterministic, so neither appears as a rebuild failure. prepare reports the added and removed counts, and any removal requires --accept-derived-association-drift.

Treat every received bundle as untrusted input until import prepare completes successfully; do not bypass its owner, mode, identity, size, manifest, and fingerprint checks. Prepare does not mutate the memory root, but it does create the requested plan file. Apply is intentionally side-effecting: it creates a retained sibling backup, changes canonical memory and the managed index, and sends the complete merged corpus to the configured embeddings provider for re-embedding, which may involve network transfer and provider cost. --include-extras content is for manual operator inspection only and is never consumed by import.

Run apply only after stopping the configured agent. It re-verifies the bundle, recomputes the merge, and requires the identical merge digest before touching anything, then makes an fsync-verified complete sibling backup. Canonical writes are ordered daily, then graph, then replay; replay is last because a published projection has no shrink path and a projection whose endpoints do not resolve makes the corpus permanently unrebuildable. A safe managed rebuild then re-embeds the whole corpus.

Embedding identity is advisory. The rebuild re-derives every vector under this agent’s configured provider, so a 768-dimension Ollama bundle imports cleanly into a 1536-dimension OpenAI agent. The manifest’s embeddingModel/dimension are provenance only and never gate an import.

Because import changes canonical sources, the post-import manifest advertises no rollback generation — exactly as after forget apply. The undo is memory import restore --backup <dir>, not memory rollback. Import backups are named .<root>-import-backup-<digest> and consume the same combined three-newest/30-day retention budget as forget backups. Copy a backup intended for longer retention outside the reserved managed names.

A canonical-only bundle is lossless for everything markdown owns and lossy for everything only SQLite held. Because import ends in a full rebuild, the first four rows below are reset for the entire destination corpus, not just the imported memories:

Not carriedWhere it livedEffect
accessCount, lastAccessedAtSQLite onlyReset to 0 / cleared
tagsSQLite onlyEmptied (nothing in the BuJo path sets it)
validFromSQLite only; absent from the replay projectionNot reconstructable
source.sessionSQLite only, Journal pathLost
content_hashesSQLite, Journal-onlyRecomputed; must be empty for BuJo
VectorsSQLiteRe-embedded for the whole corpus
.capture-intake, .capture-outboxfilesystem protocolNot merged; both stores must be quiet

Preserved: id, type, status, text, salience, insight flag, createdAt, dueAt, refs, the validTo/supersededBy/supersededAt lifecycle and thread/supersedes/supports edges carried by the replay projection, and collection. Bullets are copied as their verbatim markdown blocks rather than re-serialized from records, which is why refs survives.

Imported memories are not individually stamped with their origin: source.file/source.line are recomputed against the destination, and adding a bullet metadata key would be silently erased the first time anything rewrites that day’s file. Keep the bundle’s manifest.json if you need to prove provenance later.

Writable BuJo startup also owns one narrow provider-free compatibility repair. Supersession time must be at or after both the superseded memory’s creation and the replacement’s creation, and the replacement must live in the matching dated daily file. A historical completed-turn retry could reuse its earlier admission time after a later-created target already existed. The resulting retained pending intent could partially commit that impossible chronology to Markdown, replay authority, and SQLite, after which strict startup correctly refused the mismatch.

Startup now recognizes only a retained, run-owned pending intent with exactly that backward-clock defect. Before opening the normal writer it takes the maintenance and writer fences, creates a whole-root sibling backup, corrects the replacement/lifecycle clock and daily path across every authority, replays the intent, and validates canonical, replay, and SQLite parity. The path calls neither embeddings nor a chat model. Its sibling transaction resumes after a process death; an in-process failure restores the exact original tree. A prepared backup left before transaction publication is safely reused. Read-only stores never run the repair, and any other invalid or ambiguous intent remains a hard failure for operator review.

mono-agent memory audit --strict --json is the closed, provider-free health contract. It makes no embedding, chat-model, Ollama, LM Studio, or OpenAI request. For the local backend it takes a bounded, snapshot-coherent view of managed identity, SQLite integrity and metadata, FTS/vector coverage, canonical source parity (including BuJo’s exact replay projection), rollback-source freshness, durable completed-turn intake, capture outbox, temporary artifacts, and the runtime snapshot. SQLite still requires the native modules built for the Node runtime that invokes the command; an unavailable native module reports unknown rather than leaking the loader error.

Fresh durable work is in_progress, but it cannot remain successful forever after its owner disappears. A due intake item with no active retry, or a published capture intent awaiting replay, becomes work_stalled after the same 90-second grace used for runtime staleness. The timestamps and stability digests used for that decision remain private; the public report carries only the stable issue and aggregate counts. A live/fresh Journal write lock is similarly distinguished from a stale or malformed owner without mutating the lock during audit.

The JSON object has exactly these fields (the mode field exists only for backend: "bujo"):

FieldContract
schemaVersionInteger 1.
backendbujo or none.
modeFor bujo only: lite, journal, or bujo.
statushealthy, in_progress, degraded, unhealthy, unknown, or not_configured.
checkedAtISO-8601 instant for this audit.
issuesCanonically ordered subset of the closed issue-code list below.
countsExact non-negative integer keys: pending, due, dead, outbox, temporary, memories, vectors, missingVectors.

The statuses and process exit codes are deliberately different dimensions:

StatusMeaningExit
healthyNo issue code is present.0
in_progressDurable or snapshot-coherent work is actively pending, with no degraded/unhealthy/unknown condition.0
degradedDead letters, stalled durable work, or missing, stale, or invalid runtime telemetry needs attention.1
unhealthyManaged identity, database/index, canonical source, intake/outbox, or temporary-artifact integrity failed.1
unknownThe local database/native module or health check could not be inspected.1
not_configuredNo memory backend is configured (backend: "none"; no mode).0

For backend: "bujo", classification uses this exact precedence:

  1. database_unavailable, native_module_unavailable, or health_check_failed → unknown.
  2. Any of manifest_missing, manifest_invalid, configured_identity_mismatch, database_missing, sqlite_integrity_failed, metadata_mismatch, fts_mismatch, vector_mismatch, orphaned_rows, canonical_mismatch, canonical_invalid, intake_invalid, outbox_invalid, or temporary_artifacts → unhealthy.
  3. dead_letters, work_stalled, runtime_missing, runtime_stale, or runtime_invalid → degraded.
  4. Otherwise, mutation_in_progress, intake_pending, or outbox_pending → in_progress.
  5. No issues → healthy.

CLI misuse—including using --strict on any subcommand except memory audit—exits 2. The complete closed issue vocabulary is:

manifest_missing manifest_invalid configured_identity_mismatch
database_missing database_unavailable native_module_unavailable health_check_failed
sqlite_integrity_failed metadata_mismatch fts_mismatch vector_mismatch
orphaned_rows canonical_mismatch canonical_invalid mutation_in_progress
intake_invalid intake_pending dead_letters outbox_invalid outbox_pending
work_stalled temporary_artifacts runtime_missing runtime_stale runtime_invalid

The strict report is metadata-only by construction. It never publishes paths, filenames, record or run ids, model text, payloads, raw provider/native errors, or arbitrary extra fields. An absent backend reports not_configured with a closed empty shape.

BuJo replay projection and explicit legacy adoption

Section titled “BuJo replay projection and explicit legacy adoption”

The BuJo tier keeps one owner-only canonical replay authority at memory.path/.replay-projection-v1.json. It exactly describes the replay-owned thread edges, supersession lifecycle/edges, and migration-forget terminal timestamps that cannot be reconstructed from daily markdown and graph.jsonl alone. The file is metadata-only: ids, timestamps, thread weights, authority kinds, and content-free authority digests, never memory text or model output. It must be an owner-owned, single-link regular file with mode 0600 and exact canonical JSON.

Strict audit compares this entire projection with SQLite. A plausible raw SQLite edge or lifecycle update that has no exact sidecar authority is still RED as canonical_mismatch; malformed/unsafe canonical bytes are canonical_invalid. Missing plus nonempty legacy replay state is never auto-blessed by startup, audit, or rebuild. Lite and Journal do not consume the BuJo sidecar and reject replay-owned lifecycle/edges in their databases. Interrupted atomic replay-sidecar publication files are also strict temporary artifacts: they increase counts.temporary and report temporary_artifacts instead of being ignored, adopted, or interpreted as canonical authority.

For the one legacy case where a managed generation or legacy unmanaged memory.db legitimately predates the sidecar, make the trust decision explicitly over SSH. Use this flow only after strict audit or a refused rebuild identifies a missing projection beside nonempty replay state:

Terminal window
cd /path/to/agent
mono-agent stop
mono-agent memory adopt-replay --json
mono-agent memory rebuild --json
mono-agent start
mono-agent memory audit --strict --json

adopt-replay is an explicit trust-on-first-use operation. It requires the configured built-in mode: "bujo", a stopped store, a missing sidecar, an exact canonical non-replay base, and a SQLite BEGIN IMMEDIATE fence plus logical digest under the memory-root writer lease. It supports either a managed active BuJo generation whose semantic identity matches config or a legacy unmanaged BuJo memory.db whose identity can be pinned safely. The database and every present SQLite sidecar (-wal, -shm, or -journal) must be current-user-owned, single-link regular files with mode 0600; adoption rejects unsafe family members instead of chmodding or following them.

The durable state may contain a bounded set of disjoint capture intents and completed receipts plus at most one migration marker. Adoption proves that any already-applied SQLite replay rows are an exact subset of that durable authority before it binds the full projection. An unexplained row, overlapping mutable capture plans, or a mutable pending capture beside a pending migration fails closed. Immutable completed capture receipts may coexist with a later pending migration. The mandatory immediate memory rebuild completes mutable attested work without another chat-model or embeddings call and removes retireable markers; a retained completed receipt remains until its intake item resolves. The subsequent candidate build still uses the configured embeddings provider, in bounded batches, before it activates the managed generation. Do not run start, capture, migration, or any other writer between adoption and rebuild.

Adoption publishes the sidecar without overwriting existing authority and retires an advertised BuJo rollback before the canonical change. A Lite or Journal rollback is outside the replay source domain and remains advertised. Its success JSON contains only backend, mode, status, counts for terminals/supersedes/ threads, authorityDigest, and rebuildRequired: true; it does not reveal memory text or paths. All failures use stable closed codes/messages. JSON mode emits one parseable metadata-only failure object on stdout; human mode emits the same fixed code/message on stderr. Neither includes memory/model text, ids, paths, marker/database details, or arbitrary underlying errors.

Adoption failure codeFixed meaning
replay_adoption_usageThe command/flags do not match the documented adoption invocation.
replay_adoption_config_invalidThe mono-agent configuration could not be validated.
replay_adoption_requires_bujoConfigured memory is not built-in BuJo with embeddings.
replay_adoption_agent_runningThe configured agent is still running.
replay_adoption_failedA closed operational precondition or integrity check failed; keep the agent stopped and inspect strict health.

Adoption does not repair, reinterpret, or prove the historical meaning of raw SQLite state—the operator is accepting that meaning. The required follow-up rebuild fingerprints the sidecar with the other BuJo canonical sources, finishes any attested pending protocol without repeating paid provider work, then uses the configured embeddings provider for the normal semantic candidate build, reprojects the authority into a fully validated generation, and preserves it exactly. Future capture/migration projection changes retire an affected BuJo rollback before publication; daily-source changes still retire a rollback from any tier. Thus rollback is never advertised against stale replay authority. For an empty or new root, do not adopt: ordinary rebuild initializes the exact empty projection safely.

Completed-turn intake inspection and recovery

Section titled “Completed-turn intake inspection and recovery”

The config-aware intake commands operate only on the local Lite/Journal/BuJo intake. inspect is read-only and may be used while the agent is running. It returns only the stable 64-character item id, state, admission timestamp, attempt/revision, due flag, and bounded failure category (model_output, provider, or processing), plus aggregate state counts. It never returns runId, conversationId, summary/capture text, payload hash, filesystem path, or raw model/provider error.

retry and resolve acquire the memory writer lease and refuse to run while the trace registries show a live process for the same canonical config. Stop the agent first:

Terminal window
mono-agent stop
# With no id, retry all dead letters and make all delayed pending items due now.
mono-agent memory retry --json
# Or retry one item selected from `inspect`.
mono-agent memory retry <64-character-id> --json
# Explicitly retire one pending/dead item without claiming capture succeeded.
mono-agent memory resolve <64-character-id> operator_discarded --json
mono-agent start

retry resets selected dead letters to pending and makes selected delayed pending work immediately due; processing resumes after the store starts. A successful no-op still exits 0, so inspect changed/retried in JSON. resolve is an explicit abandonment: it writes an operator_resolved receipt and preserves the permanent id/payload commitment so the same completed run cannot be admitted as new work later. Its reason must be a 1–64 character lowercase slug that begins with a letter/digit and then uses only letters, digits, underscores, or hyphens. It refuses an item with a retained semantic plan because that recoverable commit must finish first. A missing/already-resolved id returns changed: false, so automation must check the field rather than the exit code alone.

mono-agent validate (the app doctor) checks both the configured identity and liveness. A managed generation whose tier, embeddings provider/model, or dimension differs from the current config is an error immediately—before any provider/network probe—with the exact active and configured identities plus the stop/rebuild/validate sequence. Invalid/unavailable native SQLite bindings and malformed or missing managed metadata are also errors. Operational provider failures remain waiting, so they do not flip the overall result. Read the Memory section. Downstream validation with mono-agent validate --consumer <path> never creates a memory root: a missing Lite root is a warning, while missing Journal/BuJo managed authority is an error.

Only Lite may remain unmanaged. Journal and BuJo always require the managed .index/manifest.json authority; a manifestless, deleted, or corrupt managed identity is an error and never falls back to a legacy memory.db. Fresh init creates the empty managed Journal/BuJo generation without indexing content, but interactive guided readiness first proves the selected embeddings service with a fixed non-user request and exact dimension. Flag/non-TTY scaffolding does not perform that probe or claim readiness. Init deliberately leaves every pre-existing memory root untouched. For an existing or damaged root, stop the agent, run mono-agent memory rebuild, and validate again to establish or repair the managed generation.

For scripting, mono-agent validate --json writes exactly one top-level JSON object with ok: boolean and sections (plus preset when requested). It emits no ANSI or human prose on stdout and exits 0 exactly when ok is true; errors exit 1.

Coverage: cli.

TierChecks performed
liteMemory root creatable and writable.
journalRoot writable + configured embeddings identity and provider-specific live probe.
bujoAll journal checks + explicit capture-LLM config (agent-host or Ollama) + consolidation cadence.

The checks run in this order:

  1. Managed generation identity and native availability — dynamically loads the built-in memory implementation, rejects ABI/native-loading failures, validates managed manifest authority, and compares the active tier/provider/model/dimension with the configured identity. A mismatch tells you to mono-agent stop, run mono-agent memory rebuild, and validate again.
  2. Memory root writable — confirms memory.path is creatable and writable.
  3. Provider-specific typed discovery — Ollama enumerates /api/tags and requires /api/show capabilities to include embedding; LM Studio requires an exact type: "embedding" entry from /api/v1/models and uses its key. OpenAI keeps its credential/config checks.
  4. Real embeddings probe — Ollama calls /api/embed; LM Studio calls /v1/embeddings. The selected provider must return one non-empty finite numeric vector with the configured dimension. A declared LM Studio apiKeyEnv whose variable is missing reports waiting; it never retries keyless. No provider falls through to another provider.
  5. Chat model pulled (bujo only) — only when memory.llm.provider is ollama; probes the chat endpoint and checks the chat model against its /api/tags. agent-host chat LLMs are not checked against Ollama.
  6. Consolidation cadence (bujo only) — reports the consolidation cron expression and whether the scheduler will run for the configured bujo tier.

A healthy bujo report looks like:

[ok] memory.mode bujo
[ok] consolidation 0 */2 * * * (auto)

Embeddings checks target only the configured backend. Selecting LM Studio never causes an Ollama request, and provider failure is not a cross-provider fallback. With llm.provider: "agent-host", the Ollama chat-model pull check is skipped even when embeddings use Ollama or LM Studio. See Embeddings for the provider matrix and Consolidation for the auto-scheduler.

The validator’s behavior depends on memory.llm.provider. There are two providers, and which one you pick changes both what runs BuJo capture and what validate probes. The tier contract is strict: journal requires embeddings, and bujo requires both embeddings and memory.llm; an invalid tier never silently downshifts.

Fieldollamaagent-host
provider"ollama""agent-host"
modellocal model string, e.g. qwen3.6:latestSDK runtime ref, e.g. openai-codex:gpt-5.6-terra
endpointOllama URL (default http://localhost:11434)rejected — Ollama-only
validate chat-model checkyes (probes /api/tags)no

Configure these fields in the JSON memory.llm block.

{
"memory": {
"mode": "bujo",
"path": "./.mono-agent/memory",
"writeMode": "append-host-summary",
"embeddings": {
"provider": "ollama",
"model": "nomic-embed-text:v1.5",
"endpoint": "http://localhost:11434",
"dim": 768
},
"llm": {
"provider": "ollama",
"model": "qwen3.6:latest",
"endpoint": "http://localhost:11434"
}
}
}

The agent-host provider runs memory LLM passes (one batched memory/graph extraction, one small review only when the turn has review-eligible lines, and, only when close existing candidates need classification, one batched reconcile) on their own dedicated SDK runtime built from memory.llm.model — independent of the channel runtime — so there is no separate local chat model to pull. The model is a runtime reference. Do not set endpoint — it is Ollama-only and rejected here.

{
"memory": {
"mode": "bujo",
"path": "./.mono-agent/memory",
"writeMode": "capture",
"embeddings": {
"provider": "openai",
"model": "text-embedding-3-small",
"apiKeyEnv": "OPENAI_API_KEY",
"dim": 1536
},
"llm": {
"provider": "agent-host",
"model": "openai-codex:gpt-5.6-terra"
}
}
}

memory.llm.timeoutMs sets the per-call timeout for the in-app memory LLM — each per-turn capture call (one extraction + at most one review + at most one reconcile). Its default is 60000 and the value is bounded 1000–600000 ms. Raise it when a slow local memory model trips the cap on extraction or reconcile.

There used to be a second default: the removed standalone memory-bujo binary read the same env var but defaulted to 120000. That binary and its migrate path are gone, so only the in-app 60000 default remains.

When the in-app memory LLM does exceed its timeout, the run reports it explicitly — agent-host memory LLM timed out after 60000ms (provider too slow or unavailable) — rather than the generic cancelled it used to surface, so a slow or dead provider is diagnosable from the run record. The provider answer still succeeds, while the already-admitted turn remains pending for durable retry instead of being reported as captured or lost; see Capture & recall.

Safe index generations: rebuild and rollback

Section titled “Safe index generations: rebuild and rollback”

Use the config-aware commands for normal operation:

Terminal window
cd /path/to/agent
mono-agent stop
mono-agent memory rebuild --json
# Optional detailed local accounting (may include paths/source locations)
mono-agent memory audit --json
# Stopped pre-start gate
mono-agent validate
mono-agent start
# Closed automation gate, including live runtime telemetry
mono-agent memory audit --strict --json

rebuild reads the configured tier, embeddings provider/model, and dimension; snapshots the canonical markdown/graph sources plus BuJo’s exact replay sidecar; builds a complete candidate under .index/generations/<generation>/memory.db; validates its schema, exact payloads, complete edge/lifecycle/replay inventory, Journal hash provenance, FTS coverage, vector coverage, and model identity; then atomically switches a small manifest. The replay sidecar participates in the BuJo source fingerprint and is preserved exactly rather than inferred from SQLite. The old active database is retained only through a fresh immutable online-backup generation after tier-exact payload/source parity is proven; repairable lifecycle/source/edge/hash state is normalized on that copy, and Journal may retain its documented recoverable missing-vector backlog. The manifest commits the copy’s complete logical state—including WAL-visible rows and vector blobs—so same-count semantic tampering cannot hide behind an unchanged main-file checksum. When source, tier, provider/model, and dimension are unchanged, every retained vector is additionally compared with the newly embedded candidate (missing Journal backlog rows remain repairable). The first supported daily-source mutation atomically removes any rollback advertisement before changing its source; a BuJo graph/replay-only mutation removes only an advertised BuJo rollback. Normal capture may therefore make memory rollback unavailable immediately instead of leaving a stale advertised target, while a Lite/Journal rollback remains valid across a graph/replay-only change; out-of-band source edits still surface as canonical_mismatch. SQLite writer fences hold the source stable during backup and every soon-to-be-active/retained database stable across final validation plus manifest rename; the fsynced temporary manifest’s identity and exact bytes are rechecked immediately before that rename, and the renamed file is checked again after every durability callback. If canonical source is ahead of a stale index, rebuild still activates the correct candidate but deliberately omits that unsafe rollback instead of stamping it with a current fingerprint. The previous active index remains usable if any step fails before activation. A divergent legacy memory.db remains byte-for-byte in place but is not advertised as rollback; only a parity-compatible legacy database is adopted by online backup.

The running agent must be stopped. The command refuses a matching live process, an active writer lease/SQLite transaction, concurrent source changes, symlinked source paths, or a concurrent manifest change. Journal/BuJo rebuilds can call the configured embeddings provider in bounded batches; rebuild never calls the chat LLM. Rollback swaps already-validated generations and makes no embedding or chat-model request.

rollback is deliberately conservative: its retained generation must match the currently configured tier/provider/model/dimension, its canonical source fingerprint must still match, and its persisted logical integrity commitment must verify. Rollbacks retained by an older build without that commitment fail closed; run one current rebuild first to create a verified snapshot. If the rebuild accompanied a tier, embeddings-provider/model, or dimension change, restore that prior config first, then run:

Terminal window
mono-agent stop
# restore the prior memory.mode / embeddings provider / model / dimension in mono-agent.config.json
mono-agent memory rollback --json
mono-agent validate
mono-agent start
mono-agent memory audit --strict --json

Before switching, rollback tries to snapshot the outgoing current index under the same rules. If that outgoing index is already semantically divergent or corrupt, recovery to the verified target still succeeds but no reverse rollback is advertised.

The logical digest is an integrity/CAS commitment under mono-agent’s owner-only path and writer controls, not cryptographic authentication against the same OS owner deliberately rewriting both the database and its manifest. Across a tier or model change, rollback also makes no provider call to regenerate the old vectors; it protects the fenced prior snapshot instead. Treat coordinated owner edits as explicit operator replacement, not a supported recovery path.

Rebuild output and audit --json report the generation name, indexed count, raw/unstructured/missing-identity/legacy-source/Journal-duplicate skips, source locations that require review, and legacy associations derived by exact unique whole-name matching. BuJo raw audit files are never promoted automatically into the curated index, and no command replays history through a paid chat model.

0.8.0 is the first product-v1 lockstep release published to npm. The immutable 0.7.0 source tag introduced the milestone but was not published. Product v1 is a product milestone, not an npm major-1 claim. This is the complete cutover for an existing local agent, with one backend-specific branch in step 6.

  1. Confirm Node.js meets the supported floor. The repository .nvmrc pins the exact minimum; an existing agent directory may not contain that file, so select the version explicitly there:

    Terminal window
    node --version # must be >= 24.15.0
    nvm install 24.15.0 # only if it is not installed
    nvm use 24.15.0
  2. Stop the old agent before changing its installed packages. Then upgrade the package that already owns the global mono-agent command and confirm the exact published version. create-mono-agent and @mono-agent/agent-app both provide that command, so do not install both globally:

    Terminal window
    cd /path/to/agent
    mono-agent stop
    npm ls -g --depth=0 create-mono-agent @mono-agent/agent-app || true

    If create-mono-agent is listed, upgrade that owner:

    Terminal window
    npm i -g "create-mono-agent@0.8.0"

    Otherwise, if @mono-agent/agent-app is listed, upgrade that owner instead:

    Terminal window
    npm i -g "@mono-agent/agent-app@0.8.0"

    Then confirm the command resolves to the new version:

    Terminal window
    mono-agent --version

    For a new global install, prefer create-mono-agent. To switch package owners, uninstall the currently listed package before installing the other one.

  3. Check or refresh the managed memory skill. Reconcile any operator-modified skill before using --update; the updater also reports and safely retires an exact manifest-owned legacy configuration skill:

    Terminal window
    mono-agent install-skill --project --check
    mono-agent install-skill --project --update
    mono-agent config
    mono-agent validate
    mono-agent restart
    mono-agent web run --loopback

    Edit the config or identity directly, never enter secrets in either file, validate the result, restart, and only then open the browser console (mono-agent web run --loopback).

  4. Confirm that the exact embeddings model is available from the selected provider. For Ollama:

    Terminal window
    ollama list
    ollama pull nomic-embed-text:v1.5 # only if that exact tag is absent

    For LM Studio, load the embedding model, start the local server, and ensure the typed discovery key from /api/v1/models matches memory.embeddings.model. If apiKeyEnv is configured, export that variable before validation; omitting both key fields intentionally selects keyless mode.

  5. Validate the configured folder and read the Memory section. A running provider process is not sufficient if the active managed generation has a different tier/provider/model/dimension, the configured endpoint differs, typed discovery cannot find the exact model, or the real embedding probe fails:

    Terminal window
    mono-agent validate
  6. For the built-in Lite, Journal, or BuJo backend, inspect the stopped store:

    Terminal window
    mono-agent memory audit --json

    Only for a legacy BuJo store, and only when the audit or first rebuild explicitly identifies nonempty replay-owned SQLite state without its canonical projection, adopt that replay state while every writer remains stopped:

    Terminal window
    mono-agent memory adopt-replay --json

    Then build the managed index, verify it, and start the agent:

    Terminal window
    mono-agent memory rebuild --json
    mono-agent memory audit --json
    mono-agent validate
    mono-agent start
    mono-agent memory audit --strict --json
    mono-agent status

    Do not run adopt-replay merely because an audit is unhealthy: it is a trust-on-first-use command for the exact missing-projection legacy case and rejects unrelated drift. If the first rebuild gives that explicit adoption instruction, run adopt-replay and then rerun the rebuild without starting another writer between them.

  7. Verify all evidence routes in the web console or an enabled conversational channel without restarting between messages. For Telegram, send Reply exactly with this token: V1-HISTORY-<unique>, wait for that reply, then ask What did you send in the last message? and confirm the token comes back from active history without a durable lookup. Ask one specific durable question such as Which release color did we choose? to exercise MemoryRecall. On a local tier, call a broad retrospective with an explicit period such as What did we work on from 2026-09-01 through 2026-09-07 in CET? to exercise MemoryJournal; treat its answer as a curated summary. Ask for exact commands/results or interrupted recovery separately and require RunHistory/SessionHistory. These observations are provider behavior checks; prompt string tests alone do not prove autonomous routing.

The strict audit deliberately includes live runtime telemetry. While the agent is stopped it reports runtime_missing or runtime_stale instead of claiming the whole running system is healthy. Use mono-agent validate after a stopped rebuild or rollback, then start the agent and run the strict audit as the closed live gate.

If memory.llm.provider is agent-host, the selected embeddings service is independent: Ollama is needed only for memory.embeddings.provider: "ollama", while LM Studio embeddings use only LM Studio. You do not need an Ollama chat model. If rollback is needed, stop the agent, restore the prior tier/provider/model/dimension if it changed, run mono-agent memory rollback --json, validate, start again, and run the strict audit.

The standalone memory-bujo binary and its compatibility error-deflector have been removed. The env-var-driven, <root>-positional workflow is gone with the bin; managed configuration comes from mono-agent.config.json, and every maintenance operation now runs config-aware from the agent folder.

Coverage: cli.

Removed memory-bujo commandReplacement
rebuild <root> --tier <t>mono-agent memory rebuild — config-aware; run from the agent folder
rollback <root> --tier <t>mono-agent memory rollback — config-aware; run from the agent folder
recall <root> "<query>"mono-agent memory search "<query>"
index <root>Removed with no one-for-one scheduled replacement; the in-app scheduler calls only projection-only store.consolidate()
reflect <root>Removed with no one-for-one scheduled replacement; the in-app scheduler does not run reflection
migrate <root>Removed historical v1→v2 workflow with no current CLI replacement

The config-aware mono-agent memory rebuild / rollback read the tier, embeddings provider/model, and dimension from config, so they no longer need a --tier flag or a positional <root>; see Safe index generations. The removal is recorded in Deprecations.

Terminal window
mono-agent memory profile show
mono-agent memory profile show --json

This BuJo-only read-only view uses the same deterministic selection and budgets as memory.profile.enabled, without embeddings or a model. It is available before enabling automatic injection. JSON includes supporting memory IDs for private inspection; the model payload does not include IDs. The view has no independent edit/restore authority: change supporting canonical memories through reviewed memory curate operations. Profile failures return stable codes only.

Use BuJo’s explicit private curate prepare/review/apply flow to inspect and retype legacy note/event types and labels independently of recall configuration:

Terminal window
mono-agent memory curate prepare --plan semantic-review.json --semantic-review --limit 60 --dry-run
mono-agent memory curate prepare --plan semantic-review.json --semantic-review --limit 60
mono-agent memory curate review --plan semantic-review.json

This explicit model pass needs no recall flag; it is not combined with the model-free --tasks-to-notes or --limit 0 passes. It proposes retype with type: "note" or "event", and optionally a complete reviewed labels list. Absent labels retain current refs; labels: [] removes labels only after operator acceptance. Existing attribution can be retained exactly, never invented or upgraded; new retrospective labels remain supported assistant-inferred person facts. There is no text rewrite or date backfill in this pass. Inspect the source, proposed type and labels in the owner-private plan and accept only supported proposals through the existing review selectors (for example --accept id:<memory-id>). Do not export plan contents.

Apply accepted proposals using the existing stopped-store memory curate apply --plan semantic-review.json command. The fingerprint-bound root-swap transaction creates the same backup and rebuilds the index; existing memory curate restore --backup <backup-directory> restores canonical bytes. Retyping does not change automatic recall policy, label an unknown note or prove its original speaker. Use a private store copy for subsequent experiments.

Review intention dates before enabling expiry

Section titled “Review intention dates before enabling expiry”

Before enabling memory.recall.intentExpiry, stop the configured agent and drain its pending capture under the old policy first. Inspect existing note due= values privately using memory today, memory show <date> and memory search. Use the existing bounded memory curate prepare --semantic-review --plan <private-file> --limit 60, review and stopped-store apply/restore flow to remove unsupported lines or retype actual dated episodes, accepting only supported proposals. Review the whole dated note inventory in bounded passes; a sample is not an audit of unexamined dates. Do not infer intention completion from a passed date, manufacture missing dates, upgrade attribution or rewrite generic supersession validity. This introduces no new curate command and performs no automatic audit, reinterpretation or backfill.

An ambiguous old note date may have been a deadline, not an inclusive validity end. The new policy excludes every dated note from automatic sections, including future dates, but deliberate currentness assumes reviewed inclusive-end meaning. Keep the flag off until that meaning has been checked and ambiguous sources removed/retyped through accepted curate proposals. Plans and backups contain memory text and must stay private; rollback uses the existing exact backup flow.