mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
docs: v0.23.0 agent-facing sweep — map, RAG corpus, CLAUDE.md (#468)
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>
This commit is contained in:
@@ -206,7 +206,9 @@ backlog -> pending -> claimed -> in_progress -> [blocked|paused] -> verifying
|
|||||||
completed completed
|
completed completed
|
||||||
```
|
```
|
||||||
|
|
||||||
**In-path PR-review gate** (`awaiting_pr_review`): each assembled PR is reviewed before the PM merges. The cell PM's `submit_up` opens the cell→root PR and the Main PM's `submit_root` opens the root→master PR; both enter `awaiting_pr_review`, where a reviewer `pr_pass`es it on to `awaiting_pm_review` or `pr_fail`s it back to `needs_revision` — the merge-level reject the PM otherwise lacks. Leaf dev tasks and branchless coordination roots skip the gate.
|
**In-path PR-review gate** (`awaiting_pr_review`): each assembled PR is reviewed before the PM merges. The cell PM's `submit_up` opens the cell→root PR and the Main PM's `submit_root` opens the root→master PR; both enter `awaiting_pr_review`, where a reviewer `pr_pass`es it on to `awaiting_pm_review` or `pr_fail`s it back to `needs_revision` — the merge-level reject the PM otherwise lacks. Leaf dev tasks and branchless coordination roots skip the gate. `pr_pass` additionally refuses while the assembled PR's own CI (its head commit's checks, `GitService.get_pr_ci_status`) is failing, pending, or unresolvable — a repo with no CI configured passes through with an evidence note; `pr_fail` stays available regardless so a reviewer is never stuck waiting on CI. The reviewer prompt requires a per-AC `file:line` walk (a silently dropped deliverable is an automatic fail) and the gate's diff/conventions base resolves the task's REAL parent branch (`resolve_parent_branch`, the parent task's own `branch_name`) instead of deriving it from the branch-name string, so a cross-team hop (e.g. cell→root) no longer attributes inherited base-branch content to the task under review.
|
||||||
|
|
||||||
|
**Sequence is the bar.** A task with a parent and effective sequence N (`COALESCE(sequence, 0)`) cannot be claimed while any same-parent sibling with a strictly lower effective sequence is non-terminal — assignee-blind, independent of and stricter than `dependency_ids`, enforced in `TaskService._validate_claim_preconditions` (the `claim` chokepoint itself) so every claim path crosses it. Ties run parallel; cancelled siblings never block; sequence `0` and parentless tasks are unaffected. Delegation stamps sequence from the collision DAG (`stamp_wave_sequence`: `1 + max` same-parent dependency sequence, or `0` when independent) instead of a raw per-sibling ordinal, so fully independent siblings tie and run in parallel while colliding/ordered work ascends — PM-authored sequences are never rewritten. `tasks.parent_task_id` is indexed (migration 069) since the guard's sibling probe runs on every claim; the dispatcher pre-filters dependency/sequence-held tasks (`TaskService.is_pending_claim_blocked`) before attempting a doomed claim.
|
||||||
|
|
||||||
**PM-turn elimination (auto-submit).** When every child of an assembled, PR-bearing parent goes terminal, the orchestrator's closure dispatcher (`_maybe_spawn_pm_closure` → `_closure_handled_without_pm` → `_try_auto_submit`) runs the real `submit_up`/`submit_root` gate system-side as the owning PM instead of spawning the PM for that turn — same verb, same guards (ownership, notes, journal:decision, subtasks-terminal, parent-AC coverage, branch), authorized via the internal API with the PM's own identity headers. This is unconditional — the turn cut IS the flow, no kill-switch. Success lands the task on `awaiting_pr_review` with an audited `task.auto_submitted` row and no PM spawn; ANY refusal (branchless/umbrella parent, a gate rejection — freshness, AC coverage, a subtask-terminal race — or a transport error) falls back to spawning the PM exactly as before — that fallback is the sole safety net — with the refusal reason threaded into the PM's closure prompt so it isn't rediscovering it blind.
|
**PM-turn elimination (auto-submit).** When every child of an assembled, PR-bearing parent goes terminal, the orchestrator's closure dispatcher (`_maybe_spawn_pm_closure` → `_closure_handled_without_pm` → `_try_auto_submit`) runs the real `submit_up`/`submit_root` gate system-side as the owning PM instead of spawning the PM for that turn — same verb, same guards (ownership, notes, journal:decision, subtasks-terminal, parent-AC coverage, branch), authorized via the internal API with the PM's own identity headers. This is unconditional — the turn cut IS the flow, no kill-switch. Success lands the task on `awaiting_pr_review` with an audited `task.auto_submitted` row and no PM spawn; ANY refusal (branchless/umbrella parent, a gate rejection — freshness, AC coverage, a subtask-terminal race — or a transport error) falls back to spawning the PM exactly as before — that fallback is the sole safety net — with the refusal reason threaded into the PM's closure prompt so it isn't rediscovering it blind.
|
||||||
|
|
||||||
@@ -345,7 +347,7 @@ Each agent gets a **spawn manifest** at `/app/tool-manifest.json` listing the ve
|
|||||||
| prompter | (none beyond `i_am_idle` — not a delivery-lifecycle role; intake interviewer, human-only) |
|
| prompter | (none beyond `i_am_idle` — not a delivery-lifecycle role; intake interviewer, human-only) |
|
||||||
| secretary | (none beyond `i_am_idle` — human-only chief-of-staff; reads company state + runs gated CEO directives) |
|
| secretary | (none beyond `i_am_idle` — human-only chief-of-staff; reads company state + runs gated CEO directives) |
|
||||||
|
|
||||||
Content tools (do_server) — most roles: `commit`, `note`, `dm`, `read_a2a`, `evidence`. Delivery roles (developer / qa / documenter / cell_pm / main_pm) also get `draft_playbook` (draft a curated playbook for the KB). Product Owner additionally gets `propose_roadmap` (product_owner-only, authors the weekly board-roadmap cycle) and Head of Marketing additionally gets `propose_feature_spotlight` (head_marketing-only, drafts a feature-spotlight X post) — see "Board roadmap engine" / "RoboCo X account" below. Auditor is restricted to `note` (scope=reflect) + `evidence`, plus the playbook-curation verbs `approve_playbook` / `reject_playbook` / `archive_playbook` (a bounded, deliberate expansion — KB curation, not agent comms, so its no-`dm` restriction holds). The `pr_reviewer` posts its change-request on the PR itself (no agent comms). The `prompter` (intake) and `secretary` are restricted to `note` + `evidence` — human-only, no `dm`/`notify`. The `note`/journal write returns as soon as the entry is persisted; RAG indexing (Ollama embedding) runs fire-and-forget, so the tool no longer times out under concurrent load.
|
Content tools (do_server) — most roles: `commit`, `note`, `dm`, `read_a2a`, `evidence`. Delivery roles (developer / qa / documenter / cell_pm / main_pm) also get `draft_playbook` (draft a curated playbook for the KB). Product Owner additionally gets `propose_roadmap` (product_owner-only, authors the weekly board-roadmap cycle) and Head of Marketing additionally gets `propose_feature_spotlight` (head_marketing-only, drafts a feature-spotlight X post) — see "Board roadmap engine" / "RoboCo X account" below. Auditor is restricted to `note` (scope=reflect) + `evidence`, plus the playbook-curation verbs `approve_playbook` / `reject_playbook` / `archive_playbook` (a bounded, deliberate expansion — KB curation, not agent comms, so its no-`dm` restriction holds) and, when the Obsidian vault is armed, `curate_vault` (writes one narrative paragraph onto a just-completed root's vault note — see "Obsidian vault V1" below). The `pr_reviewer` posts its change-request on the PR itself (no agent comms). The `prompter` (intake) and `secretary` are restricted to `note` + `evidence` — human-only, no `dm`/`notify`. The `note`/journal write returns as soon as the entry is persisted; RAG indexing (Ollama embedding) runs fire-and-forget, so the tool no longer times out under concurrent load.
|
||||||
|
|
||||||
### MCP servers running per agent container
|
### MCP servers running per agent container
|
||||||
|
|
||||||
@@ -356,6 +358,7 @@ Content tools (do_server) — most roles: `commit`, `note`, `dm`, `read_a2a`, `e
|
|||||||
| `roboco-git-readonly`| Read-only git: status, log, diff, branches |
|
| `roboco-git-readonly`| Read-only git: status, log, diff, branches |
|
||||||
| `roboco-optimal` | RAG: `roboco_ask_mentor`, `roboco_kb_search` |
|
| `roboco-optimal` | RAG: `roboco_ask_mentor`, `roboco_kb_search` |
|
||||||
| `roboco-docs` | Project docs file management (selected roles) |
|
| `roboco-docs` | Project docs file management (selected roles) |
|
||||||
|
| `playwright` | Structured browser tools (navigate/snapshot/evaluate/screenshot) — `fe-qa`/`ux-qa` only, role-gated not image-gated; the wrapper entrypoint points it at the image's own baked `chromium-headless-shell` |
|
||||||
|
|
||||||
Every verb returns a standardized **Envelope**:
|
Every verb returns a standardized **Envelope**:
|
||||||
- ok: `{status, task_id, next, evidence?, context_briefing}`
|
- ok: `{status, task_id, next, evidence?, context_briefing}`
|
||||||
@@ -393,6 +396,8 @@ Agent backends are pluggable. `roboco/llm/providers/` defines an `AgentProvider`
|
|||||||
|
|
||||||
**Board roadmap engine (default-off).** The Board originating strategic work: on a weekly interval (`ROBOCO_ROADMAP_ENGINE_ENABLED` + `_INTERVAL_SECONDS` / `_MIN_ITEMS_PER_CYCLE` / `_MAX_ITEMS_PER_CYCLE`) `RoadmapEngine` (`roboco/services/roadmap_engine.py`) opens ONE held **exploration** task (`source="board_roadmap"`, `confirmed_by_human=False`, PENDING, Product-Owner-assigned, `Team.BOARD`), deduped to one open cycle at a time. A dedicated one-shot `_dispatch_roadmap_exploration` spawns the Product Owner **solo** — deliberately NOT `_handle_board_assigned_task` (which would also spawn Head of Marketing and fire the Approve-&-Start handoff, both wrong for a PO-authored cycle) — reusing the `_board_dispatched` one-shot tracker + respawn breaker, and short-circuiting once the cycle is authored. The PO explores (read-only git, KB/RAG, metrics, releases, charter, optional web research) and makes ONE `propose_roadmap` call (a content verb gated to `product_owner` only, `_ROADMAP_ROLES`; wired through the do_server/Choreographer like `pitch`) authoring a **themed cycle** — a one-line goal + 3-7 item drafts — persisted as a `roadmap_cycle` marker on the exploration task (no table/migration). The CEO acts per-item in the panel roadmap queue (`roadmap-review-queue.tsx`; `/api/roadmap/cycles{,/items/{id}/approve,/items/{id}/reject}`, CEO-only): approve materializes that item as a BACKLOG task (`source="roadmap"`, no assignee — never auto-starts; normal PM activation picks it up) via `PrompterService.create_task_from_draft`, reject records a reason; when every item is terminal the exploration task completes (`RoadmapService`, idempotent per item). Dispatchers skip `board_roadmap` (never delivery work). `create_task_from_draft` honors a draft-declared `source` only from a `{prompter, roadmap}` whitelist — an LLM-authored draft can't impersonate a privileged origin.
|
**Board roadmap engine (default-off).** The Board originating strategic work: on a weekly interval (`ROBOCO_ROADMAP_ENGINE_ENABLED` + `_INTERVAL_SECONDS` / `_MIN_ITEMS_PER_CYCLE` / `_MAX_ITEMS_PER_CYCLE`) `RoadmapEngine` (`roboco/services/roadmap_engine.py`) opens ONE held **exploration** task (`source="board_roadmap"`, `confirmed_by_human=False`, PENDING, Product-Owner-assigned, `Team.BOARD`), deduped to one open cycle at a time. A dedicated one-shot `_dispatch_roadmap_exploration` spawns the Product Owner **solo** — deliberately NOT `_handle_board_assigned_task` (which would also spawn Head of Marketing and fire the Approve-&-Start handoff, both wrong for a PO-authored cycle) — reusing the `_board_dispatched` one-shot tracker + respawn breaker, and short-circuiting once the cycle is authored. The PO explores (read-only git, KB/RAG, metrics, releases, charter, optional web research) and makes ONE `propose_roadmap` call (a content verb gated to `product_owner` only, `_ROADMAP_ROLES`; wired through the do_server/Choreographer like `pitch`) authoring a **themed cycle** — a one-line goal + 3-7 item drafts — persisted as a `roadmap_cycle` marker on the exploration task (no table/migration). The CEO acts per-item in the panel roadmap queue (`roadmap-review-queue.tsx`; `/api/roadmap/cycles{,/items/{id}/approve,/items/{id}/reject}`, CEO-only): approve materializes that item as a BACKLOG task (`source="roadmap"`, no assignee — never auto-starts; normal PM activation picks it up) via `PrompterService.create_task_from_draft`, reject records a reason; when every item is terminal the exploration task completes (`RoadmapService`, idempotent per item). Dispatchers skip `board_roadmap` (never delivery work). `create_task_from_draft` honors a draft-declared `source` only from a `{prompter, roadmap}` whitelist — an LLM-authored draft can't impersonate a privileged origin.
|
||||||
|
|
||||||
|
**Obsidian vault V1 (default-off).** The org's human-readable memory palace as a rebuildable DB projection — tasks, journals, and A2A digests as wikilinked markdown — gated by `ROBOCO_OBSIDIAN_VAULT_ENABLED` + `ROBOCO_VAULT_PATH` (default off, but both compose files arm it `true`). `VaultWriter` (`roboco/services/vault_writer.py`) is a pure, DB-free materializer under `RoboCo/{Tasks/<project-slug>,Journals/<agent-slug>,A2A,Agents}/`; every note carries a stable `aliases: [<id8>]` so a title rename never breaks a `[[id8|title]]` cross-link, and private journals are excluded. Three best-effort event seams (`TaskService`'s status-transition frontmatter touch, `JournalService`, `A2AService`) patch/append on the relevant transition — a vault write failure never blocks the real action. `python -m roboco.vault rebuild` re-projects every entity from the DB (preserving an existing task's Auditor-authored `## Narrative`) and materializes the shipped `.obsidian/` config (Dataview, Kanban, graph groups) + `RoboCo/_meta/` dashboards from `roboco/vault_assets/`; `relocate <path>` moves the tree, grafting `RoboCo/` into an existing personal vault without touching its own config. The Auditor gets a one-shot `curate_vault(task_id, narrative)` do-tool, spawned by the orchestrator on each completed root task, writing the `## Narrative` section a deterministic write otherwise leaves as a placeholder. A second, independently-gated `ROBOCO_VAULT_INTAKE_ENABLED` watcher (`VaultIntakeEngine`) turns `#roboco`-tagged notes under the vault's inbox folder into PENDING, Product-Owner-assigned board-review drafts (`source=vault_note`) — the identical board-review path a chat-confirmed draft takes, never straight into delivery. Extraction runs on the local model with a deterministic fallback; the note body is screened through `foundation/policy/injection_guard.screen_external_text` (the same untrusted-content envelope `XEngine` applies to X mentions — flags an injection-pattern line inline, never removes content) before it reaches the prompt or the fallback. Deduped per `(vault-relative path, content hash)` via `vault_seen_notes`, so an edit re-qualifies a previously-seen note.
|
||||||
|
|
||||||
**Fable-mode (default-off).** Full opus-fable-playbook adoption: makes the fleet behave more like Fable 5 on the existing model tiers (the tiers stay — Fable 5 the model is not an option). Two levers, both gated by `ROBOCO_FABLE_MODE_ENABLED`: ① **doctrine** — `fable_doctrine_layer()` (`roboco/agents/factories/_base.py`) composes the vendored behavioral doctrine (`agents/prompts/doctrine/fable.md`, from `github.com/rennf93/opus-fable-playbook` MIT `output-styles/fable.md`, YAML frontmatter stripped) into `compose_prompt`'s layer tuple immediately after `base.md` — universal cross-role doctrine, the same tier as the base rules, ahead of role/team/identity layers so those keep their specificity precedence. ② **hooks** — 5 vendored scripts under `docker/scripts/fable-*.sh` (stop-gate, bash-discipline, honesty-nudge, prompt-nudge, precompact; `session-start.sh` deliberately SKIPPED — its doctrine card is redundant with ① and its output-style check is inapplicable here) are installed alongside RoboCo's own hooks, never replacing them: `AgentOrchestrator._fable_hook_groups()` appends them AFTER RoboCo's own per-event entries in the Claude-path settings.json (isolated into its own helper to protect `_generate_agent_settings`'s xenon budget); the grok path installs only `honesty-nudge` (`write_grok_fable_hooks`, `roboco/llm/providers/grok_cli_config.py`) — a deliberately conservative V1 scope, since a grok `PreToolUse`/`Stop` hook deny cancels the entire run (verified live) while `PostToolUse` never denies. Off by default: the spawn path (composed prompt, settings.json, grok hooks) is byte-for-byte unchanged when the flag is off. No new eval harness — measurement rides the existing rework/spawn-waste/`revision_count` dashboard (see "Delivery observability" below). Armed on the NAS deploy like the rest; left OFF in `docker-compose.registry.yml`.
|
**Fable-mode (default-off).** Full opus-fable-playbook adoption: makes the fleet behave more like Fable 5 on the existing model tiers (the tiers stay — Fable 5 the model is not an option). Two levers, both gated by `ROBOCO_FABLE_MODE_ENABLED`: ① **doctrine** — `fable_doctrine_layer()` (`roboco/agents/factories/_base.py`) composes the vendored behavioral doctrine (`agents/prompts/doctrine/fable.md`, from `github.com/rennf93/opus-fable-playbook` MIT `output-styles/fable.md`, YAML frontmatter stripped) into `compose_prompt`'s layer tuple immediately after `base.md` — universal cross-role doctrine, the same tier as the base rules, ahead of role/team/identity layers so those keep their specificity precedence. ② **hooks** — 5 vendored scripts under `docker/scripts/fable-*.sh` (stop-gate, bash-discipline, honesty-nudge, prompt-nudge, precompact; `session-start.sh` deliberately SKIPPED — its doctrine card is redundant with ① and its output-style check is inapplicable here) are installed alongside RoboCo's own hooks, never replacing them: `AgentOrchestrator._fable_hook_groups()` appends them AFTER RoboCo's own per-event entries in the Claude-path settings.json (isolated into its own helper to protect `_generate_agent_settings`'s xenon budget); the grok path installs only `honesty-nudge` (`write_grok_fable_hooks`, `roboco/llm/providers/grok_cli_config.py`) — a deliberately conservative V1 scope, since a grok `PreToolUse`/`Stop` hook deny cancels the entire run (verified live) while `PostToolUse` never denies. Off by default: the spawn path (composed prompt, settings.json, grok hooks) is byte-for-byte unchanged when the flag is off. No new eval harness — measurement rides the existing rework/spawn-waste/`revision_count` dashboard (see "Delivery observability" below). Armed on the NAS deploy like the rest; left OFF in `docker-compose.registry.yml`.
|
||||||
|
|
||||||
**Ponytail (bundled with Fable-mode).** Rides `ROBOCO_FABLE_MODE_ENABLED` — no separate flag. Vendors the ponytail "lazy senior dev" build-laziness doctrine (`agents/prompts/doctrine/ponytail.md` + ethos sibling, MIT, Copyright (c) 2026 DietrichGebert — trimmed, YAML frontmatter stripped) into every composed system prompt via `ponytail_doctrine_layer` (`roboco/agents/factories/_base.py`), slotted immediately after the Fable doctrine layer and gated on the same flag. Role-scoped: developers (`AgentRole.DEVELOPER`) get the full ladder (YAGNI → reuse-in-this-codebase → stdlib → native-platform → installed-dep → one-line → minimal); every other role gets the ethos-only cut (`ponytail-ethos.md`) — the code-mechanics rungs are dropped so they can't leak into prose artifacts (task plans, review notes, docs). Both files carry a 5-point RoboCo preamble (the ethos sibling adds a 6th: free-text field obligations) that makes the ladder yield to the Architectural Conventions Standard (placement), the 80% coverage gate + QA review + self-verification, the per-team design bar, task hygiene (everything-is-a-task / commits-linked / state-is-sacred), and reviewer feedback (`needs_revision` / `pr_fail` / `request_changes`) — the overlap mitigation is scoping, not deletion, and it rides ponytail's own "when NOT to be lazy" clause. Developer intensity is tunable via `ROBOCO_PONYTAIL_INTENSITY` (`lite` / `full` / `ultra`, default `full`; `roboco/config.py` `ponytail_intensity`, a string value — not a feature flag): `full` enforces the ladder, `lite` builds what's asked and names the lazier alternative, `ultra` is YAGNI-extremist (deletion before addition, challenge the requirement). Non-developers get no dial — `ultra` is wrong for prose artifacts, so the ethos runs a fixed restrained stance. Prompt-only: no hooks, no grok-path changes — ponytail adds no hook surface, so bundling it under the Fable flag changes only the composed prompt, not the spawn hooks. The Fable flag's description in `roboco/config.py` names both doctrines.
|
**Ponytail (bundled with Fable-mode).** Rides `ROBOCO_FABLE_MODE_ENABLED` — no separate flag. Vendors the ponytail "lazy senior dev" build-laziness doctrine (`agents/prompts/doctrine/ponytail.md` + ethos sibling, MIT, Copyright (c) 2026 DietrichGebert — trimmed, YAML frontmatter stripped) into every composed system prompt via `ponytail_doctrine_layer` (`roboco/agents/factories/_base.py`), slotted immediately after the Fable doctrine layer and gated on the same flag. Role-scoped: developers (`AgentRole.DEVELOPER`) get the full ladder (YAGNI → reuse-in-this-codebase → stdlib → native-platform → installed-dep → one-line → minimal); every other role gets the ethos-only cut (`ponytail-ethos.md`) — the code-mechanics rungs are dropped so they can't leak into prose artifacts (task plans, review notes, docs). Both files carry a 5-point RoboCo preamble (the ethos sibling adds a 6th: free-text field obligations) that makes the ladder yield to the Architectural Conventions Standard (placement), the 80% coverage gate + QA review + self-verification, the per-team design bar, task hygiene (everything-is-a-task / commits-linked / state-is-sacred), and reviewer feedback (`needs_revision` / `pr_fail` / `request_changes`) — the overlap mitigation is scoping, not deletion, and it rides ponytail's own "when NOT to be lazy" clause. Developer intensity is tunable via `ROBOCO_PONYTAIL_INTENSITY` (`lite` / `full` / `ultra`, default `full`; `roboco/config.py` `ponytail_intensity`, a string value — not a feature flag): `full` enforces the ladder, `lite` builds what's asked and names the lazier alternative, `ultra` is YAGNI-extremist (deletion before addition, challenge the requirement). Non-developers get no dial — `ultra` is wrong for prose artifacts, so the ethos runs a fixed restrained stance. Prompt-only: no hooks, no grok-path changes — ponytail adds no hook surface, so bundling it under the Fable flag changes only the composed prompt, not the spawn hooks. The Fable flag's description in `roboco/config.py` names both doctrines.
|
||||||
@@ -495,6 +500,7 @@ The system runs as Docker Compose services. All Dockerfiles live under `docker/`
|
|||||||
| `redis` | Cache, sessions, event bus | `redis-cli ping` |
|
| `redis` | Cache, sessions, event bus | `redis-cli ping` |
|
||||||
| `ollama` | Local LLM + embeddings | `ollama list` |
|
| `ollama` | Local LLM + embeddings | `ollama list` |
|
||||||
| `ollama-init` | Pulls models on startup | One-shot |
|
| `ollama-init` | Pulls models on startup | One-shot |
|
||||||
|
| `backup` | Daily `pg_dump` sidecar, data-only network, newest-14 rotation — see `docs/backend/ops/database-backups.md` | — |
|
||||||
| `agent-base-image` / `agent-*-image` | Pre-built images spawned per agent | One-shot |
|
| `agent-base-image` / `agent-*-image` | Pre-built images spawned per agent | One-shot |
|
||||||
| `orchestrator` | API + agent spawner | Depends on all above |
|
| `orchestrator` | API + agent spawner | Depends on all above |
|
||||||
| `panel` | Next.js control panel (internal, port 3000) | — |
|
| `panel` | Next.js control panel (internal, port 3000) | — |
|
||||||
@@ -529,6 +535,7 @@ Server-side events reach these sockets through `roboco/api/websocket_bridge.py`,
|
|||||||
- **Provider rate limits** are tracked in Redis (`RateLimitStateTracker`, `roboco/services/gateway/`). On a provider 429 an agent calls `i_am_blocked(reason="rate_limited")`; the spawn gate then **queues** (never drops) further work for that provider, and a background probe-and-resume loop in the orchestrator clears the limit and revives parked agents when it lifts.
|
- **Provider rate limits** are tracked in Redis (`RateLimitStateTracker`, `roboco/services/gateway/`). On a provider 429 an agent calls `i_am_blocked(reason="rate_limited")`; the spawn gate then **queues** (never drops) further work for that provider, and a background probe-and-resume loop in the orchestrator clears the limit and revives parked agents when it lifts.
|
||||||
- **Provider overloads** reuse the same park-and-probe break. A persistent model-API overload (HTTP 529 / 500 / 503 — the SDK already retries transient ones) parks the provider exactly like a 429 instead of crash-retrying the agent straight back into the overload and burning tokens; the overload is detected orchestrator-side from the dead container's log markers, and the background loop revives the parked work when it recovers. The same break also catches the **Claude session-limit** 429 (the org's 5-hour usage window): an agent exiting with a 0-token session-limit rejection parks the provider and is auto-revived when the window resets, instead of fleet-wide crash-respawning straight back into the limit. Gated by `ROBOCO_OVERLOAD_BREAK_ENABLED` (default-on).
|
- **Provider overloads** reuse the same park-and-probe break. A persistent model-API overload (HTTP 529 / 500 / 503 — the SDK already retries transient ones) parks the provider exactly like a 429 instead of crash-retrying the agent straight back into the overload and burning tokens; the overload is detected orchestrator-side from the dead container's log markers, and the background loop revives the parked work when it recovers. The same break also catches the **Claude session-limit** 429 (the org's 5-hour usage window): an agent exiting with a 0-token session-limit rejection parks the provider and is auto-revived when the window resets, instead of fleet-wide crash-respawning straight back into the limit. Gated by `ROBOCO_OVERLOAD_BREAK_ENABLED` (default-on).
|
||||||
- **Gateway-health recovery** closes a blind spot in the stale-claim reaper: the heartbeat is bumped only by gateway verbs, so a broken-but-alive agent (a corrupted `/app/.venv` so no gateway tool imports) goes heartbeat-stale yet keeps its container up, and the reaper's live-skip would protect it forever. On a stale-heartbeat live container the reaper now probes the gateway out-of-band (`_probe_gateway_health` → `docker exec` the gateway venv imports) and, once broken past `ROBOCO_GATEWAY_HEALTH_GRACE_SECONDS` (a transient probe miss is tolerated), kills + evicts it (`_maybe_recover_broken_gateway`) so it falls through to release + respawn; healthy or inconclusive probes spare it. Gated by `ROBOCO_GATEWAY_HEALTH_ENABLED` (default-on). It is the third leg beside the shipped bash-guard `/app` block (prevents the self-corruption) and the reaper Docker-liveness fallback (stops over-reaping live containers).
|
- **Gateway-health recovery** closes a blind spot in the stale-claim reaper: the heartbeat is bumped only by gateway verbs, so a broken-but-alive agent (a corrupted `/app/.venv` so no gateway tool imports) goes heartbeat-stale yet keeps its container up, and the reaper's live-skip would protect it forever. On a stale-heartbeat live container the reaper now probes the gateway out-of-band (`_probe_gateway_health` → `docker exec` the gateway venv imports) and, once broken past `ROBOCO_GATEWAY_HEALTH_GRACE_SECONDS` (a transient probe miss is tolerated), kills + evicts it (`_maybe_recover_broken_gateway`) so it falls through to release + respawn; healthy or inconclusive probes spare it. Gated by `ROBOCO_GATEWAY_HEALTH_ENABLED` (default-on). It is the third leg beside the shipped bash-guard `/app` block (prevents the self-corruption) and the reaper Docker-liveness fallback (stops over-reaping live containers).
|
||||||
|
- **Tool-call budget.** A per-session counter (`docker/scripts/post-tool-budget-hook.sh` → the in-container SDK server) warns an agent at 100 tool calls and halts (auto-substitutes, releasing the task) at 300 (`BudgetPolicy.tool_call_warn_at` / `_halt_at`, `roboco/foundation/policy/agent_loop.py`; env overrides `ROBOCO_AGENT_TOOL_CALL_WARN` / `_HALT`). Raised from a 150 hard cap, which repeatedly halted legitimate multi-file work mid-task. A same-window loop detector (same tool + args repeated past `ROBOCO_AGENT_LOOP_THRESHOLD`) is a separate, tighter check that can deny the repeating call before the budget cap is ever reached.
|
||||||
- **PM coordinator concurrency.** A Main / Cell PM plans and delegates many root tasks in parallel — the actual work then runs in the delegated children/cells, not in the PM's own hands. The claim-time concurrency guards that keep a *developer* to one task at a time (`already_active` / `paused`, in `roboco/services/gateway/claim_guards.py`) are therefore **skipped for the coordinator PM roles** (`_COORDINATOR_ROLES = {main_pm, cell_pm}`, consulted in `_run_claim_guards`); only a genuine upstream **sequence dependency** (`unmet_dependency`, which parks the task back to `pending`) holds a PM's root back. Without this a single PM that claimed one root could never plan a second — it thrashed between its claimed roots and respawned forever, burning tokens for zero progress (the live `i_am_idle`-auto-paused-umbrella deadlock). The `paused` guard also excludes the target task itself, so a PM re-entering its own paused umbrella never self-blocks.
|
- **PM coordinator concurrency.** A Main / Cell PM plans and delegates many root tasks in parallel — the actual work then runs in the delegated children/cells, not in the PM's own hands. The claim-time concurrency guards that keep a *developer* to one task at a time (`already_active` / `paused`, in `roboco/services/gateway/claim_guards.py`) are therefore **skipped for the coordinator PM roles** (`_COORDINATOR_ROLES = {main_pm, cell_pm}`, consulted in `_run_claim_guards`); only a genuine upstream **sequence dependency** (`unmet_dependency`, which parks the task back to `pending`) holds a PM's root back. Without this a single PM that claimed one root could never plan a second — it thrashed between its claimed roots and respawned forever, burning tokens for zero progress (the live `i_am_idle`-auto-paused-umbrella deadlock). The `paused` guard also excludes the target task itself, so a PM re-entering its own paused umbrella never self-blocks.
|
||||||
- **Orchestrator runtime-state durability.** The PM-respawn loop breaker (`_pm_respawn_tracker`, the `(agent_slug, task_id) → strike-count` circuit breaker) is **DB-durable** via the `respawn_tracker` table (migration 051): each gate mutation write-throughs fire-and-forget on the `_bg_tasks` set (`_schedule_respawn_persist` → `_persist_respawn_record`), and `restore_respawn_tracker()` repopulates it at `start()`, validating each row against live tasks (terminal/missing rows are evicted). Kept only in memory it reset to `count=1` on every restart and re-burned the whole strike threshold (4 spawns) against a still-wedged task. It mirrors the `WaitingRecordTable` / `restore_waiting_records` pattern: best-effort (a DB hiccup degrades to in-memory-only — it can only ever *suppress* a spawn, never manufacture one) and inert when the table is empty. The companion `_instances` registry is **reconciled-from-Docker** (not persisted) at startup via `_readopt_running_agents`, so the reaper's liveness path and the spawn gate's `_is_agent_active` check see surviving containers immediately after a restart.
|
- **Orchestrator runtime-state durability.** The PM-respawn loop breaker (`_pm_respawn_tracker`, the `(agent_slug, task_id) → strike-count` circuit breaker) is **DB-durable** via the `respawn_tracker` table (migration 051): each gate mutation write-throughs fire-and-forget on the `_bg_tasks` set (`_schedule_respawn_persist` → `_persist_respawn_record`), and `restore_respawn_tracker()` repopulates it at `start()`, validating each row against live tasks (terminal/missing rows are evicted). Kept only in memory it reset to `count=1` on every restart and re-burned the whole strike threshold (4 spawns) against a still-wedged task. It mirrors the `WaitingRecordTable` / `restore_waiting_records` pattern: best-effort (a DB hiccup degrades to in-memory-only — it can only ever *suppress* a spawn, never manufacture one) and inert when the table is empty. The companion `_instances` registry is **reconciled-from-Docker** (not persisted) at startup via `_readopt_running_agents`, so the reaper's liveness path and the spawn gate's `_is_agent_active` check see surviving containers immediately after a restart.
|
||||||
- **Token usage** is captured per agent session from the Claude Code transcript via the SDK server's `/usage/sync` (hook → orchestrator finalize → `agent_spawn_sessions` → `daily_usage_rollups` → dashboard). Cost uses provider-aware pricing in `roboco/billing/pricing.py` (Anthropic priced; local/Ollama intentionally `$0`). The token sweep also publishes `USAGE_SNAPSHOT` to `/ws/system`, so the dashboard's "Token Usage & Cost" panel updates live and falls back to HTTP polling when the stream is down.
|
- **Token usage** is captured per agent session from the Claude Code transcript via the SDK server's `/usage/sync` (hook → orchestrator finalize → `agent_spawn_sessions` → `daily_usage_rollups` → dashboard). Cost uses provider-aware pricing in `roboco/billing/pricing.py` (Anthropic priced; local/Ollama intentionally `$0`). The token sweep also publishes `USAGE_SNAPSHOT` to `/ws/system`, so the dashboard's "Token Usage & Cost" panel updates live and falls back to HTTP polling when the stream is down.
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ This slice is the packaging, build, and runtime-tooling layer of RoboCo: the Doc
|
|||||||
|
|
||||||
| Path | Role | LOC |
|
| Path | Role | LOC |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| docker-compose.yaml | Build-from-source compose: postgres/redis/ollama/ollama-init, 14 agent-*-image builders, orchestrator, panel, nginx, `roboco_data` DB-isolation network; NAS prod env vars + volume mounts | 556 |
|
| docker-compose.yaml | Build-from-source compose: postgres/redis/ollama/ollama-init, 14 agent-*-image builders, orchestrator, panel, nginx, `backup` pg_dump sidecar on the `roboco_data` DB-isolation network; NAS prod env vars + volume mounts, `ROBOCO_OBSIDIAN_VAULT_ENABLED`/`ROBOCO_VAULT_INTAKE_ENABLED` default `true` | 556 |
|
||||||
| docker-compose.yml | Byte-identical copy of docker-compose.yaml (kept for the canonical name compose picks up by default) | 556 |
|
| docker-compose.yml | Byte-identical copy of docker-compose.yaml (kept for the canonical name compose picks up by default) | 556 |
|
||||||
| docker-compose.registry.yml | Pull-and-run compose using pre-built GHCR/Docker Hub images (ROBOCO_REGISTRY + ROBOCO_VERSION); infra services byte-identical to build compose, agent-* services are one-shot pre-pulls, own `roboco_data` network | 334 |
|
| docker-compose.registry.yml | Pull-and-run compose using pre-built GHCR/Docker Hub images (ROBOCO_REGISTRY + ROBOCO_VERSION); infra services byte-identical to build compose (incl. `backup` + vault defaults), agent-* services are one-shot pre-pulls, own `roboco_data` network | 334 |
|
||||||
| Makefile | Ops + quality targets: infra, dev/run/orchestrator, quality gate (ruff/mypy/pytest/xenon/radon/vulture/bandit/pip-audit/deptry/lint-imports/alembic/foundation-check), per-Python test matrix, docs, lifecycle regen | 548 |
|
| Makefile | Ops + quality targets: infra, dev/run/orchestrator, quality gate (ruff/mypy/pytest/xenon/radon/vulture/bandit/pip-audit/deptry/lint-imports/alembic/foundation-check), per-Python test matrix, docs, lifecycle regen | 548 |
|
||||||
| pyproject.toml | Project + dependency manifest: requires-python >=3.13,<3.15, deps, dev/docs extras, console scripts, ruff/mypy/pytest/coverage/vulture/bandit/radon/xenon/deptry/importlinter config | 451 |
|
| pyproject.toml | Project + dependency manifest: requires-python >=3.13,<3.15, deps, dev/docs extras, console scripts, ruff/mypy/pytest/coverage/vulture/bandit/radon/xenon/deptry/importlinter config | 451 |
|
||||||
| roboco/config.py | Pydantic Settings (env prefix ROBOCO_, cached via lru_cache); every tunable: DB, Redis, RAG, LLM/Ollama, workspaces, agent guardrails, gateway thresholds, autonomy-engine flags | 1022 |
|
| roboco/config.py | Pydantic Settings (env prefix ROBOCO_, cached via lru_cache); every tunable: DB, Redis, RAG, LLM/Ollama, workspaces, agent guardrails, gateway thresholds, autonomy-engine flags | 1022 |
|
||||||
@@ -22,8 +22,8 @@ This slice is the packaging, build, and runtime-tooling layer of RoboCo: the Doc
|
|||||||
| docker/agent-dev-be.Dockerfile | Backend dev — adds postgresql-client + redis-tools on top of base | 18 |
|
| docker/agent-dev-be.Dockerfile | Backend dev — adds postgresql-client + redis-tools on top of base | 18 |
|
||||||
| docker/agent-dev-fe.Dockerfile | Frontend dev — adds Playwright system deps + pnpm + chromium browser | 37 |
|
| docker/agent-dev-fe.Dockerfile | Frontend dev — adds Playwright system deps + pnpm + chromium browser | 37 |
|
||||||
| docker/agent-qa-be.Dockerfile | Backend QA — adds postgresql-client on top of base | 18 |
|
| docker/agent-qa-be.Dockerfile | Backend QA — adds postgresql-client on top of base | 18 |
|
||||||
| docker/agent-qa-fe.Dockerfile | Frontend QA — Playwright deps + pnpm + chromium | 36 |
|
| docker/agent-qa-fe.Dockerfile | Frontend QA — pnpm + `chromium-headless-shell` (not full chromium, under `/app/.playwright`) + `@playwright/mcp` for browser-based verification, wrapped by `playwright-mcp-entrypoint.sh` | 36 |
|
||||||
| docker/agent-ux.Dockerfile | UX/UI agent — FROM base, no extra tools (placeholder for Figma/image tools) | 13 |
|
| docker/agent-ux.Dockerfile | UX/UI agent (shared by ux-dev + ux-qa) — FROM base + the same `chromium-headless-shell` + `@playwright/mcp` as agent-qa-fe (role-gated at registration, not image-gated, so ux-dev never sees the MCP server despite sharing the image) | 13 |
|
||||||
| docker/agent-doc.Dockerfile | Documenter — FROM base, no extra tools | 12 |
|
| docker/agent-doc.Dockerfile | Documenter — FROM base, no extra tools | 12 |
|
||||||
| docker/agent-prompter.Dockerfile | Intake (Prompter) — persistent Claude Agent SDK session, ENTRYPOINT python -m roboco.agent_sdk.intake_main | 17 |
|
| docker/agent-prompter.Dockerfile | Intake (Prompter) — persistent Claude Agent SDK session, ENTRYPOINT python -m roboco.agent_sdk.intake_main | 17 |
|
||||||
| docker/agent-secretary.Dockerfile | Secretary — persistent Claude Agent SDK session with gated CEO-authority tools, ENTRYPOINT python -m roboco.agent_sdk.secretary_main | 19 |
|
| docker/agent-secretary.Dockerfile | Secretary — persistent Claude Agent SDK session with gated CEO-authority tools, ENTRYPOINT python -m roboco.agent_sdk.secretary_main | 19 |
|
||||||
@@ -35,6 +35,8 @@ This slice is the packaging, build, and runtime-tooling layer of RoboCo: the Doc
|
|||||||
| docker/postgres-pgvector.Dockerfile | Example custom pgvector build (pg17) — currently unused; compose uses pgvector/pgvector:pg16 image directly | 15 |
|
| docker/postgres-pgvector.Dockerfile | Example custom pgvector build (pg17) — currently unused; compose uses pgvector/pgvector:pg16 image directly | 15 |
|
||||||
| docker/nginx.conf | nginx default.conf template: /health /ready /api/ /ws/ -> orchestrator (with X-Agent-Token header), everything else -> panel | 67 |
|
| docker/nginx.conf | nginx default.conf template: /health /ready /api/ /ws/ -> orchestrator (with X-Agent-Token header), everything else -> panel | 67 |
|
||||||
| docker/postgres-init/01-create-extensions.sql | First-init SQL: CREATE EXTENSION IF NOT EXISTS vector + availability check (used by the pgvector image entrypoint) | 17 |
|
| docker/postgres-init/01-create-extensions.sql | First-init SQL: CREATE EXTENSION IF NOT EXISTS vector + availability check (used by the pgvector image entrypoint) | 17 |
|
||||||
|
| docker/scripts/backup-entrypoint.sh | `backup` sidecar's own loop (not a Claude hook): `pg_dump -Fc` on start + every `BACKUP_INTERVAL_SECONDS` (default 24h), `.tmp`-suffix-then-rename, prune to newest `BACKUP_KEEP` (default 14) by mtime; runs on the data-only network | 46 |
|
||||||
|
| docker/scripts/playwright-mcp-entrypoint.sh | Wrapper entrypoint for `@playwright/mcp`: points it at the image's own baked `chromium-headless-shell` instead of letting the MCP package download a second browser | 20 |
|
||||||
| docker/scripts/sdk-startup-hook.sh | Claude SessionStart hook: start SDK server on :9000 with UV_PROJECT_ENVIRONMENT=/app/.venv + --no-sync, reset budget, print briefing/precompact | 58 |
|
| docker/scripts/sdk-startup-hook.sh | Claude SessionStart hook: start SDK server on :9000 with UV_PROJECT_ENVIRONMENT=/app/.venv + --no-sync, reset budget, print briefing/precompact | 58 |
|
||||||
| docker/scripts/a2a-check-hook.sh | PostToolUse hook: poll SDK /inbox/count and remind the agent of pending A2A messages (always exits 0) | 30 |
|
| docker/scripts/a2a-check-hook.sh | PostToolUse hook: poll SDK /inbox/count and remind the agent of pending A2A messages (always exits 0) | 30 |
|
||||||
| docker/scripts/bash-guard-hook.sh | PreToolUse Bash guard: deny compound shell git network ops, PR/merge bypass, credential exfil, and uv run --active / uv targeting /app/.venv (exit 2 to deny) | 354 |
|
| docker/scripts/bash-guard-hook.sh | PreToolUse Bash guard: deny compound shell git network ops, PR/merge bypass, credential exfil, and uv run --active / uv targeting /app/.venv (exit 2 to deny) | 354 |
|
||||||
@@ -160,7 +162,7 @@ graph TD
|
|||||||
deployment-tooling
|
deployment-tooling
|
||||||
├─ Compose topologies
|
├─ Compose topologies
|
||||||
│ ├─ docker-compose.yaml / .yml (build-from-source)
|
│ ├─ docker-compose.yaml / .yml (build-from-source)
|
||||||
│ │ ├─ infra: postgres, redis, ollama, ollama-init
|
│ │ ├─ infra: postgres, redis, ollama, ollama-init, backup (pg_dump sidecar, always on)
|
||||||
│ │ ├─ agent-*-image builders (14, FROM docker/agent-*.Dockerfile)
|
│ │ ├─ agent-*-image builders (14, FROM docker/agent-*.Dockerfile)
|
||||||
│ │ ├─ orchestrator (build context=.)
|
│ │ ├─ orchestrator (build context=.)
|
||||||
│ │ ├─ panel (build)
|
│ │ ├─ panel (build)
|
||||||
@@ -257,6 +259,7 @@ deployment-tooling
|
|||||||
- ROBOCO_X_ENGINE_ENABLED / _MENTIONS_INTERVAL_SECONDS / _MENTIONS_MAX_PER_CYCLE / _MENTIONS_MIN_ENGAGEMENT / _MAX_OPEN_POSTS / ROBOCO_X_ACCOUNT_USER_ID / _REQUEST_TIMEOUT_SECONDS — the X (Twitter) engine; inert without stored OAuth 1.0a credentials regardless of the flag
|
- ROBOCO_X_ENGINE_ENABLED / _MENTIONS_INTERVAL_SECONDS / _MENTIONS_MAX_PER_CYCLE / _MENTIONS_MIN_ENGAGEMENT / _MAX_OPEN_POSTS / ROBOCO_X_ACCOUNT_USER_ID / _REQUEST_TIMEOUT_SECONDS — the X (Twitter) engine; inert without stored OAuth 1.0a credentials regardless of the flag
|
||||||
- ROBOCO_ROADMAP_ENGINE_ENABLED / _INTERVAL_SECONDS (default 604800) / _MIN_ITEMS_PER_CYCLE / _MAX_ITEMS_PER_CYCLE — the board roadmap engine
|
- ROBOCO_ROADMAP_ENGINE_ENABLED / _INTERVAL_SECONDS (default 604800) / _MIN_ITEMS_PER_CYCLE / _MAX_ITEMS_PER_CYCLE — the board roadmap engine
|
||||||
- ROBOCO_X_FEATURE_SPOTLIGHT_ENABLED / _INTERVAL_SECONDS (default 259200/3d) — X-engine feature-spotlight sub-switch (requires ROBOCO_X_ENGINE_ENABLED also on), default off
|
- ROBOCO_X_FEATURE_SPOTLIGHT_ENABLED / _INTERVAL_SECONDS (default 259200/3d) — X-engine feature-spotlight sub-switch (requires ROBOCO_X_ENGINE_ENABLED also on), default off
|
||||||
|
- ROBOCO_OBSIDIAN_VAULT_ENABLED / ROBOCO_VAULT_PATH (default `/data/vault`) — Obsidian vault V1 projection master switch, config-default off but both compose files set it `true`; ROBOCO_VAULT_INTAKE_ENABLED / _INTERVAL_SECONDS / _DIR / _MAX_PER_CYCLE / _MAX_OPEN_DRAFTS — the independently-gated `#roboco`-tag inbox watcher
|
||||||
- ROBOCO_FABLE_MODE_ENABLED — opus-fable-playbook adoption (doctrine layer in the composed prompt + 5 Claude-path hook scripts + 1 grok-path hook), default off; off = byte-for-byte unchanged spawn path
|
- ROBOCO_FABLE_MODE_ENABLED — opus-fable-playbook adoption (doctrine layer in the composed prompt + 5 Claude-path hook scripts + 1 grok-path hook), default off; off = byte-for-byte unchanged spawn path
|
||||||
- ROBOCO_MINIO_ENDPOINT / _ACCESS_KEY / _SECRET_KEY / _BUCKET / _REGION — MinIO object storage (default-off; empty endpoint = disabled, media route falls back to `FileResponse`; when set, `video_renderer_client._save` PUTs each render to MinIO after the local write and `GET /api/video/posts/{id}/media` streams it via `StreamingResponse` over `minio_client.get_object_stream`, key = basename, `_require_ceo` kept so auth stays end-to-end — no presigned URLs; `S3Error` falls back to `FileResponse`); NAS compose runs `minio` + `minio-init` on the `data` network with a named `minio-data` volume, registry compose omits MinIO; see `docs/rag/architecture/minio-storage.md`
|
- ROBOCO_MINIO_ENDPOINT / _ACCESS_KEY / _SECRET_KEY / _BUCKET / _REGION — MinIO object storage (default-off; empty endpoint = disabled, media route falls back to `FileResponse`; when set, `video_renderer_client._save` PUTs each render to MinIO after the local write and `GET /api/video/posts/{id}/media` streams it via `StreamingResponse` over `minio_client.get_object_stream`, key = basename, `_require_ceo` kept so auth stays end-to-end — no presigned URLs; `S3Error` falls back to `FileResponse`); NAS compose runs `minio` + `minio-init` on the `data` network with a named `minio-data` volume, registry compose omits MinIO; see `docs/rag/architecture/minio-storage.md`
|
||||||
- ROBOCO_TRANSCRIPT_RETENTION_DAYS / ROBOCO_TRANSCRIPT_PRUNE_ENABLED / _INTERVAL_SECONDS
|
- ROBOCO_TRANSCRIPT_RETENTION_DAYS / ROBOCO_TRANSCRIPT_PRUNE_ENABLED / _INTERVAL_SECONDS
|
||||||
@@ -294,6 +297,7 @@ deployment-tooling
|
|||||||
- Both NAS composes (`docker-compose.yml`/`.yaml`) set `ROBOCO_GUARD_ENABLED=true` / `ROBOCO_GUARD_PASSIVE_MODE=true` / `ROBOCO_GUARD_FAIL_SECURE=false` — the fastapi-guard HTTP security layer runs in detect-and-log calibration mode on the NAS, never blocking; `docker-compose.registry.yml` does not set these three and stays off (`guard_enabled` default `false`). Flipping `_PASSIVE_MODE` to `false` to enforce is a deliberate later step, not part of this arming.
|
- Both NAS composes (`docker-compose.yml`/`.yaml`) set `ROBOCO_GUARD_ENABLED=true` / `ROBOCO_GUARD_PASSIVE_MODE=true` / `ROBOCO_GUARD_FAIL_SECURE=false` — the fastapi-guard HTTP security layer runs in detect-and-log calibration mode on the NAS, never blocking; `docker-compose.registry.yml` does not set these three and stays off (`guard_enabled` default `false`). Flipping `_PASSIVE_MODE` to `false` to enforce is a deliberate later step, not part of this arming.
|
||||||
- Both NAS composes now also arm `ROBOCO_SANDBOX_DB_ENABLED` / `ROBOCO_DB_NETWORK_ISOLATED` / `ROBOCO_CLOUD_AUTH_ENABLED` / `ROBOCO_X_ENGINE_ENABLED` / `ROBOCO_ROADMAP_ENGINE_ENABLED` all `${VAR:-true}` (config default is `false` for every one), alongside the pre-existing `${VAR:-true}` flips for `ROBOCO_SELF_HEAL_ENABLED`, `ROBOCO_PROVISIONING_ENABLED`, `ROBOCO_TRANSCRIPT_PRUNE_ENABLED`, and `ROBOCO_ROUTING_STRICT` — this is a personal-deploy posture (override any via `.env`), not the published conservative default. `docker-compose.registry.yml` keeps `ROBOCO_SELF_HEAL_ENABLED:-false` (and does not set `SANDBOX_DB`/`CLOUD_AUTH`/`X_ENGINE`/`ROADMAP_ENGINE` at all, so they fall through to the config `False` default) — it arms only `ROBOCO_DB_NETWORK_ISOLATED:-true`, with its own `roboco_data` network in the `networks:` stanza, so the registry compose ships DB-isolated but otherwise conservative.
|
- Both NAS composes now also arm `ROBOCO_SANDBOX_DB_ENABLED` / `ROBOCO_DB_NETWORK_ISOLATED` / `ROBOCO_CLOUD_AUTH_ENABLED` / `ROBOCO_X_ENGINE_ENABLED` / `ROBOCO_ROADMAP_ENGINE_ENABLED` all `${VAR:-true}` (config default is `false` for every one), alongside the pre-existing `${VAR:-true}` flips for `ROBOCO_SELF_HEAL_ENABLED`, `ROBOCO_PROVISIONING_ENABLED`, `ROBOCO_TRANSCRIPT_PRUNE_ENABLED`, and `ROBOCO_ROUTING_STRICT` — this is a personal-deploy posture (override any via `.env`), not the published conservative default. `docker-compose.registry.yml` keeps `ROBOCO_SELF_HEAL_ENABLED:-false` (and does not set `SANDBOX_DB`/`CLOUD_AUTH`/`X_ENGINE`/`ROADMAP_ENGINE` at all, so they fall through to the config `False` default) — it arms only `ROBOCO_DB_NETWORK_ISOLATED:-true`, with its own `roboco_data` network in the `networks:` stanza, so the registry compose ships DB-isolated but otherwise conservative.
|
||||||
- Surface N (scanner honeytrap) is two-layered because nginx only proxies `/api|/ws|/health|/ready` to the orchestrator: `docker/nginx.conf` has a `location ~*` block that `return 444`s the classic root scanner paths (`/.env`, `/.git`, `/wp-login.php`, `/phpmyadmin`, `actuator`, `cgi-bin`, `vendor/`, …) at the edge before they reach the panel (anchored to scanner fingerprints; `/.well-known` + real routes untouched; always on), while `roboco/security.py`'s `_THREAT_BAN_CONFIG` gained `recon`/`sensitive_file`/`cms_probing` so `/api`-path probes that DO reach guard trip an adaptive per-IP redis auto-ban (active mode only; passive logs).
|
- Surface N (scanner honeytrap) is two-layered because nginx only proxies `/api|/ws|/health|/ready` to the orchestrator: `docker/nginx.conf` has a `location ~*` block that `return 444`s the classic root scanner paths (`/.env`, `/.git`, `/wp-login.php`, `/phpmyadmin`, `actuator`, `cgi-bin`, `vendor/`, …) at the edge before they reach the panel (anchored to scanner fingerprints; `/.well-known` + real routes untouched; always on), while `roboco/security.py`'s `_THREAT_BAN_CONFIG` gained `recon`/`sensitive_file`/`cms_probing` so `/api`-path probes that DO reach guard trip an adaptive per-IP redis auto-ban (active mode only; passive logs).
|
||||||
|
- `.github/workflows/release.yml` must build every image `docker-compose.registry.yml` pulls: `roboco-agent-base` and `roboco-agent-grok` build first (outside the loop — the Grok-family images and two interactive Grok roles `FROM` them respectively, so they must exist in the daemon before the loop runs), then a 15-entry `IMAGES` associative array covers the rest — 17 images total. The two Grok sub-images (`roboco-agent-grok-prompter` / `roboco-agent-grok-secretary`) were previously missing from the loop and were never published, so a fresh registry-compose pull 404'd on them; adding an image anywhere in the compose files without a matching `IMAGES` entry (or the two pre-loop builds) silently re-breaks a cold registry pull.
|
||||||
|
|
||||||
|
|
||||||
## Drift from CLAUDE.md
|
## Drift from CLAUDE.md
|
||||||
|
|||||||
@@ -220,8 +220,8 @@ foundation/policy (misc slice)
|
|||||||
| markers accessors | roboco/foundation/policy/content/markers.py | Called by task.py, orchestrator, self_heal_engine, release_proposal, release_manager_engine whenever reading/writing orchestration_markers |
|
| markers accessors | roboco/foundation/policy/content/markers.py | Called by task.py, orchestrator, self_heal_engine, release_proposal, release_manager_engine whenever reading/writing orchestration_markers |
|
||||||
|
|
||||||
## Config Flags
|
## Config Flags
|
||||||
- ROBOCO_AGENT_TOOL_CALL_WARN (consumed by agent_sdk, defaults from BudgetPolicy.tool_call_warn_at=50)
|
- ROBOCO_AGENT_TOOL_CALL_WARN (consumed by agent_sdk, defaults from BudgetPolicy.tool_call_warn_at=100)
|
||||||
- ROBOCO_AGENT_TOOL_CALL_HALT (defaults tool_call_halt_at=150)
|
- ROBOCO_AGENT_TOOL_CALL_HALT (defaults tool_call_halt_at=300; raised from 150 — the prior cap repeatedly halted legitimate multi-file work mid-task)
|
||||||
- ROBOCO_AGENT_LOOP_THRESHOLD (defaults loop_threshold=3)
|
- ROBOCO_AGENT_LOOP_THRESHOLD (defaults loop_threshold=3)
|
||||||
- ROBOCO_AGENT_LOOP_WINDOW (defaults loop_window=10)
|
- ROBOCO_AGENT_LOOP_WINDOW (defaults loop_window=10)
|
||||||
- ROBOCO_AGENT_LOOP_ACTION (defaults loop_action='halt')
|
- ROBOCO_AGENT_LOOP_ACTION (defaults loop_action='halt')
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ The AgentOrchestrator is the runtime brain of RoboCo: it owns the per-agent Dock
|
|||||||
| AgentOrchestrator.spawn_agent | method | roboco/runtime/orchestrator.py:2141 | Chokepoint spawn entry: validate path-segment, refuse human-only roles, readiness gate, resolve git context, provider-park pre-check, TOCTOU re-check, prepare + launch. |
|
| AgentOrchestrator.spawn_agent | method | roboco/runtime/orchestrator.py:2141 | Chokepoint spawn entry: validate path-segment, refuse human-only roles, readiness gate, resolve git context, provider-park pre-check, TOCTOU re-check, prepare + launch. |
|
||||||
| AgentOrchestrator._resolve_host_paths | method | roboco/runtime/orchestrator.py:2297 | Compute host bind-mount paths for containerized vs host runtime (docs/workspaces/claude/mcp/grok-usage/prompt/settings/briefing). |
|
| AgentOrchestrator._resolve_host_paths | method | roboco/runtime/orchestrator.py:2297 | Compute host bind-mount paths for containerized vs host runtime (docs/workspaces/claude/mcp/grok-usage/prompt/settings/briefing). |
|
||||||
| AgentOrchestrator._spawn_container | method | roboco/runtime/orchestrator.py:2664 | Run the Claude Code SDK container via docker with all mounts/env/manifest/cwd args; returns container id. |
|
| AgentOrchestrator._spawn_container | method | roboco/runtime/orchestrator.py:2664 | Run the Claude Code SDK container via docker with all mounts/env/manifest/cwd args; returns container id. |
|
||||||
| AgentOrchestrator._generate_mcp_config | method | roboco/runtime/orchestrator.py:2779 | Write the per-agent MCP servers config (flow/do/git-readonly/optimal/docs) to disk and return the path. |
|
| AgentOrchestrator._generate_mcp_config | method | roboco/runtime/orchestrator.py:2779 | Write the per-agent MCP servers config (flow/do/git-readonly/optimal/docs) to disk and return the path; delegates the role-scoped extras to `_append_role_scoped_mcp_servers`. |
|
||||||
|
| AgentOrchestrator._append_role_scoped_mcp_servers | method | roboco/runtime/orchestrator.py:3541 | Registers `roboco-docs` (documenter/PM/board roles), `roboco-search` (Board+PM roles, `research_enabled`), and `playwright` (`fe-qa`/`ux-qa` ONLY — role-gated via `agent_role == "qa"` + team in `{frontend, ux_ui}`, not image-gated, so `ux-dev` never sees it despite sharing `agent-ux`'s image) — `command: /app/scripts/playwright-mcp-entrypoint.sh`, no args (the wrapper resolves the image's own baked `chromium-headless-shell`). Split out of `_generate_mcp_config` to keep it under the xenon complexity budget. |
|
||||||
| AgentOrchestrator._generate_composed_prompt | method | roboco/runtime/orchestrator.py:2946 | Compose the spawn prompt (identity + task briefing + ambient conventions block + tool-load block) via compose_prompt. |
|
| AgentOrchestrator._generate_composed_prompt | method | roboco/runtime/orchestrator.py:2946 | Compose the spawn prompt (identity + task briefing + ambient conventions block + tool-load block) via compose_prompt. |
|
||||||
| AgentOrchestrator._resolve_conventions_ambient | method | roboco/runtime/orchestrator.py:2998 | Resolve the in-scope projects for the ambient architectural-conventions block (single repo / product / ad-hoc cell map). |
|
| AgentOrchestrator._resolve_conventions_ambient | method | roboco/runtime/orchestrator.py:2998 | Resolve the in-scope projects for the ambient architectural-conventions block (single repo / product / ad-hoc cell map). |
|
||||||
| AgentOrchestrator._readiness_gate | method | roboco/runtime/orchestrator.py:3104 | Pre-flight refusal: missing project/cell-map, missing AC, role/status mismatch, missing git token, bad task shape, unmet dependencies. |
|
| AgentOrchestrator._readiness_gate | method | roboco/runtime/orchestrator.py:3104 | Pre-flight refusal: missing project/cell-map, missing AC, role/status mismatch, missing git token, bad task shape, unmet dependencies. |
|
||||||
@@ -93,7 +94,11 @@ The AgentOrchestrator is the runtime brain of RoboCo: it owns the per-agent Dock
|
|||||||
| AgentOrchestrator._dispatch_roadmap_exploration | method | roboco/runtime/orchestrator.py:10284 | One-shot Product-Owner spawn to author a themed roadmap cycle; bypasses the two-reviewer board-review-pair machinery (PO-solo in v1). |
|
| AgentOrchestrator._dispatch_roadmap_exploration | method | roboco/runtime/orchestrator.py:10284 | One-shot Product-Owner spawn to author a themed roadmap cycle; bypasses the two-reviewer board-review-pair machinery (PO-solo in v1). |
|
||||||
| AgentOrchestrator._dispatch_feature_spotlight_exploration | method | roboco/runtime/orchestrator.py:10424 | One-shot Head-of-Marketing spawn to investigate + call `propose_feature_spotlight` once; `_dispatch_pm_work` routes `source=x_feature_exploration` to it before the generic board-review check, mirroring the roadmap source's own early branch. |
|
| AgentOrchestrator._dispatch_feature_spotlight_exploration | method | roboco/runtime/orchestrator.py:10424 | One-shot Head-of-Marketing spawn to investigate + call `propose_feature_spotlight` once; `_dispatch_pm_work` routes `source=x_feature_exploration` to it before the generic board-review check, mirroring the roadmap source's own early branch. |
|
||||||
| AgentOrchestrator._build_feature_spotlight_prompt | method | roboco/runtime/orchestrator.py:12827 | Render the one-shot feature-spotlight exploration prompt (investigate CHANGELOG/flags/docs/charter/KB, call `propose_feature_spotlight` once, then `i_am_idle`). |
|
| AgentOrchestrator._build_feature_spotlight_prompt | method | roboco/runtime/orchestrator.py:12827 | Render the one-shot feature-spotlight exploration prompt (investigate CHANGELOG/flags/docs/charter/KB, call `propose_feature_spotlight` once, then `i_am_idle`). |
|
||||||
| AgentOrchestrator._dispatch_all_work | method | roboco/runtime/orchestrator.py:8814 | Reset tick-handled set, reap stale claims, enforce grok budget, run all 17 dispatchers under one httpx client with per-dispatcher isolation. |
|
| AgentOrchestrator._dispatch_all_work | method | roboco/runtime/orchestrator.py:10906 | Reset tick-handled set, reap stale claims, enforce grok budget, run all 18 dispatchers (added `vault_curation_work`) under one httpx client with per-dispatcher isolation. |
|
||||||
|
| 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._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._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. |
|
| 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. |
|
||||||
| AgentOrchestrator._AUTO_SUBMIT_VERB_BY_ROLE | ClassVar[dict] | roboco/runtime/orchestrator.py:10650 | Wave-1 PR-gate turn cut: maps cell_pm -> (cell_pm, submit_up) and main_pm -> (main_pm, submit_root), the flow route+verb that assembles the parent's PR for each coordinator role. |
|
| AgentOrchestrator._AUTO_SUBMIT_VERB_BY_ROLE | ClassVar[dict] | roboco/runtime/orchestrator.py:10650 | Wave-1 PR-gate turn cut: maps cell_pm -> (cell_pm, submit_up) and main_pm -> (main_pm, submit_root), the flow route+verb that assembles the parent's PR for each coordinator role. |
|
||||||
@@ -206,6 +211,8 @@ 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_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.
|
- `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.
|
- 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.
|
||||||
|
- 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
|
## Gotchas
|
||||||
- Respawn-tracker rows are restored at startup and re-stamped to live values; terminal/missing-task rows are evicted, so a stale row can't gate a fresh task. The upsert is race-free but fire-and-forget persists are ordered by `_respawn_persist_lock` acquisition (= schedule order); a stale persist resolving after a fresh one would otherwise re-burn the strike threshold on restart.
|
- Respawn-tracker rows are restored at startup and re-stamped to live values; terminal/missing-task rows are evicted, so a stale row can't gate a fresh task. The upsert is race-free but fire-and-forget persists are ordered by `_respawn_persist_lock` acquisition (= schedule order); a stale persist resolving after a fresh one would otherwise re-burn the strike threshold on restart.
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ The two choreographer mixins that implement the PR-reviewer's two distinct surfa
|
|||||||
| PRGateMixin._gate_review_event_verdict | staticmethod | roboco/services/gateway/choreographer/pr_gate.py:555 | Map gate verb → (review event, verdict label): pr_pass → APPROVE/PASSED, pr_fail → REQUEST_CHANGES/CHANGES REQUESTED — both downgraded to COMMENT on root→master (is_root) |
|
| PRGateMixin._gate_review_event_verdict | staticmethod | roboco/services/gateway/choreographer/pr_gate.py:555 | Map gate verb → (review event, verdict label): pr_pass → APPROVE/PASSED, pr_fail → REQUEST_CHANGES/CHANGES REQUESTED — both downgraded to COMMENT on root→master (is_root) |
|
||||||
| PRGateMixin._gate_review_body | staticmethod | roboco/services/gateway/choreographer/pr_gate.py:568 | Render the gate-review comment body posted to the assembled PR (includes CEO-only footer for root→master PRs) |
|
| PRGateMixin._gate_review_body | staticmethod | roboco/services/gateway/choreographer/pr_gate.py:568 | Render the gate-review comment body posted to the assembled PR (includes CEO-only footer for root→master PRs) |
|
||||||
| PRGateMixin._build_gate_review_evidence | method | roboco/services/gateway/choreographer/pr_gate.py:697 | Inline evidence for claim_gate_review: assembled branch diff + pr_number/pr_url + acceptance_criteria + is_assembled_pr |
|
| PRGateMixin._build_gate_review_evidence | method | roboco/services/gateway/choreographer/pr_gate.py:697 | Inline evidence for claim_gate_review: assembled branch diff + pr_number/pr_url + acceptance_criteria + is_assembled_pr |
|
||||||
|
| PRGateMixin._gate_diff_parent | method | roboco/services/gateway/choreographer/pr_gate.py:920 | The assembled task's REAL parent branch (via `resolve_parent_branch`, reading the parent TASK's own `branch_name`) or None for a branchless task; threaded as `preferred_parent` into `git.diff` (claim_gate_review evidence) and the conventions guard. Replaces the string-derived `parent_branch_for`, which reused the child branch's own team segment and was wrong across every cross-team hop (e.g. a frontend child of a main_pm root). Fail-open on a lookup error (falls back to the derived-base default), like every other `resolve_parent_branch` call site. |
|
||||||
| PRReviewerMixin | class | roboco/services/gateway/choreographer/pr_review.py:44 | Mixin: inbound external/fork PR review verbs (claim_pr_review/post_pr_review) + helpers; read-only, never checks out contributor code |
|
| PRReviewerMixin | class | roboco/services/gateway/choreographer/pr_review.py:44 | Mixin: inbound external/fork PR review verbs (claim_pr_review/post_pr_review) + helpers; read-only, never checks out contributor code |
|
||||||
| PRReviewerMixin.claim_pr_review | method | roboco/services/gateway/choreographer/pr_review.py:47 | Reviewer claims an external-PR review task (pending→in_progress via task.pr_review_claim, branch-gate exempt); returns contributor diff inline read-only |
|
| PRReviewerMixin.claim_pr_review | method | roboco/services/gateway/choreographer/pr_review.py:47 | Reviewer claims an external-PR review task (pending→in_progress via task.pr_review_claim, branch-gate exempt); returns contributor diff inline read-only |
|
||||||
| PRReviewerMixin._build_pr_review_content | staticmethod | roboco/services/gateway/choreographer/pr_review.py:124 | Validate summary+findings+event into a PrReviewContent via validate_content, or return an invalid_state Envelope |
|
| PRReviewerMixin._build_pr_review_content | staticmethod | roboco/services/gateway/choreographer/pr_review.py:124 | Validate summary+findings+event into a PrReviewContent via validate_content, or return an invalid_state Envelope |
|
||||||
@@ -143,6 +144,7 @@ pr-gate-review slice
|
|||||||
- claim_gate_review does NOT transition the task (status stays awaiting_pr_review) — this is intentional so pr_pass/pr_fail's source-status still matches. A reviewer who claims but never decides leaves the task assigned but still awaiting_pr_review; the stale-claim reaper path is the recovery.
|
- claim_gate_review does NOT transition the task (status stays awaiting_pr_review) — this is intentional so pr_pass/pr_fail's source-status still matches. A reviewer who claims but never decides leaves the task assigned but still awaiting_pr_review; the stale-claim reaper path is the recovery.
|
||||||
- PRReviewerMixin.post_pr_review runs content gates BEFORE _resolve_post_body, but _resolve_post_body itself can return an Envelope (malformed findings) which is then handled — the verdict_consistency_gate already ran on the (event, findings) pair, so a malformed-findings Envelope is a distinct later failure.
|
- PRReviewerMixin.post_pr_review runs content gates BEFORE _resolve_post_body, but _resolve_post_body itself can return an Envelope (malformed findings) which is then handled — the verdict_consistency_gate already ran on the (event, findings) pair, so a malformed-findings Envelope is a distinct later failure.
|
||||||
- resolve_task_project_slug was extracted to module-level specifically so _impl.py's _LegacyChoreographer (which does NOT inherit ChoreographerHelpers) can reach it; the mixin method _project_slug_for is now a one-line delegate. Changing the resolver signature would break both the mixin and the _impl.py unchanged-PR gate.
|
- resolve_task_project_slug was extracted to module-level specifically so _impl.py's _LegacyChoreographer (which does NOT inherit ChoreographerHelpers) can reach it; the mixin method _project_slug_for is now a one-line delegate. Changing the resolver signature would break both the mixin and the _impl.py unchanged-PR gate.
|
||||||
|
- Before `_gate_diff_parent` (#444/#454), the gate's evidence diff and the conventions guard derived their base via `parent_branch_for` string surgery on the child's OWN branch name — correct for a same-team hop (cell dev → cell PM) but wrong for a cross-team hop (a frontend child of a main_pm root derives a ref that never existed) and silently fell back to the repo default branch, so the reviewer judged inherited base-branch content as the task's own work. `_gate_diff_parent` is now consulted only when no explicit `base` is given, so the pinned literal-base contract (`base="HEAD~1"`) and every other diff caller (QA/doc/content) are untouched; it is skipped entirely while `conventions_enabled` is off since only the conventions guard consumes it in `_pr_pass_blocked`.
|
||||||
|
|
||||||
|
|
||||||
## Drift from CLAUDE.md
|
## Drift from CLAUDE.md
|
||||||
@@ -168,6 +170,7 @@ pr-gate-review slice
|
|||||||
> Post-snapshot updates (since 2026-06-29): two commits touched this slice.
|
> Post-snapshot updates (since 2026-06-29): two commits touched this slice.
|
||||||
> - **536bbb64** (Chore/all/logical gaps sweep #286, 2026-06-30): pr_gate.py — (a) `_post_gate_review_to_pr` wraps slug-resolution in try/except so a malformed cell_map mapping can no longer 500 the reviewer after a committed gate transition (#82 FIXED); (b) `_is_hand_formatted_verdict` regex anchored to line-start so quoted/indented PR headers no longer false-refuse post_pr_review (#188 FIXED); (c) `_re_stamp_pr_fail_head_sha_if_advanced` new method — re-captures head SHA after the transition commits and re-stamps the verdict note only if it advanced, closing the stale-SHA false-allow window (#189 FIXED); (d) `claim_gate_review` passes `skip_dev_guards=True` to `_run_claim_guards` so already_active/paused/lane guards never block a pr_reviewer from claiming a gate review (#192 FIXED); (e) two new static helpers extracted from `_post_gate_review_to_pr`: `_gate_review_event_verdict` and `_gate_review_body`. The unchanged-PR guard's fail-open slug/git error now logs a warning so a regression cannot silently disable the loop-stopper (#5/#222).
|
> - **536bbb64** (Chore/all/logical gaps sweep #286, 2026-06-30): pr_gate.py — (a) `_post_gate_review_to_pr` wraps slug-resolution in try/except so a malformed cell_map mapping can no longer 500 the reviewer after a committed gate transition (#82 FIXED); (b) `_is_hand_formatted_verdict` regex anchored to line-start so quoted/indented PR headers no longer false-refuse post_pr_review (#188 FIXED); (c) `_re_stamp_pr_fail_head_sha_if_advanced` new method — re-captures head SHA after the transition commits and re-stamps the verdict note only if it advanced, closing the stale-SHA false-allow window (#189 FIXED); (d) `claim_gate_review` passes `skip_dev_guards=True` to `_run_claim_guards` so already_active/paused/lane guards never block a pr_reviewer from claiming a gate review (#192 FIXED); (e) two new static helpers extracted from `_post_gate_review_to_pr`: `_gate_review_event_verdict` and `_gate_review_body`. The unchanged-PR guard's fail-open slug/git error now logs a warning so a regression cannot silently disable the loop-stopper (#5/#222).
|
||||||
> - **f90565ea** ([sweep] pr_gate: classify MegaTask root-subtask as root #608, 2026-06-30): `_post_gate_review_to_pr` now uses `is_batch_root_subtask` (imported from `roboco.foundation.policy.batch`) in addition to `parent_task_id is None` to identify root→master PRs. A MegaTask root-subtask (parent=umbrella, batch_id set) opens its own root→master PR but previously got APPROVE/REQUEST_CHANGES instead of COMMENT — fix prevents a single-approval branch-protection rule from allowing a non-CEO merge.
|
> - **f90565ea** ([sweep] pr_gate: classify MegaTask root-subtask as root #608, 2026-06-30): `_post_gate_review_to_pr` now uses `is_batch_root_subtask` (imported from `roboco.foundation.policy.batch`) in addition to `parent_task_id is None` to identify root→master PRs. A MegaTask root-subtask (parent=umbrella, batch_id set) opens its own root→master PR but previously got APPROVE/REQUEST_CHANGES instead of COMMENT — fix prevents a single-approval branch-protection rule from allowing a non-CEO merge.
|
||||||
|
> - **7ff70ab5** (fix(gateway): gate review diffs against the task's real parent branch #444/#454, 2026-07-10): new `_gate_diff_parent` + `preferred_parent` param threaded through `git.diff` / `list_changed_files` / `conventions_check_for_task`, resolving the assembled task's real parent branch from the parent TASK's own `branch_name` instead of string surgery on the child branch name — fixes a live cross-team false-fail (bounced a goals-tab fix three times) where the gate attributed inherited base-branch content to the task under review.
|
||||||
|
|
||||||
## Regression Risks
|
## Regression Risks
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,8 @@ The product / strategy / research / pitch slice covers the "company layer" above
|
|||||||
| `XEngine` | class | x_engine.py:150 | Dormant "engine 4": mirrors the release-manager "detect → originate a CEO-gated artifact → hold" shape across THREE responsibilities — release posts, mention replies, feature spotlights |
|
| `XEngine` | class | x_engine.py:150 | Dormant "engine 4": mirrors the release-manager "detect → originate a CEO-gated artifact → hold" shape across THREE responsibilities — release posts, mention replies, feature spotlights |
|
||||||
| `XEngine._voice_guide` | method | x_engine.py:173 | Baseline house-voice constant (`_HOM_VOICE`) plus the CEO's `company_goals.brand_voice` sample when set — feeds release/reply prompts AND is the mechanism the HoM identity file points to for its own drafting |
|
| `XEngine._voice_guide` | method | x_engine.py:173 | Baseline house-voice constant (`_HOM_VOICE`) plus the CEO's `company_goals.brand_voice` sample when set — feeds release/reply prompts AND is the mechanism the HoM identity file points to for its own drafting |
|
||||||
| `XEngine.draft_release_post` | method | x_engine.py:192 | Event-driven hook (called from `ReleaseProposalService.approve`'s publish-success branch); local-model-drafted, deduped per version, capped by `x_max_open_posts` |
|
| `XEngine.draft_release_post` | method | x_engine.py:192 | Event-driven hook (called from `ReleaseProposalService.approve`'s publish-success branch); local-model-drafted, deduped per version, capped by `x_max_open_posts` |
|
||||||
| `XEngine.run_cycle` | method | x_engine.py:255 | Periodic mentions poll; no-op unless `x_engine_enabled` AND `x_replies_enabled`; filters bot-like/low-engagement mentions, dedupes by mention id (`XSeenMentionTable`) |
|
| `XEngine.run_cycle` | method | x_engine.py:255 | Periodic mentions poll; no-op unless `x_engine_enabled` AND `x_replies_enabled`; filters bot-like/low-engagement mentions, dedupes by mention id (`XSeenMentionTable`); each mention's text is run through `screen_external_text` before the local-model reply prompt sees it |
|
||||||
|
| `screen_external_text` | function | foundation/policy/injection_guard.py:95 | Shared screen-and-neutralize guard for unattended attacker-writable text feeds (X mentions, vault inbox notes): wraps the text in an untrusted-content envelope and flags any matched injection-pattern LINE inline — nothing is removed, so the CEO-facing draft still shows what the source really said |
|
||||||
| `XEngine.open_feature_spotlight_exploration` | method | x_engine.py:337 | No-ops unless `x_engine_enabled` AND `x_feature_spotlight_enabled`, no creds, a cycle already open, the open-post cap reached, or project unresolvable; else opens ONE held PENDING exploration task for the Head of Marketing (`source=x_feature_exploration`) carrying a `x_seen_features` marker snapshot |
|
| `XEngine.open_feature_spotlight_exploration` | method | x_engine.py:337 | No-ops unless `x_engine_enabled` AND `x_feature_spotlight_enabled`, no creds, a cycle already open, the open-post cap reached, or project unresolvable; else opens ONE held PENDING exploration task for the Head of Marketing (`source=x_feature_exploration`) carrying a `x_seen_features` marker snapshot |
|
||||||
| `XEngine.materialize_feature_spotlight` | method | x_engine.py:433 | Called from the `propose_feature_spotlight` do-tool: marks the feature slug seen (`XSeenFeatureTable`), creates the held draft (`source=x_feature`, identical shape to a release/reply draft), completes the exploration task |
|
| `XEngine.materialize_feature_spotlight` | method | x_engine.py:433 | Called from the `propose_feature_spotlight` do-tool: marks the feature slug seen (`XSeenFeatureTable`), creates the held draft (`source=x_feature`, identical shape to a release/reply draft), completes the exploration task |
|
||||||
| `XPostService.approve` | method | x_post_service.py:77 | The ONLY caller of `x_client.post_tweet`; Redis single-flight lock, re-reads task under lock, idempotent on an already-posted draft |
|
| `XPostService.approve` | method | x_post_service.py:77 | The ONLY caller of `x_client.post_tweet`; Redis single-flight lock, re-reads task under lock, idempotent on an already-posted draft |
|
||||||
|
|||||||
@@ -24,9 +24,15 @@
|
|||||||
| `admin_set_status` | method | task.py:2060 | Privileged override (bypass validator); restores pre-block owner; still emits audit. Post-#2176: the blocked→pending/in_progress restore path now attributes the audit row to the admin actor (not the restored owner) and emits a `task.admin_override` row (`forced=False, restore=True`) independent of the `force` flag. |
|
| `admin_set_status` | method | task.py:2060 | Privileged override (bypass validator); restores pre-block owner; still emits audit. Post-#2176: the blocked→pending/in_progress restore path now attributes the audit row to the admin actor (not the restored owner) and emits a `task.admin_override` row (`forced=False, restore=True`) independent of the `force` flag. |
|
||||||
| `_restore_block_ownership` | method | task.py:8526 | Factored out of `_apply_pre_block_restore` (b3558d4e complexity split): applies snapshotted status/owner restore (branchless in_progress→pending divert), returns `(pre_status, restored_status, restored_owner)`. |
|
| `_restore_block_ownership` | method | task.py:8526 | Factored out of `_apply_pre_block_restore` (b3558d4e complexity split): applies snapshotted status/owner restore (branchless in_progress→pending divert), returns `(pre_status, restored_status, restored_owner)`. |
|
||||||
| `_emit_admin_override_audit` | method | task.py:8555 | Factored out of `_apply_pre_block_restore`: writes `task.admin_override` audit row for admin-triggered blocked restores (`forced=False, restore=True`). |
|
| `_emit_admin_override_audit` | method | task.py:8555 | Factored out of `_apply_pre_block_restore`: writes `task.admin_override` audit row for admin-triggered blocked restores (`forced=False, restore=True`). |
|
||||||
| `claim` | method | task.py:2469 | Lock + preconditions + finalize; calls `_validate_and_set_status(claimed)`. |
|
| `claim` | method | task.py:3112 | `FOR UPDATE` lock + `_validate_claim_preconditions` + `_finalize_claim`; calls `_validate_and_set_status(claimed)`. |
|
||||||
| `_finalize_claim` | method | task.py:2282 | Work-session create/inherit, branch cut, proactive-context injection. |
|
| `_validate_claim_preconditions` | method | task.py:2883 | Per-claim validator chain: status, `_claim_blocked_by_sequencing` (dependency + sequence), team, pre-assignment theft, self-review. |
|
||||||
| `_inject_proactive_context` | method | task.py:2511 | Briefing injection at claim (institutional memory when `org_memory_enabled`). |
|
| `_claim_blocked_by_sequence` | method | task.py:2805 | Strict sibling-sequence gate: a PENDING/`needs_revision` task with parent + effective `sequence` (`COALESCE(sequence, 0)`) N is held while any same-parent sibling with a strictly lower effective sequence is non-terminal — assignee-blind, independent of `dependency_ids`. Ties run parallel; cancelled siblings never block. |
|
||||||
|
| `_claim_blocked_by_dependencies` | method | task.py:2781 | `unmet_dependency` TIMING gate: refuses claim while any `dependency_ids` entry is non-terminal. |
|
||||||
|
| `is_pending_claim_blocked` | method | task.py:2864 | Read-only wrapper over `_claim_blocked_by_sequencing` (dependency OR sequence) so the orchestrator dispatcher can filter a doomed claim before attempting it (`_pending_claim_blocked` in orchestrator.py). |
|
||||||
|
| `stamp_wave_sequence` | method | task.py:7452 | Stamps a freshly delegated subtask's `sequence` as `1 + max(sequence of each same-parent dependency target)`, or `0` when independent — so independent siblings tie (parallel under the sequence gate) while colliding/ordered work ascends. Runs POST-wiring (after the collision DAG / cross-cell edges land); PM-authored sequences are never rewritten. |
|
||||||
|
| `_apply_dependency_lineage` / `_merge_one_dependency` | method | task.py:2308 / 2337 | Claim-time content assist (not a gate): merges each same-repo dependency's landed work into a freshly cut branch when it lies outside the branch's own ancestor chain (`GitService.merge_dependency_lineage`); a real conflict aborts the merge and stamps a `dependency_lineage_conflict` transition note instead of failing the claim. |
|
||||||
|
| `_finalize_claim` | method | task.py:2925 | Work-session create/inherit, branch cut, proactive-context injection. |
|
||||||
|
| `_inject_proactive_context` | method | task.py:3154 | Briefing injection at claim (institutional memory when `org_memory_enabled`). |
|
||||||
| `_completion_learnings_for` | method | task.py:2798 | Distill one lesson (ON) vs legacy raw capture (OFF). |
|
| `_completion_learnings_for` | method | task.py:2798 | Distill one lesson (ON) vs legacy raw capture (OFF). |
|
||||||
| `_extract_completion_learnings` | method | task.py:2837 | Fire-and-forget learning record + RAG indexing. |
|
| `_extract_completion_learnings` | method | task.py:2837 | Fire-and-forget learning record + RAG indexing. |
|
||||||
| `start` | method | task.py:3354 | `claimed→in_progress`. |
|
| `start` | method | task.py:3354 | `claimed→in_progress`. |
|
||||||
@@ -84,7 +90,7 @@ stateDiagram-v2
|
|||||||
- State core: `_validate_and_set_status`, `_emit_status_transition_audit`, `admin_set_status`, `_restore_block_ownership`, `_emit_admin_override_audit`
|
- State core: `_validate_and_set_status`, `_emit_status_transition_audit`, `admin_set_status`, `_restore_block_ownership`, `_emit_admin_override_audit`
|
||||||
- Create/shape: `create`, `_validate_parent_depth`, `_validate_batch_membership`, `activate`
|
- Create/shape: `create`, `_validate_parent_depth`, `_validate_batch_membership`, `activate`
|
||||||
- Branch/worktree: `_ensure_branch_for_task`, `_auto_create_branch`, `_remove_task_worktree*`
|
- Branch/worktree: `_ensure_branch_for_task`, `_auto_create_branch`, `_remove_task_worktree*`
|
||||||
- Claim: `claim`, `_finalize_claim`, `_inject_proactive_context`, `acquire_*_lock`
|
- Claim: `claim`, `_validate_claim_preconditions`, `_claim_blocked_by_sequence`, `_claim_blocked_by_dependencies`, `_finalize_claim`, `_apply_dependency_lineage`, `_inject_proactive_context`, `acquire_*_lock`
|
||||||
- Lifecycle verbs: `start`, `block*`, `unblock`, `pause`, `resume`, `submit_for_qa`, `pass_qa`, `fail_qa`, `docs_complete`, `submit_for_pm_review`
|
- Lifecycle verbs: `start`, `block*`, `unblock`, `pause`, `resume`, `submit_for_qa`, `pass_qa`, `fail_qa`, `docs_complete`, `submit_for_pm_review`
|
||||||
- Completion: `complete`, `_apply_complete_approval_chain`, `ceo_approve`, `ceo_reject`, `cancel`
|
- Completion: `complete`, `_apply_complete_approval_chain`, `ceo_approve`, `ceo_reject`, `cancel`
|
||||||
- Rework routing: `fail_qa`, `_resolve_revision_dev`, `ceo_reject`
|
- Rework routing: `fail_qa`, `_resolve_revision_dev`, `ceo_reject`
|
||||||
@@ -122,6 +128,8 @@ stateDiagram-v2
|
|||||||
- Branchless/umbrella/external-review tasks are exempt from the branch gate inside `GitContext` (task.py:597-611); umbrella is also exempt from the `awaiting_pm_review→awaiting_ceo_approval` pr_number gate.
|
- Branchless/umbrella/external-review tasks are exempt from the branch gate inside `GitContext` (task.py:597-611); umbrella is also exempt from the `awaiting_pm_review→awaiting_ceo_approval` pr_number gate.
|
||||||
- `complete()` requires PR merged (`_assert_pr_merged_for_complete`) EXCEPT branchless roots; `ceo_approve` separately checks `work_session.pr_status=="merged"` and refuses otherwise.
|
- `complete()` requires PR merged (`_assert_pr_merged_for_complete`) EXCEPT branchless roots; `ceo_approve` separately checks `work_session.pr_status=="merged"` and refuses otherwise.
|
||||||
- Background indexing/learning/cleanup tasks are tracked on `self._background_tasks` and are best-effort — a failure never blocks the transition.
|
- Background indexing/learning/cleanup tasks are tracked on `self._background_tasks` and are best-effort — a failure never blocks the transition.
|
||||||
|
- The sequence gate (`_claim_blocked_by_sequence`) is enforced ONLY in `_validate_claim_preconditions`, i.e. inside `claim` itself — both the gateway claim verbs AND the orchestrator's raw dispatch claim cross it because they both funnel through `TaskService.claim`, unlike the pre-#382 dependency gate which briefly lived only on the gateway side. Any future claim path that bypasses `TaskService.claim` (a raw `admin_set_status`, for instance) does NOT get sequence enforcement.
|
||||||
|
- `_apply_dependency_lineage` is scoped to SAME-REPO dependencies only (`dep_task.project_id != ctx.project.id` short-circuits) — a cross-repo dependency edge (e.g. a MegaTask root-subtask in another project) has no shared git history to merge and is silently skipped; the dependency TIMING gate still holds the claim regardless of repo.
|
||||||
|
|
||||||
## Drift from CLAUDE.md
|
## Drift from CLAUDE.md
|
||||||
- CLAUDE.md states ceo_reject "~4779 skips _validate_and_set_status in branchless path". Actual: branchless branch of `ceo_reject` is at task.py:5488 and routes through `admin_set_status` (which DOES emit audit at task.py:2100). The non-branchless branch DOES call `_validate_and_set_status` (task.py:5461). No audit gap — the line reference is stale.
|
- CLAUDE.md states ceo_reject "~4779 skips _validate_and_set_status in branchless path". Actual: branchless branch of `ceo_reject` is at task.py:5488 and routes through `admin_set_status` (which DOES emit audit at task.py:2100). The non-branchless branch DOES call `_validate_and_set_status` (task.py:5461). No audit gap — the line reference is stale.
|
||||||
@@ -133,7 +141,7 @@ stateDiagram-v2
|
|||||||
- `15effce0` Chore: 141 Gaps fill-in (#283) — bulk gap closure; transition audit chokepoint + `revision_count` centralization (task.py:685-706), branchless/umbrella git-context exemptions, fail_qa work-session fallback, ceo_reject branchless routing.
|
- `15effce0` Chore: 141 Gaps fill-in (#283) — bulk gap closure; transition audit chokepoint + `revision_count` centralization (task.py:685-706), branchless/umbrella git-context exemptions, fail_qa work-session fallback, ceo_reject branchless routing.
|
||||||
- `3aff6e04` Chore: Close gaps (#285) — follow-on gap close (worktree-on-terminal cleanup F123 Phase C, escalation audit emit, rework routing hardening).
|
- `3aff6e04` Chore: Close gaps (#285) — follow-on gap close (worktree-on-terminal cleanup F123 Phase C, escalation audit emit, rework routing hardening).
|
||||||
|
|
||||||
> Post-snapshot updates (since 2026-06-29): `20f1f9ba` admin_set_status: thread actor_id/actor_role into `_apply_pre_block_restore`; blocked→pending/in_progress restore now attributes the audit row to the admin actor (not the restored owner) and emits a `task.admin_override` row (forced=False, restore=True) independent of the force flag. `b3558d4e` complexity: extract `_restore_block_ownership` (line 8526) + `_emit_admin_override_audit` (line 8555) from `_apply_pre_block_restore` — no behavior change, splits a C-rank block for the xenon gate. `0e7674af` escalate_to_ceo gains `actor_agent_id: UUID | None = None` param stamped as audit_agent_id; push_branch / create_pr / create_root_pr / escalate_to_ceo side-effect handlers in the verb runner now forward actor_agent_id (was dropped, causing wrong workspace or role-only audit attribution).
|
> Post-snapshot updates (since 2026-06-29): `20f1f9ba` admin_set_status: thread actor_id/actor_role into `_apply_pre_block_restore`; blocked→pending/in_progress restore now attributes the audit row to the admin actor (not the restored owner) and emits a `task.admin_override` row (forced=False, restore=True) independent of the force flag. `b3558d4e` complexity: extract `_restore_block_ownership` (line 8526) + `_emit_admin_override_audit` (line 8555) from `_apply_pre_block_restore` — no behavior change, splits a C-rank block for the xenon gate. `0e7674af` escalate_to_ceo gains `actor_agent_id: UUID | None = None` param stamped as audit_agent_id; push_branch / create_pr / create_root_pr / escalate_to_ceo side-effect handlers in the verb runner now forward actor_agent_id (was dropped, causing wrong workspace or role-only audit attribution). `8f3f4236` (#452) "sequence is the bar" — adds `_claim_blocked_by_sequence` + `_validate_claim_preconditions` wiring, `stamp_wave_sequence` (replacing a raw per-sibling delegation ordinal), and migration 069 (`tasks.parent_task_id` index, the sibling probe's hot path). `f2834cf5` (#466) adds `_apply_dependency_lineage`/`_merge_one_dependency`, called from `_create_branch_in_project` right after a fresh branch cut.
|
||||||
|
|
||||||
## Regression Risks
|
## Regression Risks
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
## 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 = `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_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_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` — the `curate_vault` verb
|
||||||
|
- `docs/map/orchestrator.md` — `_dispatch_vault_curation_work` / `_maybe_spawn_vault_curation` / `_vault_intake_loop`
|
||||||
|
- `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)
|
||||||
@@ -90,6 +90,8 @@ The PR-gate turn cut (when every child of an assembled parent is terminal, `_try
|
|||||||
| `ROBOCO_SANDBOX_DB_ENABLED` | `false` | Sandboxed per-agent-spawn test DB/Redis/Mongo: throwaway sibling containers provisioned from the engine registry in `roboco/models/sandbox.py` (postgres:16-alpine / redis:8-alpine / mongo:8), per-project opt-in. The valid-service set is `VALID_SANDBOX_SERVICES` (registry-derived). See "Sandboxed Dev DB/Redis/Mongo" below and `docs/rag/architecture/sandbox-db.md`. |
|
| `ROBOCO_SANDBOX_DB_ENABLED` | `false` | Sandboxed per-agent-spawn test DB/Redis/Mongo: throwaway sibling containers provisioned from the engine registry in `roboco/models/sandbox.py` (postgres:16-alpine / redis:8-alpine / mongo:8), per-project opt-in. The valid-service set is `VALID_SANDBOX_SERVICES` (registry-derived). See "Sandboxed Dev DB/Redis/Mongo" below and `docs/rag/architecture/sandbox-db.md`. |
|
||||||
| `ROBOCO_X_ENGINE_ENABLED` | `false` | The X (Twitter) engine: draft release/mention posts, ALL held for per-post CEO approval. See "X (Twitter) Engine" below and `docs/rag/architecture/x-engine.md`. |
|
| `ROBOCO_X_ENGINE_ENABLED` | `false` | The X (Twitter) engine: draft release/mention posts, ALL held for per-post CEO approval. See "X (Twitter) Engine" below and `docs/rag/architecture/x-engine.md`. |
|
||||||
| `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_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`. |
|
||||||
|
|
||||||
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).
|
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).
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Obsidian Vault (V1)
|
||||||
|
|
||||||
|
## 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).
|
||||||
|
|
||||||
|
## 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_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. |
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Layout and link stability
|
||||||
|
|
||||||
|
```
|
||||||
|
RoboCo/
|
||||||
|
Tasks/<project-slug>/<title> (<id8>).md
|
||||||
|
Journals/<agent-slug>/<date> <title> (<id8>).md
|
||||||
|
A2A/<date> <agents> (<thread-id8>).md
|
||||||
|
Agents/<slug>.md
|
||||||
|
_meta/ (shipped Dataview/Kanban/graph-group dashboards)
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
- A `note()` (journal entry) writes one immutable file per entry.
|
||||||
|
- A `dm()` (A2A message) appends to a per-thread digest file.
|
||||||
|
|
||||||
|
None of these fail your call if the vault write itself fails — it's logged and swallowed.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## 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
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
- Record private observations via `note(text="...", scope="reflect")`
|
- Record private observations via `note(text="...", scope="reflect")`
|
||||||
- Attach evidence via `evidence(task_id)`
|
- Attach evidence via `evidence(task_id)`
|
||||||
- Search the knowledge base via `roboco_ask_mentor` / `roboco_kb_search`
|
- Search the knowledge base via `roboco_ask_mentor` / `roboco_kb_search`
|
||||||
|
- Curate the Obsidian vault's narrative for a just-completed root task-tree via `curate_vault(task_id, narrative)` — see below (only when `ROBOCO_OBSIDIAN_VAULT_ENABLED`)
|
||||||
|
|
||||||
## What You CANNOT Do
|
## What You CANNOT Do
|
||||||
|
|
||||||
@@ -59,16 +60,20 @@ note(
|
|||||||
evidence(task_id="...") # attach the evidence trail to the finding
|
evidence(task_id="...") # attach the evidence trail to the finding
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Vault curation
|
||||||
|
|
||||||
|
When the Obsidian vault is armed, the orchestrator spawns you once per completed ROOT task (a one-shot, not something you poll for) with the task id and title named in your prompt. Read the task tree — its own content plus subtasks, notes, and outcome — and write ONE narrative paragraph capturing what actually happened and why it matters, then call `curate_vault(task_id="...", narrative="...")` exactly once. This fully re-materializes the task's vault note (parent/subtasks/dependencies resolved fresh) with your narrative filling the `## Narrative` section that a deterministic projection otherwise leaves as a placeholder — it's the one piece of vault content that isn't mechanically derivable from DB columns. The write is idempotent; a retry just re-materializes the same note.
|
||||||
|
|
||||||
## Tool Surface (per-spawn manifest)
|
## Tool Surface (per-spawn manifest)
|
||||||
|
|
||||||
| MCP server | Verbs you can call |
|
| MCP server | Verbs you can call |
|
||||||
|-----------------------|--------------------|
|
|-----------------------|--------------------|
|
||||||
| `roboco-flow` | `triage`, `i_am_idle` |
|
| `roboco-flow` | `triage`, `i_am_idle` |
|
||||||
| `roboco-do` | `note` (scope=`reflect`), `evidence`, `notify_list`, `notify_get` |
|
| `roboco-do` | `note` (scope=`reflect`), `evidence`, `notify_list`, `notify_get`, `curate_vault` |
|
||||||
| `roboco-git-readonly` | `roboco_git_status`, `roboco_git_log`, `roboco_git_diff`, `roboco_git_branch_list` |
|
| `roboco-git-readonly` | `roboco_git_status`, `roboco_git_log`, `roboco_git_diff`, `roboco_git_branch_list` |
|
||||||
| `roboco-optimal` | `roboco_ask_mentor`, `roboco_kb_search` |
|
| `roboco-optimal` | `roboco_ask_mentor`, `roboco_kb_search` |
|
||||||
|
|
||||||
**Read-only observer.** No `dm`, `notify`, `commit`, or any write verb is in your manifest. All `Write/Edit` and native git commands are blocked.
|
**Read-only observer.** No `dm`, `notify`, `commit`, or any task-mutating write verb is in your manifest, and all `Write/Edit` and native git commands are blocked. `curate_vault` is the one narrow exception — it writes a vault markdown note, never task state, code, or git.
|
||||||
|
|
||||||
## Communication
|
## Communication
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ The `pr_reviewer` role also runs the **in-path gate** on the org's OWN assembled
|
|||||||
|
|
||||||
When the architectural-conventions standard is enabled, `pr_pass` is refused on any block-level convention finding, the same way the developer's `i_am_done` is — the remediation hint points you at the offending `file:line` + the `pr_fail` verb (not `i_am_blocked`). When toolchain matching is enabled, `pr_pass` is likewise refused on a "broken" toolchain status. Your verdict note is a mandatory structured field (`pr_reviewer_notes`) written at `pr_pass` / `pr_fail`; it is persisted structured with a derived text mirror.
|
When the architectural-conventions standard is enabled, `pr_pass` is refused on any block-level convention finding, the same way the developer's `i_am_done` is — the remediation hint points you at the offending `file:line` + the `pr_fail` verb (not `i_am_blocked`). When toolchain matching is enabled, `pr_pass` is likewise refused on a "broken" toolchain status. Your verdict note is a mandatory structured field (`pr_reviewer_notes`) written at `pr_pass` / `pr_fail`; it is persisted structured with a derived text mirror.
|
||||||
|
|
||||||
|
`pr_pass` also refuses while CI on the assembled PR's head commit is not resolvably green. Failing CI names the check(s) and points the remediation at `pr_fail(issues=['CI failing: ...'])`; pending / not-yet-scheduled / a transient GitHub API error are framed as retryable — wait and call `pr_pass` again once CI resolves, not a defect to route back to the dev via `pr_fail` unless the diff itself is also bad. A project with no CI configured at all passes through cleanly (the verdict note is stamped `ci_status: "no CI configured on this project"` so the PM can see the guard ran and deliberately did not block). Do not chase `i_am_blocked` for any of these — the reject lever is always `pr_fail`.
|
||||||
|
|
||||||
|
**The per-AC evidence-walk is non-negotiable.** Do not assert "criteria met" from a skim. Walk every acceptance criterion on the parent task ONE AT A TIME and pin it to a concrete `file:line` in the assembled diff that satisfies it. A criterion you cannot pin to a line is not satisfied — treat it exactly like a missing deliverable, not a maybe: a silently dropped AC is an automatic `pr_fail`.
|
||||||
|
|
||||||
|
The diff you review is resolved against the task's REAL parent branch (its recorded `branch_name`, not a string-derived guess) — so a cross-team hop (a frontend cell child of a main_pm root, for example) shows you only what this task actually added, never the inherited base-branch content underneath it.
|
||||||
|
|
||||||
You cannot `pr_pass` / `pr_fail` an assembled PR you authored (self-review guard, same shape as QA's). A `claim_gate_review` on your own work returns `not_authorized`.
|
You cannot `pr_pass` / `pr_fail` an assembled PR you authored (self-review guard, same shape as QA's). A `claim_gate_review` on your own work returns `not_authorized`.
|
||||||
|
|
||||||
## What You CAN Do
|
## What You CAN Do
|
||||||
|
|||||||
@@ -29,10 +29,12 @@ The claim verb both claims and starts the task — there is no separate `start`
|
|||||||
|
|
||||||
Exactly one active WorkSession exists per task at a time (enforced in the service layer and by a DB unique index). A re-claim — pool release, reaper unclaim, escalation redirect — supersedes any prior agent's stale active session for that task and re-points the worktree at the new claim.
|
Exactly one active WorkSession exists per task at a time (enforced in the service layer and by a DB unique index). A re-claim — pool release, reaper unclaim, escalation redirect — supersedes any prior agent's stale active session for that task and re-points the worktree at the new claim.
|
||||||
|
|
||||||
|
If your task has `dependency_ids` in the same repo, the fresh branch cut also backfills each dependency's already-landed work when it sits outside your branch's own ancestor chain (a same-parent sibling or a same-project wave usually doesn't need this — the shared base already has it; a cross-subtree or cross-cell dependency edge can). This is a content assist, not a gate: a clean merge is silent, and a real conflict aborts the merge (your branch is left exactly at its cut point) and appends a note to the task naming the conflicting branch and files — resolve it by hand (merge the named branch into yours) before assembling your PR. A cross-repo dependency has no shared git history and is skipped entirely.
|
||||||
|
|
||||||
## Before Claiming
|
## Before Claiming
|
||||||
|
|
||||||
1. Check you have capacity (developers / QA / documenters work one task at a time; **PM coordinators are exempt** — a Main / Cell PM may hold many roots at once, gated only by sequence dependencies)
|
1. Check you have capacity (developers / QA / documenters work one task at a time; **PM coordinators are exempt** — a Main / Cell PM may hold many roots at once, gated only by sequence dependencies)
|
||||||
2. Verify dependencies are completed
|
2. Verify dependencies are completed AND no lower-sequence sibling is still open (see Claiming Rules below)
|
||||||
3. Read task description and acceptance criteria
|
3. Read task description and acceptance criteria
|
||||||
|
|
||||||
## After Claiming
|
## After Claiming
|
||||||
@@ -47,6 +49,7 @@ Exactly one active WorkSession exists per task at a time (enforced in the servic
|
|||||||
- **Self-review prevention**: QA cannot `claim_review` tasks they developed
|
- **Self-review prevention**: QA cannot `claim_review` tasks they developed
|
||||||
- **Self-documentation prevention**: Documenter cannot claim tasks they developed
|
- **Self-documentation prevention**: Documenter cannot claim tasks they developed
|
||||||
- **Branch requirement**: Branch auto-created on `i_will_work_on`
|
- **Branch requirement**: Branch auto-created on `i_will_work_on`
|
||||||
|
- **Sequence order (strict, assignee-blind)**: if a task has a parent and a `sequence` number, it cannot be claimed while any sibling with a strictly lower sequence is still non-terminal — regardless of who owns which task. Siblings on the SAME sequence run in parallel (independent work ties at 0, or at the wave a delegating PM stamped from the collision graph). This is independent of, and stricter than, `dependency_ids`: a claim attempt on a sequence-held task fails even with no unmet dependency. The error names the blocking sibling by title — `unclaim`/wait is the only remedy, there is no override verb. The dispatcher pre-filters sequence-held (and dependency-held) tasks before attempting a claim, so you should rarely see this in practice — but a claim you make directly (rather than via `give_me_work`) can still hit it.
|
||||||
|
|
||||||
## Releasing a Claimed Task
|
## Releasing a Claimed Task
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user