mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(vault): Obsidian vault V2 — janitor, archival, weekly report, KB ingest, Bases + sync runbook (#482)
* feat(vault): V2 — create-seam + drift janitor, archival, weekly org-report, KB ingest, Bases views + sync runbook Implements the vault V2 canonical spec end to end (the splice guard shipped separately and is reused at KB-ingest time): - materialize-on-create: TaskService.create writes each task's note best-effort from the moment it exists; the transition-touch stops no-oping on live work - drift janitor (services/vault_janitor.py + hourly _vault_janitor_loop): daily changed-task re-projection, random drift sample, archival pass — restart-proof via RoboCo/_meta/.janitor_state.json, 200/cycle caps, per-item isolation, processed-only resume markers, self-repairing state file - archival: vault_archive_days (30, 0=off) moves old terminal tasks' notes to RoboCo/Archive/<year>/Tasks/<project>/ — one write_task code path for janitor and rebuild, id8 lookup across Tasks/+Archive/, alias links keep moves safe - weekly org-report: VaultWriter.write_org_report renders Reports/<ISO-week>.md from MetricsService/UsageService (numbers duplicated into frontmatter for trend queries), once per ISO week, with a best-effort CEO notification - KB ingest: IndexType.VAULT_NOTES + VaultNotesIndexPlugin + _vault_kb_loop embed the CEO's RoboCo/Notes into the RAG corpus — injection guard as a hard gate (flagged notes quarantined with an idempotent callout), traversal- and symlink-contained at both config and engine layers, content-hash dedup, 50-ingest/cycle cap, frontmatter stripped; reaches roboco_kb_search, the mentor default domain, claim-time briefings (kind vault_note), and the panel KB browser; no migration (chunks table auto-creates; migration 030's CHUNK_TABLES tuple appended per the chunks_playbooks precedent) - Bases views (Task Board.base, Reports.base — schema verified against the Obsidian docs) + the Mac sync runbook vault asset - config/flags/compose: vault_archive_days, vault_report_enabled (flags card), vault_kb_enabled (flags card; NAS compose arms it, registry ships it off), vault_kb_dirs (+ overlap/traversal validator), vault_kb_interval_seconds - e2e smoke (tests/e2e_smoke/test_vault_v2.py): real create-seam, real janitor cycle incl. archival + state, real KB engine + real guard * docs: vault V2 sweep — map, RAG corpus, CLAUDE.md - docs/map/vault.md: V1+V2 — janitor/archival/report/KB data flows, new files, config, health posture - docs/map/orchestrator.md + task-service.md: the two new loops, the create seam, the three janitor queries - docs/rag/architecture/obsidian-vault.md: agent-facing what-changed (notes from creation, archive link-safety, CEO notes retrievable, weekly report) - docs/rag/architecture/config-reference.md: the five new settings - CLAUDE.md: vault paragraph covers V1+V2; flags-card list mentions the vault report/KB flags --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -98,6 +98,8 @@ The AgentOrchestrator is the runtime brain of RoboCo: it owns the per-agent Dock
|
||||
| AgentOrchestrator._dispatch_vault_curation_work | method | roboco/runtime/orchestrator.py:12057 | Obsidian-vault root-completion hook (`obsidian_vault_enabled`): reads `TaskService.list_completed_roots_pending_vault_curation` and spawns the Auditor once per candidate via `_maybe_spawn_vault_curation`. Owns only this trigger — distinct from `_dispatch_audit_work`'s scheduled sweeps. |
|
||||
| AgentOrchestrator._maybe_spawn_vault_curation | method | roboco/runtime/orchestrator.py:12080 | One-shot Auditor spawn for one completed root: `_board_dispatched` in-memory one-shot guard + a durable `vault_curation_dispatched` marker (survives a restart) before spawning WITHOUT a bound task_id (mirrors `_dispatch_audit_work`'s alert spawn — the root is already `completed`, binding would trip the readiness gate) — the task id is named in the prompt, `curate_vault` takes it as an explicit argument. |
|
||||
| AgentOrchestrator._vault_intake_loop | method | roboco/runtime/orchestrator.py:8106 | Default-off tick loop (BOTH `obsidian_vault_enabled` AND `vault_intake_enabled`); calls `_run_vault_intake_cycle` → `VaultIntakeEngine.run_cycle()` every `vault_intake_interval_seconds`. |
|
||||
| AgentOrchestrator._vault_janitor_loop | method | roboco/runtime/orchestrator.py:8142 | V2: gated on `obsidian_vault_enabled` alone; ticks hourly (`JANITOR_LOOP_INTERVAL_SECONDS`, no config knob) calling `_run_vault_janitor_cycle` → `VaultJanitor.run_cycle()`, which itself only does real work when a `RoboCo/_meta/.janitor_state.json` state file says a sweep/report is actually due. |
|
||||
| AgentOrchestrator._vault_kb_loop | method | roboco/runtime/orchestrator.py:8174 | V2: gated on BOTH `obsidian_vault_enabled` AND `vault_kb_enabled`; ticks every `vault_kb_interval_seconds` (default 900) calling `_run_vault_kb_cycle` → `VaultKBEngine.run_cycle()`, embedding changed `RoboCo/Notes` notes into `IndexType.VAULT_NOTES`. |
|
||||
| AgentOrchestrator._pending_claim_blocked | method | roboco/runtime/orchestrator.py:11586 | Dispatch-time probe reusing `TaskService.is_pending_claim_blocked` (the exact claim-gate predicate — dependency OR sequence) so `_route_unassigned_pm_task` (called from `_dispatch_pm_work` per PENDING coordination-root task) can filter a doomed later-wave/sequence-held task before attempting the raw claim, instead of one failed claim per tick. Fails open (False) on any lookup error — the claim attempt itself is the safety net. |
|
||||
| AgentOrchestrator._pm_respawn_should_gate | method | roboco/runtime/orchestrator.py:8915 | Per-(slug,task) respawn circuit breaker; tracing_gap rule-following resets (bounded) + durable persist; CEO notify once when tripped. |
|
||||
| AgentOrchestrator._handle_pm_assigned_task | method | roboco/runtime/orchestrator.py:9084 | Spawn/respawn the PM for an assigned coordination root subject to the respawn gate. |
|
||||
@@ -211,7 +213,7 @@ stateDiagram-v2
|
||||
- `ROBOCO_X_FEATURE_SPOTLIGHT_ENABLED` (+ `_interval_seconds` default 259200/3d, default off, sub-switch of `x_engine_enabled`) — gates `_x_feature_spotlight_loop`.
|
||||
- `ROBOCO_FABLE_MODE_ENABLED` (default off) — gates `_fable_hook_groups` (Claude-path hook install) and, via `roboco/agents/factories/_base.py`, the `fable_doctrine_layer` prompt layer; off = byte-for-byte unchanged spawn path.
|
||||
- No flag — the PR-gate turn cut is unconditional: when every child of an assembled parent is terminal, `_try_auto_submit` always runs the owning PM's submit_up/submit_root gate system-side instead of spawning the PM for that turn; a gate rejection (freshness/integrity/AC-coverage/race) falls back to the classic PM closure spawn (the sole safety net), with the reason threaded into the PM's closure prompt. The `pr_gate_auto_submit_enabled` kill-switch that gated this through 0.19.0 has been removed.
|
||||
- `ROBOCO_OBSIDIAN_VAULT_ENABLED` (default off; both compose files set `true`) — gates `_dispatch_vault_curation_work` (the vault-curation Auditor-spawn trigger) and `_vault_intake_loop`'s outer check. `ROBOCO_VAULT_INTAKE_ENABLED` (default off; both compose files set `true`) — the `_vault_intake_loop`'s own switch, consulted only when the vault master switch is also on.
|
||||
- `ROBOCO_OBSIDIAN_VAULT_ENABLED` (default off; both compose files set `true`) — gates `_dispatch_vault_curation_work` (the vault-curation Auditor-spawn trigger), `_vault_intake_loop`'s outer check, and (V2) `_vault_janitor_loop`'s sole gate. `ROBOCO_VAULT_INTAKE_ENABLED` (default off; both compose files set `true`) — the `_vault_intake_loop`'s own switch, consulted only when the vault master switch is also on. V2: `ROBOCO_VAULT_ARCHIVE_DAYS` (default 30) / `ROBOCO_VAULT_REPORT_ENABLED` (default true) tune the janitor's archival pass and weekly report, both folded into `_vault_janitor_loop` with no separate loop. `ROBOCO_VAULT_KB_ENABLED` (+ `_KB_DIRS` default `RoboCo/Notes` / `_KB_INTERVAL_SECONDS` default 900, default off; NAS compose sets it `true`, registry compose leaves it `false`) — the second, independent switch `_vault_kb_loop` checks alongside the vault master switch.
|
||||
- No flag — `_pending_claim_blocked` (the dependency/sequence claim-gate prefilter) is unconditional, mirroring `TaskService.claim`'s own always-on sequence gate; it is a pure dispatch-time optimization (fails open on lookup error) with no behavior change vs. attempting and failing the claim.
|
||||
|
||||
## Gotchas
|
||||
|
||||
@@ -15,8 +15,12 @@
|
||||
|------|------|-----------|----------------|
|
||||
| `_validate_and_set_status` | method | task.py:548 | Single chokepoint: validate transition + git requirements, set status, poke dispatcher, emit audit. |
|
||||
| `_emit_status_transition_audit` | method | task.py:652 | Write `task.<status>` audit row in caller session; bump `revision_count` on entry into `needs_revision`. |
|
||||
| `create` | method | task.py:864 | New task; depth/batch/AC validation; branchless/umbrella flags; baseline constraints attachment. |
|
||||
| `create` | method | task.py:864 | New task; depth/batch/AC validation; branchless/umbrella flags; baseline constraints attachment; (V2) vault materialize-on-create. |
|
||||
| `_attach_baseline_constraints` | method | task.py:971 | Append conventions baseline constraints to task prompt (gated `conventions_enabled`). |
|
||||
| `_materialize_vault_note` | method | task.py:910 | V2: best-effort vault seam called from `create` — assembles + writes a deterministic task note (narrative placeholder) so a task is visible in the vault from the moment it exists, not just at Auditor curation/rebuild. Gated `obsidian_vault_enabled`; swallows + logs any failure. |
|
||||
| `list_updated_since` | method | task.py:7101 | V2: tasks touched (`COALESCE(updated_at, created_at)`) since a timestamp, ascending, paged — the vault janitor's changed-task re-projection set. |
|
||||
| `list_archive_candidates` | method | task.py:7124 | V2: terminal tasks whose terminal timestamp falls in `[after, before)`, ascending, paged — the vault janitor's archival-pass candidate window (watermark-bounded so a sweep never rescans the whole archive). |
|
||||
| `sample_stale_tasks` | method | task.py:7154 | V2: random sample of tasks last touched before a cutoff — the vault janitor's drift-verification sample. |
|
||||
| `activate` | method | task.py:1577 | `backlog→pending` (PM only); batch-shape guard. |
|
||||
| `_ensure_branch_for_task` | method | task.py:1675 | Branch resolution for claim; `""` for branchless/umbrella. |
|
||||
| `_auto_create_branch` | method | task.py:1833 | Cut hierarchical branch + per-task worktree add (F123). |
|
||||
|
||||
+41
-17
@@ -1,46 +1,70 @@
|
||||
## 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.
|
||||
The Obsidian vault (V1+V2): 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. V2 adds three things on top of the V1 projection: materialize-on-create (a task's note exists from the moment it's created, not just at curation/rebuild), a drift janitor (hourly-ticked, daily/weekly-gated: re-projects changed tasks, verifies a random sample, archives old terminal tasks, writes the weekly org-report), and KB ingest (the CEO's own `RoboCo/Notes/` notes become one more RAG corpus the fleet can retrieve). Default-off (`ROBOCO_OBSIDIAN_VAULT_ENABLED`; both compose files arm it `true`). Still structurally different from the other default-off engines: the projection never originates delivery work itself — 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/vault_writer.py` | `VaultWriter` — pure, DB-free markdown materializer. `write_task` / `write_journal_entry` / `append_a2a_message` / `write_agent` / `touch_task_frontmatter` / `write_org_report`. 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. V2: `write_task` is archive-aware (`TaskNoteData.archive_year` routes it to `RoboCo/Archive/<year>/Tasks/<project>/` instead of `Tasks/<project>/`, removing the stale copy on a move); `find_task_note`/`task_note_status` locate/inspect a note wherever it lives (recursive id8 lookup across both trees) for the janitor's drift check; `write_org_report` renders `RoboCo/Reports/<ISO-week>.md`. | 534 |
|
||||
| `roboco/services/vault_assembly.py` | `assemble_task_note_data` — resolves a task's project slug, parent, subtasks, dependencies, and (V2) archive eligibility (`_archive_year`, gated on `vault_archive_days`) via the live `TaskService`/`ProjectService` into a `TaskNoteData`. `reproject_task` (V2) bundles assemble + narrative-preservation + `write_task` into the one code path shared by `rebuild`, the janitor's changed/sample/archival passes, and the create-on-task seam — none of them can drift on how a note gets refreshed. | ~125 |
|
||||
| `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). V2: the frontmatter split + content-hash helpers moved to the shared `foundation/policy/vault_notes.py` (this module now just imports them). | ~350 |
|
||||
| `roboco/services/vault_janitor.py` **(new, V2)** | `VaultJanitor.run_cycle` — one state-gated sweep: re-project tasks changed since the last sweep (`TaskService.list_updated_since`, capped/paged, per-item isolated), verify a random stale sample (`sample_stale_tasks`), archive old terminal tasks (`list_archive_candidates`), and (weekly) render the org-report. Dueness is tracked in a JSON state file (`RoboCo/_meta/.janitor_state.json`: `last_sweep`, `last_report_week`, `archive_watermark`), not the loop's own cadence — restart-proof. | ~343 |
|
||||
| `roboco/services/vault_kb_engine.py` **(new, V2)** | `VaultKBEngine.run_cycle` — scans the allowlisted `vault_kb_dirs` (default `RoboCo/Notes`), dedups by content hash, screens every note body through the injection guard as a hard GATE (flagged → quarantined, never indexed), and ingests/deindexes into `IndexType.VAULT_NOTES` via `OptimalService.index_vault_note`/`unindex_vault_note`. Defense-in-depth containment: symlinks and any resolved-path escape from the vault root are skipped, independent of the config-load validator. | ~315 |
|
||||
| `roboco/foundation/policy/vault_notes.py` **(new, V2)** | Shared pure helpers: `content_hash` (sha256 with every `> [!kind] RoboCo: ...` feedback callout stripped first, so appending one doesn't change what the next scan considers "changed") and `split_frontmatter` (YAML frontmatter + body). Used by both the intake watcher's "drafted" callout and the KB engine's "quarantined" callout — one shared convention instead of two copies drifting. | ~46 |
|
||||
| `roboco/services/optimal_brain/indexes/vault_notes.py` **(new, V2)** | `VaultNotesIndexPlugin` — `IndexType.VAULT_NOTES` plugin, mirrors `PlaybooksIndexPlugin`'s shape (`index_note`/`delete_note`/`search_notes`, source URI `vault://<relpath>`). Scope enforced by the KB engine's dir allowlist, not this plugin. | ~70 |
|
||||
| `roboco/vault.py` | `python -m roboco.vault {rebuild,relocate}` CLI. `rebuild` re-projects every agent/task/journal-entry/A2A-thread from the DB (now archive-aware via `vault_assembly.reproject_task` — an old terminal task projects straight into `Archive/<year>/`) 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 — V2 adds `Archive`/`Reports` graph color groups) + `meta/` (dashboard + kanban-board + README, V2 adds `Task Board.base` + `Reports.base` for Obsidian's core Bases plugin, and `Sync to your Mac.md`, the Syncthing/SMB/Obsidian-Sync runbook). Dataview dashboard queries now exclude `Archive/`. | — |
|
||||
| `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. V2 reuses it a third time: the KB engine's ingest-time hard gate (quarantine on a hit, vs. the intake watcher's screen-and-still-process posture). | 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. | — |
|
||||
| `roboco/services/task.py` `_materialize_vault_note` / `list_updated_since` / `list_archive_candidates` / `sample_stale_tasks` | V2: the create-time seam + the janitor's three query methods. See `docs/map/task-service.md`. | — |
|
||||
|
||||
## 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.
|
||||
**PROJECTION (always-on when the flag is armed).** `TaskService.create` (V2) calls `_materialize_vault_note` — best-effort, same swallow-and-log posture as every other seam — so a task's note exists from the moment it's created, not just at curation/rebuild. Three more best-effort event seams fire from existing services: `TaskService._emit_status_transition_audit` → `_touch_vault_frontmatter` patches an EXISTING note's status/team/pr fields in place (now effectively always finds one for any task created post-V2, since materialize-on-create ran; a pre-V2 task without a note is still a no-op here — the janitor's changed/sample passes are what backfill it); `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 import `get_vault_writer()` lazily and catch every exception.
|
||||
|
||||
**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._`.
|
||||
**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 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.
|
||||
**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; skip if already seen (path + content-hash in `vault_seen_notes`); screen the body via `screen_external_text`; extract title/description/action-items via a local-model chat call against the SCREENED text, falling back to deterministic extraction; open ONE PENDING task (`source=vault_note`, Product-Owner-assigned, `team=board`) capped by `vault_intake_max_open_drafts`/`vault_intake_max_per_cycle`; append a feedback callout (best-effort). Never starts delivery directly — the board-review path is the only door.
|
||||
|
||||
**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).
|
||||
**JANITOR (V2, hourly-ticked, day/week-gated).** `AgentOrchestrator._vault_janitor_loop` (gated on `obsidian_vault_enabled` alone) ticks every `JANITOR_LOOP_INTERVAL_SECONDS` (3600, no config knob) and calls `VaultJanitor.run_cycle`. Actual work only happens when the restart-proof state file (`RoboCo/_meta/.janitor_state.json`) says it's due:
|
||||
- **Sweep** (due when `last_sweep` is >= 24h stale): `_reproject_changed` re-projects every task touched since the last sweep (`TaskService.list_updated_since`, ascending, paged 100 at a time, capped at `_MAX_REPROJECT_PER_CYCLE=200` per tick, one bad item logged-and-skipped rather than wedging the pass) via the shared `reproject_task`; `_verify_sample` pulls a random 20-task sample of tasks last touched before the sweep window (`sample_stale_tasks`) and repairs any whose note is missing or whose frontmatter status disagrees with the DB (via `touch_task_frontmatter`, not a full re-projection); `_archive_pass` moves terminal tasks past `vault_archive_days` into `Archive/<year>/` (see below). A capped tick advances `last_sweep`/`archive_watermark` only to the last-processed item's stamp (not "now"), so the very next hourly tick — already due again — picks up the tail with no gap. Logs one `vault_drift_repaired` line: `count` (repaired) / `archived` / `failed`.
|
||||
- **Weekly report** (due when `last_report_week` != the current ISO week, and `vault_report_enabled` is on): `_run_weekly_report` pulls `MetricsService.get_velocity/get_cycle_time_by_stage/get_bottleneck_distribution/get_rework_metrics` (days=7) + `UsageService.get_summary("7d")`, renders `VaultWriter.write_org_report`, and best-effort notifies the CEO (`NotificationService.send_weekly_report_notification`) — a notification failure never invalidates the already-written note.
|
||||
|
||||
**ARCHIVAL (V2, folded into the janitor sweep).** Policy: a terminal (`completed`/`cancelled`) task whose terminal timestamp (`completed_at` else `updated_at` else `created_at`) is older than `vault_archive_days` (default 30; `0` disables archival outright) moves from `RoboCo/Tasks/<project>/` to `RoboCo/Archive/<year>/Tasks/<project>/`. `TaskService.list_archive_candidates(after, before, ...)` returns terminal tasks whose terminal timestamp falls in `[watermark, cutoff)`, paged/capped identically to the changed-task pass (`_MAX_ARCHIVE_PER_CYCLE=200`). The move itself is free: `VaultWriter.write_task` is archive-aware (`TaskNoteData.archive_year` set by `vault_assembly._archive_year`) — it looks up an existing note across BOTH `Tasks/` and `Archive/` by id8, writes the new copy at the archive-aware target directory, and deletes the stale copy if it moved. Alias-based wikilinks (`[[id8|title]]`) mean nothing pointing at an archived task ever breaks. `rebuild` is archive-aware for free (routes through the same `reproject_task`), and the shipped Dataview dashboard + graph color groups exclude `Archive/`.
|
||||
|
||||
**KB INGEST (V2, independently double-gated).** `AgentOrchestrator._vault_kb_loop` (BOTH `obsidian_vault_enabled` AND `vault_kb_enabled` required) ticks `VaultKBEngine.run_cycle` every `vault_kb_interval_seconds` (default 900). Per allowlisted dir in `vault_kb_dirs` (default `RoboCo/Notes`; config-load validation in `Settings._validate_vault_kb_dirs` rejects an absolute/`..`-carrying entry or one overlapping `vault_intake_dir` or a reserved projection dir): recursively scan `*.md`, skip a note that's a symlink, escapes the resolved vault root, or exceeds 64KB; content-hash-dedup against the currently-tracked `IndexType.VAULT_NOTES` docs (an unchanged note is skipped); screen the frontmatter-stripped body through `screen_external_text` as a hard GATE — a flagged note is quarantined (skipped, warn-logged, a one-line feedback callout appended, and any PRIOR indexed chunks removed if it was previously clean) rather than indexed; a clean note ingests via `OptimalService.index_vault_note` (bounded to `_MAX_INGEST_PER_CYCLE=50` per tick — the tail waits for the next cycle). A deletion pass deindexes any previously-tracked path no longer seen on disk. Consumers: `roboco_kb_search` picks up `VAULT_NOTES` for free once the enum exists; `MentorService`'s default/general domain search list includes it (labeled "Vault Notes"); `EvidenceRepo.similar_memory` includes it in claim-time briefings (kind `vault_note`), same relevance floor as learnings/playbooks; the panel's KB browser has a full type entry (nav/filter/badge/stats).
|
||||
|
||||
**REBUILD/RELOCATE (operator/CLI, not agent-facing).** `python -m roboco.vault rebuild` walks every agent, then every task (via the shared `reproject_task` — archive-aware, narrative-preserving), then every non-private journal entry, then every A2A thread, 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.
|
||||
|
||||
## Config Flags
|
||||
|
||||
- `ROBOCO_OBSIDIAN_VAULT_ENABLED` — master switch; off = `VaultWriter` is never invoked from any seam, `curate_vault` returns `invalid_state`, and `python -m roboco.vault` refuses. Config default `false`; both `docker-compose.yml` and `docker-compose.registry.yml` set it `true`.
|
||||
- `ROBOCO_OBSIDIAN_VAULT_ENABLED` — master switch; off = `VaultWriter` is never invoked from any seam, `curate_vault` returns `invalid_state`, the janitor/KB loops return immediately, and `python -m roboco.vault` refuses. Config default `false`; both compose files set it `true`.
|
||||
- `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 default `false`; both compose files set it `true`.
|
||||
- `ROBOCO_VAULT_INTAKE_ENABLED` — independent switch for `_vault_intake_loop`; inert unless the master switch is ALSO on. Config default `false`; both compose files set it `true`.
|
||||
- `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.
|
||||
- `ROBOCO_VAULT_ARCHIVE_DAYS` (default `30`, `0` disables) — age past which a terminal task's note archives during the janitor sweep. Checked only under the master switch — no separate enable flag.
|
||||
- `ROBOCO_VAULT_REPORT_ENABLED` (default `true`) — the janitor's weekly org-report + CEO notification. Config default `true` in both compose files (deterministic, no LLM, cheap to leave on).
|
||||
- `ROBOCO_VAULT_KB_ENABLED` (default `false`) — master switch for KB ingest; off = `_vault_kb_loop` returns immediately and `IndexType.VAULT_NOTES` stays empty. NAS compose (`docker-compose.yml`) sets it `true`; the public registry compose (`docker-compose.registry.yml`) leaves it `false` (optional engines ship off).
|
||||
- `ROBOCO_VAULT_KB_DIRS` (default `RoboCo/Notes`, CSV) — vault-relative folders the KB engine scans. Rejected at config load if absolute, `..`-carrying, or overlapping `vault_intake_dir`/`Tasks`/`Journals`/`A2A`/`Agents`/`Archive`/`Reports`/`_meta`/`.obsidian`.
|
||||
- `ROBOCO_VAULT_KB_INTERVAL_SECONDS` (default `900`, min `60`) — KB-engine scan cadence.
|
||||
|
||||
## 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/`.
|
||||
The projection side is zero-risk by construction: every seam is best-effort and DB-free from the writer's perspective, so a filesystem or permission failure degrades to a stale/missing note, never a blocked verb. Materialize-on-create closes the V1 gap where the Dataview board only ever showed curated/rebuilt tasks — a fresh task is visible immediately. The janitor is the freshness backstop for everything best-effort seams can miss: it's restart-proof (dueness lives in a state file, not loop cadence — an orchestrator that restarts more often than daily still sweeps exactly once per elapsed day), self-healing against a corrupt/hand-edited state file (any unparseable value degrades to "no state," never a wedged loop), and every per-item drain (changed-task, sample-verify, archive) isolates failures — one bad row is logged and skipped, never aborts the pass, and re-qualifies on its next change or the next sample draw. Per-cycle caps (200 reprojects, 200 archives) mean a first-enable or long-downtime backlog drains in bounded hourly slices via the resume-marker convention (a capped tick advances the marker only to the last item it actually processed) rather than one unbounded burst.
|
||||
|
||||
The KB-ingest side is the one path with real security stakes — once a vault note is agent-retrievable, unscreened note text is injection into the fleet's retrieval context, not just a drafting risk. It layers defense-in-depth: the config-load validator rejects a dangerous `vault_kb_dirs` entry outright (can't even start with an escaping/overlapping dir); the engine independently re-checks every allowlisted dir resolves under the vault root before scanning it; every individual note is re-checked for symlink-ness and resolved-path escape before it's read (belt-and-suspenders against a dir-level check being bypassed by a per-file symlink); and the injection guard runs as a hard GATE (not the intake watcher's screen-and-still-process posture) — a flagged note is never embedded, only quarantined with a visible callout so the CEO knows why. Content-hash dedup (shared with the intake watcher's ledger convention) makes both re-scans and the quarantine callout's own append idempotent — appending the callout never itself re-triggers reprocessing.
|
||||
|
||||
Rebuild/relocate remain idempotent and additive-safe (`ensure_vault_assets` never overwrites an existing file), so re-running against a CEO-customized vault cannot clobber `.obsidian/`/`_meta/` edits.
|
||||
|
||||
## Related
|
||||
|
||||
- `docs/rag/architecture/obsidian-vault.md` — the agent-facing doc (what the Auditor and vault-intake-originated tasks actually see)
|
||||
- `docs/rag/architecture/obsidian-vault.md` — the agent-facing doc (what the Auditor and vault-intake-originated tasks actually see, plus what changed for KB retrieval)
|
||||
- `docs/rag/roles/auditor.md` — the `curate_vault` verb
|
||||
- `docs/map/orchestrator.md` — `_dispatch_vault_curation_work` / `_maybe_spawn_vault_curation` / `_vault_intake_loop`
|
||||
- `docs/map/orchestrator.md` — `_dispatch_vault_curation_work` / `_maybe_spawn_vault_curation` / `_vault_intake_loop` / `_vault_janitor_loop` / `_vault_kb_loop`
|
||||
- `docs/map/task-service.md` — `_materialize_vault_note` / `list_updated_since` / `list_archive_candidates` / `sample_stale_tasks`
|
||||
- `docs/map/product-strategy-research-pitch.md` — `XEngine`, the sibling engine sharing `injection_guard.screen_external_text`
|
||||
- `docs/internal/specs/2026-07-09-obsidian-vault.md` — the original design spec (vault layout, link-stability rationale)
|
||||
- `docs/internal/specs/2026-07-09-obsidian-vault.md` — the original V1 design spec (vault layout, link-stability rationale)
|
||||
- `docs/internal/specs/2026-07-11-obsidian-vault-v2.md` — the V2 spec (materialize-on-create, janitor, archival, KB ingest, weekly report, Bases, sync doc)
|
||||
|
||||
@@ -92,6 +92,9 @@ The PR-gate turn cut (when every child of an assembled parent is terminal, `_try
|
||||
| `ROBOCO_ROADMAP_ENGINE_ENABLED` | `false` | The board roadmap engine: weekly Product-Owner-authored cycle, CEO approves each item individually into BACKLOG. See "Board Roadmap Engine" below. |
|
||||
| `ROBOCO_OBSIDIAN_VAULT_ENABLED` | `false` (both compose files set `true`) | The Obsidian vault projection: tasks/journals/A2A become wikilinked markdown, rebuildable from the DB. See `docs/rag/architecture/obsidian-vault.md`. |
|
||||
| `ROBOCO_VAULT_INTAKE_ENABLED` | `false` (both compose files set `true`) | The vault's `#roboco`-tag inbox watcher — requires `ROBOCO_OBSIDIAN_VAULT_ENABLED` also on. See `docs/rag/architecture/obsidian-vault.md`. |
|
||||
| `ROBOCO_VAULT_ARCHIVE_DAYS` | `30` (`0` disables) | Age (past its terminal timestamp) a completed/cancelled task's note must reach before the vault janitor moves it to `RoboCo/Archive/<year>/`. |
|
||||
| `ROBOCO_VAULT_REPORT_ENABLED` | `true` | The vault janitor's weekly `RoboCo/Reports/<ISO-week>.md` org-report note + CEO notification (deterministic, no LLM). Needs `ROBOCO_OBSIDIAN_VAULT_ENABLED` also on. |
|
||||
| `ROBOCO_VAULT_KB_ENABLED` | `false` (NAS compose sets `true`; registry compose leaves `false`) | Master switch for vault KB ingest: embeds `ROBOCO_VAULT_KB_DIRS` note folders (default `RoboCo/Notes`) into `IndexType.VAULT_NOTES`, screened for injection attempts before indexing. Requires `ROBOCO_OBSIDIAN_VAULT_ENABLED` also on. `ROBOCO_VAULT_KB_DIRS` (CSV, default `RoboCo/Notes`) and `ROBOCO_VAULT_KB_INTERVAL_SECONDS` (default `900`) tune scope and cadence. See `docs/rag/architecture/obsidian-vault.md`. |
|
||||
|
||||
The company-in-a-box subsystems toggle the same way: web research (`ROBOCO_RESEARCH_ENABLED`, default **on** — see "Web Research" below), the strategy engine (`ROBOCO_STRATEGY_ENGINE_ENABLED`, default off), and pitch provisioning (`ROBOCO_PROVISIONING_ENABLED`, default on but inert without a token/org configured).
|
||||
|
||||
|
||||
@@ -1,55 +1,78 @@
|
||||
# Obsidian Vault (V1)
|
||||
# Obsidian Vault
|
||||
|
||||
## What It Is
|
||||
|
||||
RoboCo can project its own state into a human-readable [Obsidian](https://obsidian.md) vault — tasks, journal entries, and A2A thread digests as wikilinked markdown notes — so the CEO can browse the org's memory with a normal notes app instead of the panel alone. It is a **rebuildable projection**, not a second source of truth: every note is derived from DB state and can always be regenerated from scratch. Implemented in `roboco/services/vault_writer.py` (pure markdown materializer), `roboco/services/vault_assembly.py` (DB → materializer dataclasses), `roboco/services/vault_intake_engine.py` (the inbox watcher), and `roboco/vault.py` (the `rebuild`/`relocate` CLI).
|
||||
RoboCo can project its own state into a human-readable [Obsidian](https://obsidian.md) vault — tasks, journal entries, and A2A thread digests as wikilinked markdown notes — so the CEO can browse the org's memory with a normal notes app instead of the panel alone. It is a **rebuildable projection**, not a second source of truth: every note is derived from DB state and can always be regenerated from scratch. Implemented in `roboco/services/vault_writer.py` (pure markdown materializer), `roboco/services/vault_assembly.py` (DB → materializer dataclasses), `roboco/services/vault_intake_engine.py` (the inbox watcher), `roboco/services/vault_janitor.py` (drift repair, archival, weekly report), `roboco/services/vault_kb_engine.py` (KB ingest), and `roboco/vault.py` (the `rebuild`/`relocate` CLI).
|
||||
|
||||
## Enable/Disable
|
||||
|
||||
| Variable | Default | Effect |
|
||||
|----------|---------|--------|
|
||||
| `ROBOCO_OBSIDIAN_VAULT_ENABLED` | `false` (both compose files set `true`) | Master switch. Off = no note is ever written, `curate_vault` and `python -m roboco.vault` both refuse. |
|
||||
| `ROBOCO_OBSIDIAN_VAULT_ENABLED` | `false` (both compose files set `true`) | Master switch. Off = no note is ever written, `curate_vault` and `python -m roboco.vault` both refuse, and neither the janitor nor the KB engine run. |
|
||||
| `ROBOCO_VAULT_PATH` | `/data/vault` | Root directory the vault materializes into. |
|
||||
| `ROBOCO_VAULT_INTAKE_ENABLED` | `false` (both compose files set `true`) | The `#roboco`-tag inbox watcher (see below) — requires the master switch also on. |
|
||||
| `ROBOCO_VAULT_ARCHIVE_DAYS` | `30` (`0` disables) | Age past which a completed/cancelled task's note moves into the vault's archive during the janitor's daily sweep. |
|
||||
| `ROBOCO_VAULT_REPORT_ENABLED` | `true` | The janitor's weekly org-report note + CEO notification. |
|
||||
| `ROBOCO_VAULT_KB_ENABLED` | `false` (NAS compose sets `true`; registry compose leaves `false`) | KB ingest of the CEO's own `RoboCo/Notes/` into `IndexType.VAULT_NOTES` — see below. Requires the master switch also on. |
|
||||
|
||||
## What agents actually touch
|
||||
|
||||
Almost everything here is transparent to a working agent — notes get written or patched as a side effect of normal verbs (a status transition, a `note()` call, a `dm()`), never something you call yourself. There are exactly two places an agent interacts with the vault directly:
|
||||
Almost everything here is transparent to a working agent — notes get written or patched as a side effect of normal verbs (a status transition, a `note()` call, a `dm()`), never something you call yourself. What changed for you since V1:
|
||||
|
||||
1. **The Auditor's `curate_vault(task_id, narrative)`** — spawned once per completed root task to write the one piece of vault content that isn't mechanically derivable from DB columns: a narrative paragraph on the task's note. See `docs/rag/roles/auditor.md`.
|
||||
2. **The vault-intake watcher turning a CEO-authored note into a task you might get delegated.** If the CEO tags a note `#roboco` in the vault's inbox folder, a default-off watcher (`ROBOCO_VAULT_INTAKE_ENABLED`) drafts it into a board-review task (`source=vault_note`) — same shape as a chat-confirmed intake draft, Product-Owner-assigned, `team=board`. It never starts work directly: the board reviews it and only the CEO's `approve_and_start` hands it to the Main PM for real delegation. If you end up working a task with `source=vault_note`, its origin was a note the CEO wrote in their own vault, not a chat.
|
||||
1. **Your task has a note from the moment it's created**, not just once curated or rebuilt — `TaskService.create` now materializes it directly. You'll never see a task the CEO can look up in the vault yet you can't.
|
||||
2. **A task that gets archived (old + terminal) doesn't lose its links.** The vault moves an old completed/cancelled task's note into `RoboCo/Archive/<year>/Tasks/<project>/`, but every wikilink to it (`[[id8|title]]`) is alias-based, so nothing pointing at it breaks — you'd never notice unless you went looking at the raw file path.
|
||||
3. **If `vault_kb_enabled` is on, the CEO's own vault notes are retrievable by you.** Anything the CEO writes under `RoboCo/Notes/` (screened for injection attempts first) is embedded into the knowledge base like any other corpus — it shows up in `roboco_kb_search` / `roboco_ask_mentor` results and in your claim-time institutional-memory briefing, labeled `vault_note` (or "Vault Notes" in mentor output). Treat it exactly like a learning or a playbook: institutional memory, not a directive to follow blindly.
|
||||
4. **A weekly org-report note exists** (`RoboCo/Reports/<ISO-week>.md`) — velocity, cycle time by stage, bottlenecks, rework, cost. Deterministic (no LLM), for the CEO's browsing; not something you're expected to author or reference.
|
||||
5. **The Auditor's `curate_vault(task_id, narrative)`** — unchanged: spawned once per completed root task to write the one piece of vault content that isn't mechanically derivable from DB columns, a narrative paragraph. See `docs/rag/roles/auditor.md`.
|
||||
6. **The vault-intake watcher turning a CEO-authored note into a task you might get delegated.** If the CEO tags a note `#roboco` in the vault's inbox folder, a default-off watcher (`ROBOCO_VAULT_INTAKE_ENABLED`) drafts it into a board-review task (`source=vault_note`) — same shape as a chat-confirmed intake draft, Product-Owner-assigned, `team=board`. It never starts work directly: the board reviews it and only the CEO's `approve_and_start` hands it to the Main PM for real delegation.
|
||||
|
||||
Everything else — note layout, link stability, the rebuild CLI — is infrastructure you don't need to reason about to do your job; the rest of this doc is here for completeness, not because you'll call any of it.
|
||||
Everything else — note layout, link stability, the janitor's internals, the rebuild CLI — is infrastructure you don't need to reason about to do your job; the rest of this doc is here for completeness, not because you'll call any of it.
|
||||
|
||||
## Layout and link stability
|
||||
|
||||
```
|
||||
RoboCo/
|
||||
Tasks/<project-slug>/<title> (<id8>).md
|
||||
Archive/<year>/Tasks/<project-slug>/<title> (<id8>).md (old terminal tasks)
|
||||
Journals/<agent-slug>/<date> <title> (<id8>).md
|
||||
A2A/<date> <agents> (<thread-id8>).md
|
||||
Agents/<slug>.md
|
||||
_meta/ (shipped Dataview/Kanban/graph-group dashboards)
|
||||
Reports/<ISO-week>.md (weekly org-report)
|
||||
Notes/ (CEO's own writing — KB ingest scope)
|
||||
_meta/ (shipped Dataview/Kanban/graph-group dashboards + Bases views)
|
||||
```
|
||||
|
||||
Every note carries `aliases: [<id8>]` in its frontmatter, so a cross-link is always written as `[[<id8>|<title>]]` — Obsidian resolves it by alias regardless of the target's current filename. A task title edit updates the note's own title line without ever renaming the file or breaking a link elsewhere in the vault. Private journal entries (`is_private`) are excluded from the projection, same as the shared RAG corpus.
|
||||
Every note carries `aliases: [<id8>]` in its frontmatter, so a cross-link is always written as `[[<id8>|<title>]]` — Obsidian resolves it by alias regardless of the target's current filename or folder. A task title edit updates the note's own title line without ever renaming the file or breaking a link elsewhere in the vault; an archival move works the same way — it relocates the file, not the identity agents/links reference. Private journal entries (`is_private`) are excluded from the projection, same as the shared RAG corpus, and stay excluded from KB ingest scope too (KB ingest only ever covers `RoboCo/Notes/`, never `Journals`/`Tasks`/`A2A`/`Agents`/`Archive`/`Reports`).
|
||||
|
||||
## What triggers a write
|
||||
|
||||
Best-effort event seams patch or append on the relevant transition — never a gate, never something that can block the real action:
|
||||
|
||||
- A task status transition patches the note's frontmatter (status/team/PR) in place, if the note already exists.
|
||||
- **Task creation** materializes the note immediately (deterministic template; the narrative is a placeholder until the Auditor curates it).
|
||||
- A task status transition patches the note's frontmatter (status/team/PR) in place.
|
||||
- A `note()` (journal entry) writes one immutable file per entry.
|
||||
- A `dm()` (A2A message) appends to a per-thread digest file.
|
||||
- An hourly-ticked, daily-gated janitor sweep catches anything the above seams missed: re-projects tasks changed since the last sweep, verifies a random sample of older ones, and archives old terminal tasks. This is a backstop, not something your own verb calls need to think about.
|
||||
|
||||
None of these fail your call if the vault write itself fails — it's logged and swallowed.
|
||||
|
||||
## KB ingest — the CEO's notes become retrievable
|
||||
|
||||
When `vault_kb_enabled` is on (alongside the master switch), notes the CEO writes under `RoboCo/Notes/` are embedded into a dedicated `IndexType.VAULT_NOTES` corpus on a ~15-minute cycle. Before anything is indexed, the note body is screened for prompt-injection patterns — a flagged note is quarantined (never embedded, a warning callout is appended to it so the CEO sees why) rather than silently indexed. A clean note is retrievable through:
|
||||
|
||||
- `roboco_kb_search` (pass no filter, or filter to `vault_notes` explicitly)
|
||||
- `roboco_ask_mentor` — company/general domain queries include it, formatted as "Vault Notes"
|
||||
- Your claim-time institutional-memory briefing, alongside distilled learnings and approved playbooks, subject to the same relevance floor (nothing injected below it)
|
||||
|
||||
Content you retrieve this way is the CEO's own writing — background, decisions, preferences — not a verified fact or a directive. Weigh it the same way you'd weigh a learning: useful context, not gospel.
|
||||
|
||||
## Rebuild and relocate
|
||||
|
||||
`python -m roboco.vault rebuild` re-projects every live entity from the DB from scratch (preserving any existing task's Auditor-authored narrative, since that isn't derivable) and materializes the shipped `.obsidian/` config + `_meta/` dashboards if not already present. `python -m roboco.vault relocate <path>` moves the tree — useful for grafting `RoboCo/` into an existing personal vault without touching that vault's own config. Both are operator/CEO commands, not something an agent runs.
|
||||
`python -m roboco.vault rebuild` re-projects every live entity from the DB from scratch (preserving any existing task's Auditor-authored narrative, archive-aware so an old terminal task lands directly in `Archive/<year>/`) and materializes the shipped `.obsidian/` config + `_meta/` dashboards/Bases views if not already present. `python -m roboco.vault relocate <path>` moves the tree. Both are operator/CEO commands, not something an agent runs.
|
||||
|
||||
## Related
|
||||
|
||||
- `docs/rag/roles/auditor.md` — the `curate_vault` verb
|
||||
- `docs/rag/architecture/config-reference.md` — full env var table
|
||||
- `docs/rag/architecture/x-engine.md` — the sibling engine whose injection-screening guard (`screen_external_text`) the vault-intake watcher shares
|
||||
- `docs/rag/architecture/x-engine.md` — the sibling engine whose injection-screening guard (`screen_external_text`) both the vault-intake watcher and the KB engine share
|
||||
- `docs/map/vault.md` — the code-level map (files, data flow, config, health)
|
||||
|
||||
Reference in New Issue
Block a user