New map + RAG entries for the vault subsystem; sequence gate, lineage merge, gate diff-base, CI guard, playwright MCP, dispatcher prefilter, backup sidecar, and the 300/100 budget reflected across docs/map, docs/rag, and CLAUDE.md; stale claims fixed (agent-ux 'no extra tools', old budget defaults). No redirects needed — nothing publicly published moved. Co-authored-by: Renn F <rennf93@users.noreply.github.com>
9.6 KiB
Purpose
The Obsidian vault V1: a rebuildable, human-readable DB projection of the org's memory (tasks, journal entries, A2A thread digests) as wikilinked markdown, plus a default-off inbox watcher that turns #roboco-tagged vault notes into board-review intake drafts. Default-off (ROBOCO_OBSIDIAN_VAULT_ENABLED; both compose files arm it true). Mirrors the other default-off engines' shape in one respect (event-driven, best-effort, never a gate) but is structurally different: it never originates delivery work itself — the projection is read-only from the DB's perspective, and the ONE writer-side effect that reaches delivery (the intake watcher) rides the existing board-review path, not a held-artifact queue.
Files
| Path | Role | approx LOC |
|---|---|---|
roboco/services/vault_writer.py |
VaultWriter — pure, DB-free markdown materializer. write_task / write_journal_entry / append_a2a_message / write_agent / touch_task_frontmatter. Every note carries aliases: [<id8>] so a rename never breaks a [[id8|title]] wikilink; existing_narrative reads back an Auditor-authored ## Narrative section so a rebuild never clobbers it. |
371 |
roboco/services/vault_assembly.py |
assemble_task_note_data — resolves a task's project slug, parent, subtasks, and dependencies via the live TaskService/ProjectService into a TaskNoteData for VaultWriter.write_task. Shared by curate_vault and the CLI rebuild; kept separate from vault_writer so that module stays DB-free and trivially unit-testable. |
87 |
roboco/services/vault_intake_engine.py |
VaultIntakeEngine.run_cycle — scans the vault's inbox dir for #roboco-tagged notes, dedupes via vault_seen_notes (path + content-hash), screens the body through injection_guard.screen_external_text, extracts a title/description/action-items via a local-model chat call (deterministic fallback: first heading / raw body / checkbox lines), and opens ONE PENDING board-review draft (source=vault_note, Product-Owner-assigned, team=board) per note. Appends a feedback callout back into the note (best-effort). |
373 |
roboco/vault.py |
python -m roboco.vault {rebuild,relocate} CLI. rebuild re-projects every agent/task/journal-entry/A2A-thread from the DB and materializes .obsidian/ + RoboCo/_meta/ from roboco/vault_assets/ (never overwrites an existing file). relocate <path> moves the tree; grafts RoboCo/ into an existing destination vault without touching its own config. |
237 |
roboco/vault_assets/ |
Packaged templates copied by ensure_vault_assets: .obsidian/ (Dataview, Kanban, graph-group config) + meta/ (dashboard + kanban-board + README shipped into RoboCo/_meta/). |
— |
roboco/foundation/policy/injection_guard.py |
screen_external_text / detect_injection — the shared prompt-injection screen-and-neutralize (data path) and hard-deny (interactive-input path) pattern set, used by both the vault intake watcher and XEngine's mentions poll. |
125 |
roboco/services/gateway/content_actions.py curate_vault |
Server-side do-action: Auditor-only, re-materializes a task's note with the Auditor's narrative filling ## Narrative. Inert (invalid_state) when the flag is off. |
— |
roboco/mcp/do_server.py curate_vault |
Do-tool the Auditor calls exactly once per completed root, POSTing to /api/v1/do/curate_vault. |
— |
roboco/db/tables.py VaultSeenNoteTable |
Dedup ledger for the intake watcher: (note_path, content_hash) — an unchanged note is never reprocessed; an edited one is eligible again. |
— |
Data Flow
PROJECTION (always-on when the flag is armed). Three best-effort event seams fire from existing services, never gating the real action: TaskService._emit_status_transition_audit → _touch_vault_frontmatter patches an EXISTING note's status/team/pr fields in place (a not-yet-materialized note is a silent no-op — full materialization happens at Auditor curation or CLI rebuild); JournalService's entry-write path → _materialize_vault_note writes one immutable file per non-private entry; A2AService.send → _materialize_vault_note appends to a per-thread digest file, deduped per message id via an in-body marker comment. All three import get_vault_writer() lazily and catch every exception — a vault failure is logged and swallowed, never surfaced to the caller.
CURATION (root-completion hook, orchestrator-driven). AgentOrchestrator._dispatch_vault_curation_work (one of the 18 tick dispatchers, gated on obsidian_vault_enabled) reads TaskService.list_completed_roots_pending_vault_curation and calls _maybe_spawn_vault_curation per candidate: an in-memory one-shot guard (_board_dispatched) plus a durable vault_curation_dispatched marker (survives a restart) precede a bindingless Auditor spawn — the root task id is named in the prompt rather than bound as the spawn's active task (the root is already completed, so binding would trip the readiness gate's role-for-status check). The Auditor writes one narrative paragraph and calls curate_vault(task_id, narrative) exactly once; the verb re-resolves the task's parent/subtasks/dependencies fresh via assemble_task_note_data and fully re-materializes the note, filling the ## Narrative section a deterministic write otherwise leaves as _Pending Auditor curation._.
INTAKE (independently-gated inbox watcher). AgentOrchestrator._vault_intake_loop (both obsidian_vault_enabled AND vault_intake_enabled required) ticks VaultIntakeEngine.run_cycle every vault_intake_interval_seconds. Per note under the inbox dir: skip if no #roboco tag (frontmatter list or inline #roboco token); skip if already seen (path + content-hash in vault_seen_notes — an edit changes the hash and re-qualifies); screen the body via screen_external_text (wraps it in an untrusted-content envelope, flags any injection-pattern line inline, never removes content); extract title/description/action-items via a local-model chat call against the SCREENED text, falling back to a deterministic first-heading/raw-body/checkbox-line extraction on any local-model failure; open ONE PENDING task (source=vault_note, Product-Owner-assigned, team=board, confirmed_by_human=True — the board routing IS the start gate) capped by vault_intake_max_open_drafts (rolling) and vault_intake_max_per_cycle (per-tick); append a feedback callout into the source note (best-effort). The draft NEVER starts delivery directly — it takes the identical intake board-review path a chat-confirmed draft would: Product Owner + Head of Marketing review, and only the CEO's approve_and_start hands it to the Main PM.
REBUILD/RELOCATE (operator/CLI, not agent-facing). python -m roboco.vault rebuild walks every agent, then every task (preserving each task's existing ## Narrative via writer.existing_narrative before re-materializing), then every non-private journal entry, then every A2A thread — fully re-projecting from the DB — and materializes the shipped .obsidian//_meta/ assets if absent. relocate <path> moves RoboCo/ into a destination, refusing if the destination already has a RoboCo/ subtree (avoids clobbering another vault's own projection).
Config Flags
ROBOCO_OBSIDIAN_VAULT_ENABLED— master switch; off =VaultWriteris never invoked from any seam,curate_vaultreturnsinvalid_state, andpython -m roboco.vaultrefuses. Config defaultfalse; bothdocker-compose.ymlanddocker-compose.registry.ymlset ittrue.ROBOCO_VAULT_PATH(default/data/vault) — root directory the vault materializes into; bind-mounted in both compose files.ROBOCO_VAULT_INTAKE_ENABLED— independent second switch for_vault_intake_loop; inert unless the master switch is ALSO on. Config defaultfalse; both compose files set ittrue.ROBOCO_VAULT_INTAKE_INTERVAL_SECONDS/ROBOCO_VAULT_INTAKE_DIR/ROBOCO_VAULT_INTAKE_MAX_PER_CYCLE/ROBOCO_VAULT_INTAKE_MAX_OPEN_DRAFTS— cadence, inbox subfolder, per-cycle origination cap, rolling open-draft cap.
Health
The projection side is zero-risk by construction: every seam is best-effort and DB-free from the writer's perspective (VaultWriter takes plain dataclasses, never touches the DB itself), so a filesystem or permission failure degrades to a stale/missing note, never a blocked verb. The intake side is the one path that can reach delivery, and it is triple-gated: the flag pair, the injection screen ahead of both the local-model prompt and the deterministic fallback (so neither ever sees raw unscreened text), and the board-review path itself (a vault_note draft is PENDING + board-assigned, never auto-started — the CEO's approve_and_start is the only door to the Main PM). The dedup ledger (vault_seen_notes) is content-hash keyed, not just path-keyed, so an edited note is correctly re-eligible without needing a delete+recreate. Rebuild is idempotent and additive-safe (ensure_vault_assets never overwrites an existing file), so re-running it against a vault the CEO has already customized cannot clobber their edits to .obsidian//_meta/.
Related
docs/rag/architecture/obsidian-vault.md— the agent-facing doc (what the Auditor and vault-intake-originated tasks actually see)docs/rag/roles/auditor.md— thecurate_vaultverbdocs/map/orchestrator.md—_dispatch_vault_curation_work/_maybe_spawn_vault_curation/_vault_intake_loopdocs/map/product-strategy-research-pitch.md—XEngine, the sibling engine sharinginjection_guard.screen_external_textdocs/internal/specs/2026-07-09-obsidian-vault.md— the original design spec (vault layout, link-stability rationale)