chore(comms): finalize #306 teardown — changelog + purge dangling refs

#306 removed the channels/sessions/messages subsystem but left dangling
references. Agents were still told to call removed verbs at spawn, and a
maintenance script referenced a dropped table.

- prompts (roles/identities/teams/base): drop say/open_session/link_session/
  channels() and the dead Channels sections; comms rows now teach A2A
  (dm + read_a2a); renumber the PM workflow steps the removed open_session
  step left behind
- scripts/reset_runtime_state.sql: drop the dropped chunks_conversations table
- models/events.py: mark the retired SESSION_*/MESSAGE_SENT enum members inert
- mcp/{do,flow}_server.py: drop the dead SESSION_CLOSED error-map key
- panel: drop channels_read/write from AgentPermissions; remove dead channel:
  KB source branch
- pyproject.toml: refresh a ruff-exemption example off the removed verb kwargs
- CHANGELOG: record #306 under [Unreleased]
This commit is contained in:
Renn F
2026-07-04 03:42:04 +02:00
parent 7901ea419e
commit 7cb00611e1
23 changed files with 43 additions and 105 deletions
+17
View File
@@ -6,6 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
## [Unreleased] ## [Unreleased]
### Added
- **A2A is now a delivered inbox, not a write-only log.** The new `read_a2a` verb returns an agent's unread message bodies (atomic, own-sends excluded) and is granted to every delivery role, and the claim briefing's `list_unread_a2a` carries an incoming-only `last_message_preview` (single correlated query, no N+1). A peer's `dm` now actually reaches the recipient's reasoning instead of sitting unread — the gap that motivated retiring the channel/session backbone in the first place.
### Changed
- **A2A + Notifications are the two comms primitives.** With the channel/session backbone gone, coordination rides the task state machine + task details, direct peer contact rides A2A (`dm` + `read_a2a`, same-cell), and formal ack-required signals ride Notifications. The Secretary's company-wide announce reroutes from the retired broadcast channel to a Notification fanned to every agent's inbox. Docs, panel, and CLAUDE.md are rewritten A2A-primary.
### Removed
- **The channels / groups / discussion-sessions / messages subsystem is retired.** Direction was validated first — agents barely read channel/session messages, and A2A bodies never reached the recipient's reasoning — so the whole backbone is gone. Removed the `say`, `open_session`, `link_session`, and `channels()` verbs; `MessagingService`, the five tables + models, and the `ChannelType``agents_config` → permissions → `stream.py` policy cascade; the `CONVERSATIONS` RAG index and channel seeding; the channel/session API routes + the `/ws/channels` / `/ws/sessions` WebSocket streams; and the panel's Communications surface (channel/session views + the auditor channel-feed + a dead communication-metric route). Migration 060 drops the five tables (FK-safe order), the `journal_entries.session_id` column, `chunks_conversations`, and the four enum types — verified 001 → 060 against a real Postgres, single head. `ExtractedMessage` (the extraction pipeline) is retained and confirmed never persisted to a dropped table.
### Fixed
- **Release-manager read clone no longer walks the entire history.** The release-readiness read clone was tagless, so the diff-since-last-tag walk saw no tags and classified all history as unreleased (the observed 729-commit blow-up). The clone now carries tags, so the semver bump and CHANGELOG-completeness checks assess only the real delta.
- **Panel RAG-health errors are labelled by subsystem.** Health error lines in the panel now name which subsystem failed instead of rendering an unattributed error.
## [0.17.0] - 2026-07-03 ## [0.17.0] - 2026-07-03
### Added ### Added
+1 -5
View File
@@ -65,10 +65,6 @@ If `task_handoff` is present, treat the work as in-progress: read these fields f
The briefing also carries `company_goals` — the company's charter (north star, prioritized objectives, constraints, operating policy) set by the CEO. When it is present, let it steer your judgment: favour work and trade-offs that advance the stated objectives and honour the constraints, and flag work that conflicts with them. The charter shapes *how* you do your role's work well — it is never a license to step outside your role. The briefing also carries `company_goals` — the company's charter (north star, prioritized objectives, constraints, operating policy) set by the CEO. When it is present, let it steer your judgment: favour work and trade-offs that advance the stated objectives and honour the constraints, and flag work that conflicts with them. The charter shapes *how* you do your role's work well — it is never a license to step outside your role.
## Channels
Channel arguments take the slug **without** the `#` prefix: `"backend-cell"`, not `"#backend-cell"`. Channel names with `#` may be tolerated but are not correct.
## TodoWrite vs `progress()` ## TodoWrite vs `progress()`
`TodoWrite` is your private session-local scratchpad — track your own immediate next steps with it freely. It does **NOT** surface to the panel and is **NOT** a substitute for `progress(task_id, message, percentage)`. The panel's Progress tab is populated by `progress()` calls; if you record narrative updates via `TodoWrite` instead, QA / PM / CEO see an empty tab. Use TodoWrite for "next 3 steps to remember"; use `progress()` for "what just landed". `TodoWrite` is your private session-local scratchpad — track your own immediate next steps with it freely. It does **NOT** surface to the panel and is **NOT** a substitute for `progress(task_id, message, percentage)`. The panel's Progress tab is populated by `progress()` calls; if you record narrative updates via `TodoWrite` instead, QA / PM / CEO see an empty tab. Use TodoWrite for "next 3 steps to remember"; use `progress()` for "what just landed".
@@ -81,7 +77,7 @@ Channel arguments take the slug **without** the `#` prefix: `"backend-cell"`, no
- `env`/`printenv` is **denied** — secrets are not readable from your container. - `env`/`printenv` is **denied** — secrets are not readable from your container.
- `Edit`/`Write` are scoped to your workspace: `/data/workspaces/{project}/{team}/{your-slug}/`. - `Edit`/`Write` are scoped to your workspace: `/data/workspaces/{project}/{team}/{your-slug}/`.
- Subagents (the `Agent` tool, where granted) are for **parallel research only** — fanning out to read multiple files at once. They are NOT a way to delegate your actual task to another instance of yourself. - Subagents (the `Agent` tool, where granted) are for **parallel research only** — fanning out to read multiple files at once. They are NOT a way to delegate your actual task to another instance of yourself.
- **Everything you write is validated — no filler, no word soup, anywhere.** This holds for *every* free-text field, not just chat: content tools (`say`/`dm`/`note`/`progress`/`notify`/`pitch`/`pr_update`/`open_session`) AND the flow verbs' text — `i_am_blocked(reason=...)`, `i_am_done(notes=...)`, `submit_up`/`submit_root`/`complete(notes=...)`, `escalate_up`/`escalate_to_ceo(reason=...)`, `fail_review`/`pr_fail(issues=[...])`, `pass_review(notes=...)`, `delegate(title=, description=)`. Empty/placeholder text (`asdf`, `wip`, `tbd`, `n/a`, `...`, `x`) and all-filler strings (`wip wip`) are rejected with a remediable envelope; state what actually happened. Structured artifacts must fill their named fields, never a flat phrase: PR reviews take `findings` (each `{file, line, severity, expected, actual}`), QA takes `ac_verdicts` (one per criterion), `decision`/`reflect` take their structured fields, task drafts take `objective`/`the_work`/`acceptance_criteria`. - **Everything you write is validated — no filler, no word soup, anywhere.** This holds for *every* free-text field, not just chat: content tools (`dm`/`note`/`progress`/`notify`/`pitch`/`pr_update`) AND the flow verbs' text — `i_am_blocked(reason=...)`, `i_am_done(notes=...)`, `submit_up`/`submit_root`/`complete(notes=...)`, `escalate_up`/`escalate_to_ceo(reason=...)`, `fail_review`/`pr_fail(issues=[...])`, `pass_review(notes=...)`, `delegate(title=, description=)`. Empty/placeholder text (`asdf`, `wip`, `tbd`, `n/a`, `...`, `x`) and all-filler strings (`wip wip`) are rejected with a remediable envelope; state what actually happened. Structured artifacts must fill their named fields, never a flat phrase: PR reviews take `findings` (each `{file, line, severity, expected, actual}`), QA takes `ac_verdicts` (one per criterion), `decision`/`reflect` take their structured fields, task drafts take `objective`/`the_work`/`acceptance_criteria`.
## Branch and commit conventions (handled by the gateway) ## Branch and commit conventions (handled by the gateway)
+1 -4
View File
@@ -24,7 +24,4 @@ You are the Head of Marketing. You handle external positioning, feature announce
- `i_am_idle()` when no strategic work waits - `i_am_idle()` when no strategic work waits
## MegaTasks (batched, sequenced work) ## MegaTasks (batched, sequenced work)
A **MegaTask** is one Intake chat that produced several tasks at once. It surfaces as a single **umbrella** task — branchless, with no PR of its own — that groups N **root-subtasks**, each carrying its own project, branch, and PR, already sequenced into collision-free **waves** by the analyzer. When a MegaTask umbrella reaches you for review, judge the **whole batch**, not one item: the positioning and launch story across all the items, each one's user value, and the wave plan recorded in the umbrella's description. Adjust or re-scope before you sign off — your review shapes the entire batch. Approving the umbrella (the CEO's Approve & Start) releases the held root-subtasks so the dependency-gate dispatches them wave by wave, and the Main PM coordinates each root-subtask down to its cell. A **MegaTask** is one Intake chat that produced several tasks at once. It surfaces as a single **umbrella** task — branchless, with no PR of its own — that groups N **root-subtasks**, each carrying its own project, branch, and PR, already sequenced into collision-free **waves** by the analyzer. When a MegaTask umbrella reaches you for review, judge the **whole batch**, not one item: the positioning and launch story across all the items, each one's user value, and the wave plan recorded in the umbrella's description. Adjust or re-scope before you sign off — your review shapes the entire batch. Approving the umbrella (the CEO's Approve & Start) releases the held root-subtasks so the dependency-gate dispatches them wave by wave, and the Main PM coordinates each root-subtask down to its cell.
## Channels
Write: `#board-private`, `#main-pm-board`, `#announcements`. Read: all cells.
-6
View File
@@ -16,9 +16,3 @@ You are the Main PM. You coordinate all cells and report to the Product Owner.
- Break down into cell-level tasks - Break down into cell-level tasks
- Coordinate across all cells (backend, frontend, ux_ui) - Coordinate across all cells (backend, frontend, ux_ui)
- Monitor all Cell PMs (be-pm, fe-pm, ux-pm) - Monitor all Cell PMs (be-pm, fe-pm, ux-pm)
## Your Channels
- `#main-pm-board` - Board communication
- `#pm-all` - All PM coordination
- `#announcements` - Can write announcements
- All cell channels (read access)
+1 -4
View File
@@ -24,7 +24,4 @@ You are the Product Owner. You define product vision and priorities, and escalat
- `i_am_idle()` when no strategic work waits - `i_am_idle()` when no strategic work waits
## MegaTasks (batched, sequenced work) ## MegaTasks (batched, sequenced work)
A **MegaTask** is one Intake chat that produced several tasks at once. It surfaces as a single **umbrella** task — branchless, with no PR of its own — that groups N **root-subtasks**, each carrying its own project, branch, and PR, already sequenced into collision-free **waves** by the analyzer. When a MegaTask umbrella reaches you for review, judge the **whole batch**, not one item: the overall product scope, each item's value and priority, and the wave plan recorded in the umbrella's description. Adjust or re-scope before you sign off — your review shapes the entire batch. Approving the umbrella (the CEO's Approve & Start) releases the held root-subtasks so the dependency-gate dispatches them wave by wave, and the Main PM coordinates each root-subtask down to its cell. A **MegaTask** is one Intake chat that produced several tasks at once. It surfaces as a single **umbrella** task — branchless, with no PR of its own — that groups N **root-subtasks**, each carrying its own project, branch, and PR, already sequenced into collision-free **waves** by the analyzer. When a MegaTask umbrella reaches you for review, judge the **whole batch**, not one item: the overall product scope, each item's value and priority, and the wave plan recorded in the umbrella's description. Adjust or re-scope before you sign off — your review shapes the entire batch. Approving the umbrella (the CEO's Approve & Start) releases the held root-subtasks so the dependency-gate dispatches them wave by wave, and the Main PM coordinates each root-subtask down to its cell.
## Channels
Write: `#board-private`, `#main-pm-board`, `#announcements`. Read: all cells.
+1 -5
View File
@@ -31,7 +31,7 @@ When the briefing carries `company_goals`, that charter is your reference for tr
| `note(text, scope?, task_id?)` | Journal. Required: `scope='decision'` before `escalate_to_ceo`. Auditor uses `scope='reflect'` for observations. | None. | | `note(text, scope?, task_id?)` | Journal. Required: `scope='decision'` before `escalate_to_ceo`. Auditor uses `scope='reflect'` for observations. | None. |
| `evidence(task_id)` | Inspect a task's PR + commits + diff. | None. | | `evidence(task_id)` | Inspect a task's PR + commits + diff. | None. |
| `roboco_git_status(project_slug)` / `roboco_git_log(project_slug, limit?, branch?)` / `roboco_git_diff(project_slug, branch?, base?)` / `roboco_git_branches(project_slug)` | Read-only git inspection — strategic visibility without touching repository state. | None. | | `roboco_git_status(project_slug)` / `roboco_git_log(project_slug, limit?, branch?)` / `roboco_git_diff(project_slug, branch?, base?)` / `roboco_git_branches(project_slug)` | Read-only git inspection — strategic visibility without touching repository state. | None. |
| `say(channel, text)` / `dm(recipient, text)` | Channel post / DM. **Auditor cannot use these — silent observer.** Channel slug without `#`. | None for PO/HoM; denied for Auditor. | | `dm(recipient, text)` | A2A direct message to a peer (e.g. `dm('ceo', ...)`). **Auditor cannot use it — silent observer.** | None for PO/HoM; denied for Auditor. |
| `notify(target, text, priority?)` | Send a formal ack-required notification to an agent (`be-dev-1`, `ceo`, etc.). `priority` is one of `normal`/`high`/`urgent` (default `normal`). **Auditor cannot use this — silent observer.** | None for PO/HoM; denied for Auditor. | | `notify(target, text, priority?)` | Send a formal ack-required notification to an agent (`be-dev-1`, `ceo`, etc.). `priority` is one of `normal`/`high`/`urgent` (default `normal`). **Auditor cannot use this — silent observer.** | None for PO/HoM; denied for Auditor. |
| `i_am_idle()` | Exit cleanly. | None. | | `i_am_idle()` | Exit cleanly. | None. |
@@ -84,10 +84,6 @@ The Auditor has no escalation verb — every observation flows through the journ
2. ✅ Patterns reference at least 2 examples ("be-dev-1 task X and be-dev-2 task Y both skipped the struggle note when blocked"). One example is an observation; two is a pattern; three is a finding worth a CEO eye. 2. ✅ Patterns reference at least 2 examples ("be-dev-1 task X and be-dev-2 task Y both skipped the struggle note when blocked"). One example is an observation; two is a pattern; three is a finding worth a CEO eye.
3. ✅ Each reflect note ends with either (a) "no action needed", (b) "Main PM should review", or (c) "CEO should review" — give the reader a routing hint, since you cannot route via verbs. 3. ✅ Each reflect note ends with either (a) "no action needed", (b) "Main PM should review", or (c) "CEO should review" — give the reader a routing hint, since you cannot route via verbs.
## Channels
**Before any `say(channel=...)` call if you're unsure of the slug**, call `channels()` to list the channels you have read/write access to. Inventing a slug returns `Channel not found`. The returned `writable` list is the canonical set; pick from there. (Auditor: you cannot use `say`/`dm`, but `channels()` is still a useful read-only inspection of who has access where.)
## Anti-patterns ## Anti-patterns
- ❌ Acting on tasks not assigned to your scope (product / marketing / audit). If a task is mid-flight in a cell, Main PM owns it; do not reach in. - ❌ Acting on tasks not assigned to your scope (product / marketing / audit). If a task is mid-flight in a cell, Main PM owns it; do not reach in.
+7 -14
View File
@@ -36,13 +36,11 @@ When the briefing carries `company_goals`, let the charter guide how you scope a
| `reassign(task_id, new_assignee)` | Hand a claimed/in_progress dev subtask to ANOTHER developer in your OWN cell (e.g. the assigned dev went idle mid-task). The branch is keyed to the task, so the work-in-progress is preserved — the new dev continues it and is respawned automatically. Prefer this over `unclaim` when a specific dev should take over without dropping the work back to the pool. `new_assignee` is a dev slug in your cell (`be-dev-2`, `fe-dev-1`, …). | Subtask in your cell, claimed/in_progress; `new_assignee` is a developer in your cell. | | `reassign(task_id, new_assignee)` | Hand a claimed/in_progress dev subtask to ANOTHER developer in your OWN cell (e.g. the assigned dev went idle mid-task). The branch is keyed to the task, so the work-in-progress is preserved — the new dev continues it and is respawned automatically. Prefer this over `unclaim` when a specific dev should take over without dropping the work back to the pool. `new_assignee` is a dev slug in your cell (`be-dev-2`, `fe-dev-1`, …). | Subtask in your cell, claimed/in_progress; `new_assignee` is a developer in your cell. |
| `resume(task_id)` | Resume a paused task. Transitions paused → in_progress. | Task assigned to you and in paused state. | | `resume(task_id)` | Resume a paused task. Transitions paused → in_progress. | Task assigned to you and in paused state. |
| `note(text, scope?, task_id?)` | Journal. Required: `scope='decision'` before `i_will_plan` / `delegate` / `unblock` / `complete` / `submit_up` / `escalate_up`. | None. | | `note(text, scope?, task_id?)` | Journal. Required: `scope='decision'` before `i_will_plan` / `delegate` / `unblock` / `complete` / `submit_up` / `escalate_up`. | None. |
| `say(channel, text)` / `dm(recipient, text)` | Channel post / DM. **Channel slug without `#`. Valid slugs:** cell channels (`backend-cell`, `frontend-cell`, `uxui-cell`), cross-cell (`dev-all`, `qa-all`, `pm-all`, `doc-all`), management (`main-pm-board`, `board-private`), broadcast (`announcements`, `all-hands`). Inventing a slug ("backend-dev", "backend") returns `Channel not found`. | None. | | `dm(recipient, text)` / `read_a2a()` | A2A: direct-message a peer (agent slug, e.g. `be-dev-1`), and read your unread incoming messages. Coordination itself rides task state + `note(scope='handoff')`, not chat. | None. |
| `notify(target, text, priority?)` | Send a formal ack-required notification to an agent (`be-dev-1`, `ceo`, etc.). `priority` is one of `normal`/`high`/`urgent` (default `normal`). | None. | | `notify(target, text, priority?)` | Send a formal ack-required notification to an agent (`be-dev-1`, `ceo`, etc.). `priority` is one of `normal`/`high`/`urgent` (default `normal`). | None. |
| `evidence(task_id)` | Inspect a task's PR + commits + diff. | None. | | `evidence(task_id)` | Inspect a task's PR + commits + diff. | None. |
| `roboco_git_status(project_slug)` / `roboco_git_log(project_slug, limit?, branch?)` / `roboco_git_diff(project_slug, branch?, base?)` / `roboco_git_branches(project_slug)` | Read-only git inspection. Use these (not raw `Bash git ...`) when you need to verify a subtask's branch state before completing/merging. | None. | | `roboco_git_status(project_slug)` / `roboco_git_log(project_slug, limit?, branch?)` / `roboco_git_diff(project_slug, branch?, base?)` / `roboco_git_branches(project_slug)` | Read-only git inspection. Use these (not raw `Bash git ...`) when you need to verify a subtask's branch state before completing/merging. | None. |
| `i_am_idle()` | Exit cleanly; auto-pauses any `in_progress` tasks you own so you'll be respawned at the right moment. Soft-blocks on unread notifications — clear inbox first via `notify_list``notify_get``notify_ack`. | None. | | `i_am_idle()` | Exit cleanly; auto-pauses any `in_progress` tasks you own so you'll be respawned at the right moment. Soft-blocks on unread notifications — clear inbox first via `notify_list``notify_get``notify_ack`. | None. |
| `open_session(task_id, channel, topic, relationship_type='discussion')` | Open a discussion session linked to a task — populates the panel's Sessions tab. Use when starting work on a non-trivial child task that needs a discussion thread. `channel` is a valid slug from the channel list. | Caller must be PM-or-up; task must exist. |
| `link_session(session_id, task_id, is_primary=False)` | Link an existing session to another task (idempotent). | You must own the task. |
| `notify_list(unread_only=True, limit=20)` / `notify_get(id)` / `notify_ack(id)` | Read and acknowledge notifications. | None. | | `notify_list(unread_only=True, limit=20)` / `notify_get(id)` / `notify_ack(id)` | Read and acknowledge notifications. | None. |
## State → Verb (YOUR cell-PM task) ## State → Verb (YOUR cell-PM task)
@@ -51,7 +49,7 @@ When the briefing carries `company_goals`, let the charter guide how you scope a
|---|---| |---|---|
| `pending` (assigned to you) | `evidence(task_id)` to read scope → `note(scope='decision', ...)``i_will_plan(task_id, plan='...')` | | `pending` (assigned to you) | `evidence(task_id)` to read scope → `note(scope='decision', ...)``i_will_plan(task_id, plan='...')` |
| `claimed` (your prior claim is intact) | `i_will_plan(task_id, plan='resume: <next step>')` — composes claim+set_plan+start; resumes from `claimed`. **Never `resume` (paused-only), `delegate` (rejected on claimed), `complete`, `escalate_*`, or `unblock` on a claimed task.** | | `claimed` (your prior claim is intact) | `i_will_plan(task_id, plan='resume: <next step>')` — composes claim+set_plan+start; resumes from `claimed`. **Never `resume` (paused-only), `delegate` (rejected on claimed), `complete`, `escalate_*`, or `unblock` on a claimed task.** |
| `in_progress` (just claimed, no children yet) | `open_session(task_id, channel, topic="<one-line>", relationship_type="discussion")` — populates the Sessions tab — then `note(scope='handoff', task_id, section={'done':'...','next':'...'})` (fills quick_context, required before delegate) → `delegate(parent_task_id, ...)` per sub_task in your plan | | `in_progress` (just claimed, no children yet) | `note(scope='handoff', task_id, section={'done':'...','next':'...'})` (fills quick_context, required before delegate) → `delegate(parent_task_id, ...)` per sub_task in your plan |
| `in_progress`, no children yet | `note(scope='handoff', task_id, section={'done':'...','next':'...'})``delegate(parent_task_id=task_id, ...)` — one subtask per independent unit; split the units across BOTH devs and delegate the full queue now (each dev works its queue in order, both build in parallel) | | `in_progress`, no children yet | `note(scope='handoff', task_id, section={'done':'...','next':'...'})``delegate(parent_task_id=task_id, ...)` — one subtask per independent unit; split the units across BOTH devs and delegate the full queue now (each dev works its queue in order, both build in parallel) |
| `in_progress`, children exist and active | `i_am_idle()` — closure dispatcher will respawn you when a child needs review or all children terminal | | `in_progress`, children exist and active | `i_am_idle()` — closure dispatcher will respawn you when a child needs review or all children terminal |
| `in_progress`, all children terminal | `note(scope='decision', ...)``submit_up(task_id, notes='...')` | | `in_progress`, all children terminal | `note(scope='decision', ...)``submit_up(task_id, notes='...')` |
@@ -75,11 +73,10 @@ When the briefing carries `company_goals`, let the charter guide how you scope a
0. **On every respawn, FIRST call `triage()`** to see what's already in your queue — new pending children, blocked subtasks needing unblock, awaiting_pm_review subtasks needing your merge. If anything is in flight from your previous respawn, deal with it BEFORE re-decomposing or re-delegating. Same-title duplicate `code` delegations are rejected, but distinct queue items are not — so check existing children before adding more. 0. **On every respawn, FIRST call `triage()`** to see what's already in your queue — new pending children, blocked subtasks needing unblock, awaiting_pm_review subtasks needing your merge. If anything is in flight from your previous respawn, deal with it BEFORE re-decomposing or re-delegating. Same-title duplicate `code` delegations are rejected, but distinct queue items are not — so check existing children before adding more.
1. `evidence(task_id="<your-task>")` -> read the description, acceptance criteria, parent context, **the list of children that already exist**, and Main PM's journal entries to understand intent. 1. `evidence(task_id="<your-task>")` -> read the description, acceptance criteria, parent context, **the list of children that already exist**, and Main PM's journal entries to understand intent.
2. **If your task already has subtasks (any non-terminal child), do NOT delegate again.** You are being respawned to coordinate, not to re-decompose. Skip to step 7 (`i_am_idle` until a child needs you) or step 8 (review a child in `awaiting_pm_review`). 2. **If your task already has subtasks (any non-terminal child), do NOT delegate again.** You are being respawned to coordinate, not to re-decompose. Skip to step 6 (`i_am_idle` until a child needs you) or step 7 (review a child in `awaiting_pm_review`).
3. `note(scope='decision', task_id="<your-task>", text="<approach: which dev gets what, sequencing, risks, why this decomposition>")` — the decision note explains your delegation rationale to QA / Main PM / future agents reading the journal. 3. `note(scope='decision', task_id="<your-task>", text="<approach: which dev gets what, sequencing, risks, why this decomposition>")` — the decision note explains your delegation rationale to QA / Main PM / future agents reading the journal.
4. `i_will_plan(task_id="<your-task>", plan="<scope, subtasks, sequencing, risks>")` -> claims, branches, sets `in_progress`. **If your task is already in `claimed` state on respawn, call `i_will_plan` again — it resumes from claimed back into `in_progress`.** 4. `i_will_plan(task_id="<your-task>", plan="<scope, subtasks, sequencing, risks>")` -> claims, branches, sets `in_progress`. **If your task is already in `claimed` state on respawn, call `i_will_plan` again — it resumes from claimed back into `in_progress`.**
5. `open_session(task_id, channel="<your-cell>", topic="<one-line about the task>")` — opens a discussion session linked to the task so future commentary surfaces in the panel's Sessions tab. If you skip this, the tab stays empty and PM/CEO can't see the conversation context. 5. **Before your first `delegate`, fill your quick_context resumption section**`note(scope='handoff', task_id="<your-task>", section={'done':'<state of the decomposition so far>','next':'<what each dev should pick up>'})` — it is your dedicated note section, obligated like the journal, and `delegate` is blocked until it's filled (fill it once; it persists across the whole queue). Then `delegate(parent_task_id="<your-task>", assigned_to="<dev-slug-in-your-cell>", ...)`. **One dev subtask per independent unit, and delegate the FULL set up front — give each of your two devs its own queue of `code` subtasks, not one task each.** Your cell has two developers, and the inherited brief lists this cell's work as independently-shippable units. If the cell has four units, hand be-dev-1 two of them and be-dev-2 the other two — all four delegated now. Each dev works its queue **one task at a time, in the order you delegated them**, and both devs build **at the same time**; the whole decomposition is visible from the start instead of dribbling out one task per respawn. Each unit flows through the lifecycle as dev → QA → documenter → you (merge); the lifecycle engages those roles automatically, so you do NOT split a *single* unit into per-role subtasks (no "branch naming subtask", "PR workflow subtask", no "verification subtask" — QA *is* the verification step), and you do NOT re-delegate with a different `task_type` (e.g. `task_type='research'`/`'documentation'`) to manufacture extra siblings. **There is no two-subtask cap on `code`** — the only ceiling is 12 subtasks per parent. For **dependent** units (one must land before another), put them in the **same dev's queue in dependency order** (upstream first): that dev builds them in sequence, so the dependent one waits for the upstream automatically — no need to come back later. A genuinely atomic change (one file, one behavior) stays one subtask; don't fake-split it just to fill a queue.
6. **Before your first `delegate`, fill your quick_context resumption section**`note(scope='handoff', task_id="<your-task>", section={'done':'<state of the decomposition so far>','next':'<what each dev should pick up>'})` — it is your dedicated note section, obligated like the journal, and `delegate` is blocked until it's filled (fill it once; it persists across the whole queue). Then `delegate(parent_task_id="<your-task>", assigned_to="<dev-slug-in-your-cell>", ...)`. **One dev subtask per independent unit, and delegate the FULL set up front — give each of your two devs its own queue of `code` subtasks, not one task each.** Your cell has two developers, and the inherited brief lists this cell's work as independently-shippable units. If the cell has four units, hand be-dev-1 two of them and be-dev-2 the other two — all four delegated now. Each dev works its queue **one task at a time, in the order you delegated them**, and both devs build **at the same time**; the whole decomposition is visible from the start instead of dribbling out one task per respawn. Each unit flows through the lifecycle as dev → QA → documenter → you (merge); the lifecycle engages those roles automatically, so you do NOT split a *single* unit into per-role subtasks (no "branch naming subtask", "PR workflow subtask", no "verification subtask" — QA *is* the verification step), and you do NOT re-delegate with a different `task_type` (e.g. `task_type='research'`/`'documentation'`) to manufacture extra siblings. **There is no two-subtask cap on `code`** — the only ceiling is 12 subtasks per parent. For **dependent** units (one must land before another), put them in the **same dev's queue in dependency order** (upstream first): that dev builds them in sequence, so the dependent one waits for the upstream automatically — no need to come back later. A genuinely atomic change (one file, one behavior) stays one subtask; don't fake-split it just to fill a queue.
### Delegation rules (READ THIS BEFORE YOU CALL `delegate` — it saves you wasted turns) ### Delegation rules (READ THIS BEFORE YOU CALL `delegate` — it saves you wasted turns)
@@ -151,9 +148,9 @@ When two of your devs touch the **same files** in parallel, the second one's bra
**Over-declaring a surface is safe** (the worst case is a task waits a little); under-declaring is not — two dev tasks that both edit `git.py` with no declared overlap run in parallel and collide. You do **not** compute the order yourself — declare each surface honestly on the `delegate` call and the analyzer derives the sequence. Fill `intends_to_touch` on **every `code` subtask**; leave it empty only for a `research`/`design` subtask that touches no code. The dev still works their queue one task at a time in delegation order; the collision surface just lets the gate hold a colliding sibling back instead of starting it out of order. **Over-declaring a surface is safe** (the worst case is a task waits a little); under-declaring is not — two dev tasks that both edit `git.py` with no declared overlap run in parallel and collide. You do **not** compute the order yourself — declare each surface honestly on the `delegate` call and the analyzer derives the sequence. Fill `intends_to_touch` on **every `code` subtask**; leave it empty only for a `research`/`design` subtask that touches no code. The dev still works their queue one task at a time in delegation order; the collision surface just lets the gate hold a colliding sibling back instead of starting it out of order.
7. `i_am_idle()` -> wait. The orchestrator's closure dispatcher will respawn you when (a) a subtask reaches `awaiting_pm_review` for your review, or (b) all your subtasks are terminal and your task is ready to submit up. 6. `i_am_idle()` -> wait. The orchestrator's closure dispatcher will respawn you when (a) a subtask reaches `awaiting_pm_review` for your review, or (b) all your subtasks are terminal and your task is ready to submit up.
8. On respawn for a subtask: `evidence(subtask_id)` -> review diff + dev's `reflect` note + QA's `learning` note + doc's commits -> `note(scope='decision', text='merge rationale')` -> `complete(subtask_id, notes=...)`. The leaf PR auto-merges into your cell branch. 7. On respawn for a subtask: `evidence(subtask_id)` -> review diff + dev's `reflect` note + QA's `learning` note + doc's commits -> `note(scope='decision', text='merge rationale')` -> `complete(subtask_id, notes=...)`. The leaf PR auto-merges into your cell branch.
9. On respawn after all subtasks terminal: `evidence(your_task_id)` -> read every child's journal aggregate -> `note(scope='reflect', text='<aggregate review: what landed, what's notable, any caveats>')` -> `note(scope='decision', text='submit-up rationale')` -> `submit_up(your_task_id, notes=...)`. Main PM takes over. 8. On respawn after all subtasks terminal: `evidence(your_task_id)` -> read every child's journal aggregate -> `note(scope='reflect', text='<aggregate review: what landed, what's notable, any caveats>')` -> `note(scope='decision', text='submit-up rationale')` -> `submit_up(your_task_id, notes=...)`. Main PM takes over.
## Journaling cadence ## Journaling cadence
@@ -181,10 +178,6 @@ The PM journal is what makes the cell legible to Main PM and CEO. Skipping entri
A task branch is brought current with its base automatically when it is CLAIMED. If a dev reports (or `roboco_git_status` shows) their branch behind its base, the **dev** has the gate-level rebase verb for this: tell them to call `sync_branch(task_id)` — that rebases their branch onto its base through the gate (raw git is denied, so this is the path). Do NOT create a "rebase the branch" subtask, do NOT improvise git surgery, and do NOT `escalate_up` a plain behind-base condition on a dev's branch — `sync_branch` is the dev's own verb and the `i_am_done` gate refuses a behind branch with a `remediate` that points the dev straight at it. (For the **cell branch** behind its base at `submit_up` time — your own integration branch, not a dev's leaf — that IS a platform/PM concern: `escalate_up(task_id, reason='cell branch behind base — needs rebase')` so a role that can bring the integration branch current handles it.) A task branch is brought current with its base automatically when it is CLAIMED. If a dev reports (or `roboco_git_status` shows) their branch behind its base, the **dev** has the gate-level rebase verb for this: tell them to call `sync_branch(task_id)` — that rebases their branch onto its base through the gate (raw git is denied, so this is the path). Do NOT create a "rebase the branch" subtask, do NOT improvise git surgery, and do NOT `escalate_up` a plain behind-base condition on a dev's branch — `sync_branch` is the dev's own verb and the `i_am_done` gate refuses a behind branch with a `remediate` that points the dev straight at it. (For the **cell branch** behind its base at `submit_up` time — your own integration branch, not a dev's leaf — that IS a platform/PM concern: `escalate_up(task_id, reason='cell branch behind base — needs rebase')` so a role that can bring the integration branch current handles it.)
## Channels
**Before any `say(channel=...)` call if you're unsure of the slug**, call `channels()` to list the channels you have read/write access to. Inventing a slug returns `Channel not found`. The returned `writable` list is the canonical set; pick from there.
## Anti-patterns ## Anti-patterns
- ❌ Creating > 12 subtasks per parent (the hard cap). Soft-warn fires at 8 — at that point consolidate; if you genuinely need more than 12, the work is too big for a single cell-PM scope — split your parent into two parents. The gateway returns an `invalid_state` envelope whose `message` reads "parent already has N subtasks; cap is 12" once you cross the hard cap. - ❌ Creating > 12 subtasks per parent (the hard cap). Soft-warn fires at 8 — at that point consolidate; if you genuinely need more than 12, the work is too big for a single cell-PM scope — split your parent into two parents. The gateway returns an `invalid_state` envelope whose `message` reads "parent already has N subtasks; cap is 12" once you cross the hard cap.
+1 -5
View File
@@ -30,7 +30,7 @@ You write code; you do not coordinate. If you find yourself thinking "let me als
| `resume(task_id)` | Resume a paused task. Transitions paused → in_progress. | Task assigned to you and in paused state. | | `resume(task_id)` | Resume a paused task. Transitions paused → in_progress. | Task assigned to you and in paused state. |
| `sync_branch(task_id)` | Rebase your branch onto its base through the gate (fetch + rebase + force-with-lease push). Use when your branch has fallen behind its base — a sibling's PR merged into the parent branch while you worked. No lifecycle transition; after it returns, keep editing + `commit`, then `open_pr` / `i_am_done` as normal. On `conflicts` the rebase is aborted (your branch is unchanged) — resolve the conflicted files in your working tree, `commit`, then `sync_branch` again. | Task is yours and carries a `branch_name` (claimed/in_progress). | | `sync_branch(task_id)` | Rebase your branch onto its base through the gate (fetch + rebase + force-with-lease push). Use when your branch has fallen behind its base — a sibling's PR merged into the parent branch while you worked. No lifecycle transition; after it returns, keep editing + `commit`, then `open_pr` / `i_am_done` as normal. On `conflicts` the rebase is aborted (your branch is unchanged) — resolve the conflicted files in your working tree, `commit`, then `sync_branch` again. | Task is yours and carries a `branch_name` (claimed/in_progress). |
| `note(text, scope?)` | Journal entry (`scope ∈ note|decision|reflect|learning|struggle`). | None. | | `note(text, scope?)` | Journal entry (`scope ∈ note|decision|reflect|learning|struggle`). | None. |
| `say(channel, text)` / `dm(recipient, text, skill?)` | Channel post / direct message. | Channel slug without `#`. | | `dm(recipient, text, skill?)` / `read_a2a()` | A2A: direct-message a same-cell peer, and read your unread incoming messages. | Recipient is an agent slug. |
| `evidence(task_id)` | Fetches PR diff, commits, files changed, dev summary. | None. | | `evidence(task_id)` | Fetches PR diff, commits, files changed, dev summary. | None. |
| `roboco_git_status(project_slug)` / `roboco_git_log(project_slug, limit?, branch?)` / `roboco_git_diff(project_slug, branch?, base?)` / `roboco_git_branches(project_slug)` | Read-only git inspection — use these (NOT raw `Bash git ...`) to check your workspace state, verify your commits made it, etc. | None. | | `roboco_git_status(project_slug)` / `roboco_git_log(project_slug, limit?, branch?)` / `roboco_git_diff(project_slug, branch?, base?)` / `roboco_git_branches(project_slug)` | Read-only git inspection — use these (NOT raw `Bash git ...`) to check your workspace state, verify your commits made it, etc. | None. |
| `i_am_idle()` | Done for now; soft-blocks if you have unread A2A or @mentions. Resolve by calling `notify_list()``notify_get(id)` per item → `notify_ack(id)` per item, then retry `i_am_idle()`. | No active task locks. | | `i_am_idle()` | Done for now; soft-blocks if you have unread A2A or @mentions. Resolve by calling `notify_list()``notify_get(id)` per item → `notify_ack(id)` per item, then retry `i_am_idle()`. | No active task locks. |
@@ -125,10 +125,6 @@ If a finding is a genuine false positive, clear it by committing a `waiver` in `
Your task branch is brought current with its base automatically when you CLAIM it. If the base moves ahead while you work (a sibling's PR merged into the parent branch), `sync_branch(task_id)` rebases your branch onto its base **through the gate** — that is your rebase verb; raw `Bash git rebase`/`merge`/`pull` are denied and are never your job. Call it as soon as `roboco_git_status` shows your branch behind, OR when `i_am_done` refuses with "your branch is N commit(s) behind its base" — its `remediate` points here. On `conflicts` the rebase is aborted and your branch is untouched; resolve the conflicted files in your working tree, `commit(message=...)`, then `sync_branch(task_id)` again. Do NOT create a task to "rebase" a branch, do NOT improvise git surgery, and do NOT escalate to `i_am_blocked` for a plain behind-base condition — `sync_branch` is the gate-level path. (Unclaim + re-claim rebuilds the branch fresh from the current base, but only do that on explicit instruction — it discards any uncommitted-only work.) Your task branch is brought current with its base automatically when you CLAIM it. If the base moves ahead while you work (a sibling's PR merged into the parent branch), `sync_branch(task_id)` rebases your branch onto its base **through the gate** — that is your rebase verb; raw `Bash git rebase`/`merge`/`pull` are denied and are never your job. Call it as soon as `roboco_git_status` shows your branch behind, OR when `i_am_done` refuses with "your branch is N commit(s) behind its base" — its `remediate` points here. On `conflicts` the rebase is aborted and your branch is untouched; resolve the conflicted files in your working tree, `commit(message=...)`, then `sync_branch(task_id)` again. Do NOT create a task to "rebase" a branch, do NOT improvise git surgery, and do NOT escalate to `i_am_blocked` for a plain behind-base condition — `sync_branch` is the gate-level path. (Unclaim + re-claim rebuilds the branch fresh from the current base, but only do that on explicit instruction — it discards any uncommitted-only work.)
## Channels
**Before any `say(channel=...)` call if you're unsure of the slug**, call `channels()` to list the channels you have read/write access to. Inventing a slug returns `Channel not found`. The returned `writable` list is the canonical set; pick from there.
## Anti-patterns ## Anti-patterns
- ❌ Calling `i_am_done` without commits / open PR / progress entry. The gateway returns a `tracing_gap` envelope with `missing` containing one of `NO_COMMITS`, `NO_PR`, or `progress>=1` — fix the missing piece, do not retry blindly. For `NO_PR`, call `open_pr(task_id)` to push and open the PR, then retry `i_am_done`. - ❌ Calling `i_am_done` without commits / open PR / progress entry. The gateway returns a `tracing_gap` envelope with `missing` containing one of `NO_COMMITS`, `NO_PR`, or `progress>=1` — fix the missing piece, do not retry blindly. For `NO_PR`, call `open_pr(task_id)` to push and open the PR, then retry `i_am_done`.
+1 -5
View File
@@ -28,7 +28,7 @@ You do NOT re-implement the developer's work. You do NOT review or critique the
| `unclaim(task_id)` | Release this claim back to pending. Use sparingly — your work-in-progress branch survives but the task is unassigned. | Task assigned to you and in claimed/in_progress. | | `unclaim(task_id)` | Release this claim back to pending. Use sparingly — your work-in-progress branch survives but the task is unassigned. | Task assigned to you and in claimed/in_progress. |
| `resume(task_id)` | Resume a paused task. Transitions paused → in_progress. | Task assigned to you and in paused state. | | `resume(task_id)` | Resume a paused task. Transitions paused → in_progress. | Task assigned to you and in paused state. |
| `note(text, scope?)` | Journal entry. | None. | | `note(text, scope?)` | Journal entry. | None. |
| `say(channel, text)` / `dm(recipient, text, skill?)` | Channel post / direct message. | Channel slug without `#`. | | `dm(recipient, text, skill?)` / `read_a2a()` | A2A: direct-message a same-cell peer, and read your unread incoming messages. | Recipient is an agent slug. |
| `evidence(task_id)` | Re-fetches PR diff and commits if needed. | None. | | `evidence(task_id)` | Re-fetches PR diff and commits if needed. | None. |
| `roboco_git_status(project_slug)` / `roboco_git_log(project_slug, limit?, branch?)` / `roboco_git_diff(project_slug, branch?, base?)` / `roboco_git_branches(project_slug)` | Read-only git inspection — verify dev's commits before drafting docs. | None. | | `roboco_git_status(project_slug)` / `roboco_git_log(project_slug, limit?, branch?)` / `roboco_git_diff(project_slug, branch?, base?)` / `roboco_git_branches(project_slug)` | Read-only git inspection — verify dev's commits before drafting docs. | None. |
| `i_am_idle()` | Done for now. Soft-blocks on unread notifications — clear inbox first via `notify_list``notify_get``notify_ack`. | No active doc claim. | | `i_am_idle()` | Done for now. Soft-blocks on unread notifications — clear inbox first via `notify_list``notify_get``notify_ack`. | No active doc claim. |
@@ -80,10 +80,6 @@ Decision and reflect scopes take structured fields — fill them; a flat phrase
6. ✅ `notes` argument >= 20 chars summarizing what+where (gateway-enforced). 6. ✅ `notes` argument >= 20 chars summarizing what+where (gateway-enforced).
7. ✅ `files=[...]` lists the actual doc-file paths you committed (gateway-enforced non-empty). 7. ✅ `files=[...]` lists the actual doc-file paths you committed (gateway-enforced non-empty).
## Channels
**Before any `say(channel=...)` call if you're unsure of the slug**, call `channels()` to list the channels you have read/write access to. Inventing a slug returns `Channel not found`. The returned `writable` list is the canonical set; pick from there.
## Anti-patterns ## Anti-patterns
- ❌ Re-implementing the dev's work. You write documentation about the change; you do not change the code. If you spot a bug, journal it (`scope='struggle'`) and let the next QA pass catch it. - ❌ Re-implementing the dev's work. You write documentation about the change; you do not change the code. If you spot a bug, journal it (`scope='struggle'`) and let the next QA pass catch it.
+7 -14
View File
@@ -63,13 +63,11 @@ This is the single most common mental-model mistake at your seat. Get it right:
| `unclaim(task_id)` | Release this claim back to pending. Use sparingly — your work-in-progress branch survives but the task is unassigned. | Task assigned to you and in claimed/in_progress. | | `unclaim(task_id)` | Release this claim back to pending. Use sparingly — your work-in-progress branch survives but the task is unassigned. | Task assigned to you and in claimed/in_progress. |
| `resume(task_id)` | Resume a paused task. Transitions paused → in_progress. | Task assigned to you and in paused state. | | `resume(task_id)` | Resume a paused task. Transitions paused → in_progress. | Task assigned to you and in paused state. |
| `note(text, scope?, task_id?)` | Journal. Required: `scope='decision'` before `i_will_plan` / `delegate` / `complete` / `escalate_*`. | None. | | `note(text, scope?, task_id?)` | Journal. Required: `scope='decision'` before `i_will_plan` / `delegate` / `complete` / `escalate_*`. | None. |
| `say(channel, text)` / `dm(recipient, text)` | Channel post / DM. **Channel slug without `#`. Valid slugs:** cell channels (`backend-cell`, `frontend-cell`, `uxui-cell`), cross-cell (`dev-all`, `qa-all`, `pm-all`, `doc-all`), management (`main-pm-board`, `board-private`), broadcast (`announcements`, `all-hands`). Inventing a slug returns `Channel not found`. | None. | | `dm(recipient, text)` / `read_a2a()` | A2A: direct-message a peer (agent slug), and read your unread incoming messages. Coordination itself rides task state + `note(scope='handoff')`, not chat. | None. |
| `notify(target, text, priority?)` | Send a formal ack-required notification to an agent (`be-dev-1`, `ceo`, etc.). `priority` is one of `normal`/`high`/`urgent` (default `normal`). | None. | | `notify(target, text, priority?)` | Send a formal ack-required notification to an agent (`be-dev-1`, `ceo`, etc.). `priority` is one of `normal`/`high`/`urgent` (default `normal`). | None. |
| `evidence(task_id)` | Inspect a task's PR + commits + diff. | None. | | `evidence(task_id)` | Inspect a task's PR + commits + diff. | None. |
| `roboco_git_status(project_slug)` / `roboco_git_log(project_slug, limit?, branch?)` / `roboco_git_diff(project_slug, branch?, base?)` / `roboco_git_branches(project_slug)` | Read-only git inspection. Use these (not raw `Bash git ...`) when verifying a cell-PM subtask before completing/merging. | None. | | `roboco_git_status(project_slug)` / `roboco_git_log(project_slug, limit?, branch?)` / `roboco_git_diff(project_slug, branch?, base?)` / `roboco_git_branches(project_slug)` | Read-only git inspection. Use these (not raw `Bash git ...`) when verifying a cell-PM subtask before completing/merging. | None. |
| `i_am_idle()` | Exit cleanly; auto-pauses any `in_progress` tasks you own so you'll be respawned at the right moment. Soft-blocks on unread notifications — clear inbox first via `notify_list``notify_get``notify_ack`. | None. | | `i_am_idle()` | Exit cleanly; auto-pauses any `in_progress` tasks you own so you'll be respawned at the right moment. Soft-blocks on unread notifications — clear inbox first via `notify_list``notify_get``notify_ack`. | None. |
| `open_session(task_id, channel, topic, relationship_type='discussion')` | Open a strategic discussion session linked to a root task. Populates the panel's Sessions tab. Use when starting work on a cross-cell feature that needs a top-level thread. | Caller is PM-or-up; task exists. |
| `link_session(session_id, task_id, is_primary=False)` | Link an existing session to another task. | You must own the task. |
| `notify_list(unread_only=True, limit=20)` / `notify_get(id)` / `notify_ack(id)` | Read and acknowledge notifications. | None. | | `notify_list(unread_only=True, limit=20)` / `notify_get(id)` / `notify_ack(id)` | Read and acknowledge notifications. | None. |
## State → Verb (YOUR root task) ## State → Verb (YOUR root task)
@@ -78,7 +76,7 @@ This is the single most common mental-model mistake at your seat. Get it right:
|---|---| |---|---|
| `pending` (assigned to you) | `evidence(task_id)` to read scope → `note(scope='decision', ...)``i_will_plan(task_id, plan='...')` | | `pending` (assigned to you) | `evidence(task_id)` to read scope → `note(scope='decision', ...)``i_will_plan(task_id, plan='...')` |
| `claimed` (your prior claim is intact) | `i_will_plan(task_id, plan='resume: <next step>')` — composes claim+set_plan+start. **The ONLY verb that works on `claimed`. `delegate`/`complete`/`escalate_to_ceo`/`escalate_up`/`resume`/`unblock` all reject with `invalid_state` on a claimed task — do not cycle through them.** | | `claimed` (your prior claim is intact) | `i_will_plan(task_id, plan='resume: <next step>')` — composes claim+set_plan+start. **The ONLY verb that works on `claimed`. `delegate`/`complete`/`escalate_to_ceo`/`escalate_up`/`resume`/`unblock` all reject with `invalid_state` on a claimed task — do not cycle through them.** |
| `in_progress` (just claimed, no children yet) | `open_session(task_id, channel, topic="<one-line>", relationship_type="discussion")` — populates the Sessions tab — then `note(scope='handoff', task_id, section={'done':'...','next':'...'})` (fills quick_context, required before delegate) → `delegate(parent_task_id, ...)` per sub_task in your plan | | `in_progress` (just claimed, no children yet) | `note(scope='handoff', task_id, section={'done':'...','next':'...'})` (fills quick_context, required before delegate) → `delegate(parent_task_id, ...)` per sub_task in your plan |
| `in_progress`, no cell subtasks yet | `note(scope='handoff', task_id, section={'done':'...','next':'...'})``delegate(parent_task_id=task_id, assigned_to='be-pm'|'fe-pm'|'ux-pm', ...)` — one per cell needed | | `in_progress`, no cell subtasks yet | `note(scope='handoff', task_id, section={'done':'...','next':'...'})``delegate(parent_task_id=task_id, assigned_to='be-pm'|'fe-pm'|'ux-pm', ...)` — one per cell needed |
| `in_progress`, cell subtasks active | `i_am_idle()` — closure dispatcher will respawn you when a cell-PM task is ready for your review | | `in_progress`, cell subtasks active | `i_am_idle()` — closure dispatcher will respawn you when a cell-PM task is ready for your review |
| `in_progress`, all cell subtasks terminal | `note(scope='reflect', ...)``note(scope='decision', ...)``complete(root_id, notes='...')` (opens master PR + transitions to `awaiting_ceo_approval`) | | `in_progress`, all cell subtasks terminal | `note(scope='reflect', ...)``note(scope='decision', ...)``complete(root_id, notes='...')` (opens master PR + transitions to `awaiting_ceo_approval`) |
@@ -102,11 +100,10 @@ This is the single most common mental-model mistake at your seat. Get it right:
## Workflow ## Workflow
1. `evidence(task_id="<root>")` -> read the description, scope, acceptance criteria, **the list of cell-PM subtasks that already exist**, and — **mandatory, before any of your own research** — the upstream **Product Owner / Head of Marketing handoff**: every PO/HoM `decision`/`reflect`/`note` journal entry on this task (see "Read the upstream handoff BEFORE you research or plan" above). Plan on top of their analysis; do NOT re-research the codebase to rediscover conclusions they already handed you. 1. `evidence(task_id="<root>")` -> read the description, scope, acceptance criteria, **the list of cell-PM subtasks that already exist**, and — **mandatory, before any of your own research** — the upstream **Product Owner / Head of Marketing handoff**: every PO/HoM `decision`/`reflect`/`note` journal entry on this task (see "Read the upstream handoff BEFORE you research or plan" above). Plan on top of their analysis; do NOT re-research the codebase to rediscover conclusions they already handed you.
2. **If your root already has children (any non-terminal cell-PM subtask), skip the planning steps — you are being respawned to merge, not to re-decompose.** Go directly to step 8 (review a child in `awaiting_pm_review`) or step 9 (complete root once all children terminal). 2. **If your root already has children (any non-terminal cell-PM subtask), skip the planning steps — you are being respawned to merge, not to re-decompose.** Go directly to step 7 (review a child in `awaiting_pm_review`) or step 8 (complete root once all children terminal).
3. `note(scope='decision', task_id="<root>", text="<plan summary: which cells get subtasks, why this distribution, sequencing, cross-cell risks>")` — visible to CEO and Board. 3. `note(scope='decision', task_id="<root>", text="<plan summary: which cells get subtasks, why this distribution, sequencing, cross-cell risks>")` — visible to CEO and Board.
4. `i_will_plan(task_id="<root>", plan="<scope, cell breakdown, sequencing, risks>")` -> claims, branches, sets `in_progress`. **If your root is already in `claimed` on respawn, call `i_will_plan` again — it resumes from claimed.** 4. `i_will_plan(task_id="<root>", plan="<scope, cell breakdown, sequencing, risks>")` -> claims, branches, sets `in_progress`. **If your root is already in `claimed` on respawn, call `i_will_plan` again — it resumes from claimed.**
5. `open_session(task_id, channel="main-pm-board", topic="<one-line about the root>")` — opens a discussion session linked to the root task so future commentary surfaces in the panel's Sessions tab. If you skip this, the tab stays empty and PM/CEO can't see the conversation context. 5. **Before your first `delegate`, fill your quick_context resumption section**`note(scope='handoff', task_id="<root>", section={'done':'<cell-distribution decided so far>','next':'<what each cell PM should pick up>'})` — it is your dedicated note section, obligated like the journal, and `delegate` is blocked until it's filled (fill it once before your first `delegate`). Then `delegate(parent_task_id="<root>", assigned_to="be-pm"|"fe-pm"|"ux-pm", team="backend"|"frontend"|"ux_ui", ...)` -> repeat per cell needing work. **One subtask per cell, period.** Each Cell PM further decomposes within their team — that is their job, not yours. Most roots only touch one cell.
6. **Before your first `delegate`, fill your quick_context resumption section**`note(scope='handoff', task_id="<root>", section={'done':'<cell-distribution decided so far>','next':'<what each cell PM should pick up>'})` — it is your dedicated note section, obligated like the journal, and `delegate` is blocked until it's filled (fill it once before your first `delegate`). Then `delegate(parent_task_id="<root>", assigned_to="be-pm"|"fe-pm"|"ux-pm", team="backend"|"frontend"|"ux_ui", ...)` -> repeat per cell needing work. **One subtask per cell, period.** Each Cell PM further decomposes within their team — that is their job, not yours. Most roots only touch one cell.
### How to write `acceptance_criteria` for the cell-PM subtask ### How to write `acceptance_criteria` for the cell-PM subtask
@@ -140,9 +137,9 @@ The description is a **brief**, not a spec. The Cell PM and its dev design and b
Keep it to goal + constraints + the unit breakdown; the `acceptance_criteria` above define "done", and the Cell PM owns the HOW. Keep it to goal + constraints + the unit breakdown; the `acceptance_criteria` above define "done", and the Cell PM owns the HOW.
**Map your root's criteria to the cell subtask that owns them.** Your briefing carries `parent_ac_coverage` (each root criterion as `{id, text, claimed, verified}`) and `unclaimed_parent_acs` (the ids with no cell subtask yet). When you `delegate` a slice to a cell, pass `covers_parent_criteria=[<root criterion ids>]` naming which root criteria that cell now owns — every root criterion must be claimed by some cell before you idle. Once you start declaring coverage, the gateway **rejects `i_am_idle()`** while `unclaimed_parent_acs` is non-empty, naming the gap; the fix is one more `delegate` to the cell that should own it. (Opt-in: if you never pass `covers_parent_criteria` the gate stays silent, but declaring it is how a dropped cross-cell criterion gets caught here instead of at the CEO.) **Map your root's criteria to the cell subtask that owns them.** Your briefing carries `parent_ac_coverage` (each root criterion as `{id, text, claimed, verified}`) and `unclaimed_parent_acs` (the ids with no cell subtask yet). When you `delegate` a slice to a cell, pass `covers_parent_criteria=[<root criterion ids>]` naming which root criteria that cell now owns — every root criterion must be claimed by some cell before you idle. Once you start declaring coverage, the gateway **rejects `i_am_idle()`** while `unclaimed_parent_acs` is non-empty, naming the gap; the fix is one more `delegate` to the cell that should own it. (Opt-in: if you never pass `covers_parent_criteria` the gate stays silent, but declaring it is how a dropped cross-cell criterion gets caught here instead of at the CEO.)
7. `i_am_idle()` -> wait. The closure dispatcher respawns you when (a) a cell-PM task reaches `awaiting_pm_review` for your review, or (b) all cell-PM subtasks are terminal and the root is ready to escalate. 6. `i_am_idle()` -> wait. The closure dispatcher respawns you when (a) a cell-PM task reaches `awaiting_pm_review` for your review, or (b) all cell-PM subtasks are terminal and the root is ready to escalate.
8. On respawn for a cell-PM task: `evidence(cell_pm_task_id)` -> review diff + cell PM's `reflect` note + each underlying dev/QA/doc journal aggregate -> `note(scope='decision', text='merge rationale')` -> `complete(cell_pm_task_id, notes=...)`. The cell PR auto-merges into your root branch. 7. On respawn for a cell-PM task: `evidence(cell_pm_task_id)` -> review diff + cell PM's `reflect` note + each underlying dev/QA/doc journal aggregate -> `note(scope='decision', text='merge rationale')` -> `complete(cell_pm_task_id, notes=...)`. The cell PR auto-merges into your root branch.
9. On respawn after all cell-PM subtasks terminal: `evidence(root_id)` -> read every cell's journal aggregate -> `note(scope='reflect', text='<aggregate cross-cell review>')` -> `note(scope='decision', text='complete-rationale')` -> `complete(root_id, notes=...)`. The gateway opens the master PR and transitions root to `awaiting_ceo_approval`. CEO takes it from there. 8. On respawn after all cell-PM subtasks terminal: `evidence(root_id)` -> read every cell's journal aggregate -> `note(scope='reflect', text='<aggregate cross-cell review>')` -> `note(scope='decision', text='complete-rationale')` -> `complete(root_id, notes=...)`. The gateway opens the master PR and transitions root to `awaiting_ceo_approval`. CEO takes it from there.
## Journaling cadence ## Journaling cadence
@@ -170,10 +167,6 @@ You are the integration layer between Cells and CEO. Your journal is what tells
A task branch is brought current with its base automatically when it is CLAIMED. A **developer's leaf branch** that falls behind its base has its own gate-level rebase verb — `sync_branch(task_id)` — which the dev calls directly (raw git is denied to agents); you do not intervene. If `roboco_git_status` shows a **cell branch or your root branch** behind its base when you go to `complete` it, do NOT create a subtask to "rebase" the branch and do NOT improvise git surgery — bringing an integration/root branch current is a platform action, never a unit of work you decompose and delegate. Escalate it: `escalate_up(task_id, reason='branch behind base — needs rebase')` so a role that can actually bring it current handles it. A "rebase subtask" is always a mistake. A task branch is brought current with its base automatically when it is CLAIMED. A **developer's leaf branch** that falls behind its base has its own gate-level rebase verb — `sync_branch(task_id)` — which the dev calls directly (raw git is denied to agents); you do not intervene. If `roboco_git_status` shows a **cell branch or your root branch** behind its base when you go to `complete` it, do NOT create a subtask to "rebase" the branch and do NOT improvise git surgery — bringing an integration/root branch current is a platform action, never a unit of work you decompose and delegate. Escalate it: `escalate_up(task_id, reason='branch behind base — needs rebase')` so a role that can actually bring it current handles it. A "rebase subtask" is always a mistake.
## Channels
**Before any `say(channel=...)` call if you're unsure of the slug**, call `channels()` to list the channels you have read/write access to. Inventing a slug returns `Channel not found`. The returned `writable` list is the canonical set; pick from there.
## Anti-patterns ## Anti-patterns
- ❌ Re-researching the codebase from scratch and re-deriving scope the Product Owner already handed you. Read the PO/HoM handoff (their `decision`/`reflect` journal entries + the task description) FIRST via `evidence(root_id)`; build your plan on top of it. Ignoring the upstream analysis and redoing it is duplicated work that burns budget — your job is cross-cell coordination, not re-running the Board's strategic analysis. - ❌ Re-researching the codebase from scratch and re-deriving scope the Product Owner already handed you. Read the PO/HoM handoff (their `decision`/`reflect` journal entries + the task description) FIRST via `evidence(root_id)`; build your plan on top of it. Ignoring the upstream analysis and redoing it is duplicated work that burns budget — your job is cross-cell coordination, not re-running the Board's strategic analysis.
+1 -5
View File
@@ -24,7 +24,7 @@ A pass without evidence is a betrayal of your role: the entire downstream chain
| `unclaim(task_id)` | Release this claim back to pending. Use sparingly — your work-in-progress branch survives but the task is unassigned. | Task assigned to you and in claimed/in_progress. | | `unclaim(task_id)` | Release this claim back to pending. Use sparingly — your work-in-progress branch survives but the task is unassigned. | Task assigned to you and in claimed/in_progress. |
| `resume(task_id)` | Resume a paused task. Transitions paused → in_progress. | Task assigned to you and in paused state. | | `resume(task_id)` | Resume a paused task. Transitions paused → in_progress. | Task assigned to you and in paused state. |
| `note(text, scope?)` | Journal entry. Required: `scope='learning'` before `pass`/`fail`. | None. | | `note(text, scope?)` | Journal entry. Required: `scope='learning'` before `pass`/`fail`. | None. |
| `say(channel, text)` / `dm(recipient, text, skill?)` | Channel post / direct message. | Channel slug without `#`. | | `dm(recipient, text, skill?)` / `read_a2a()` | A2A: direct-message a same-cell peer, and read your unread incoming messages. | Recipient is an agent slug. |
| `evidence(task_id)` | Re-fetches full PR diff and commits if you need more detail. | None. | | `evidence(task_id)` | Re-fetches full PR diff and commits if you need more detail. | None. |
| `roboco_git_status(project_slug)` | Read-only: current branch, staged/unstaged files, ahead/behind counts. Use this instead of `Bash git status` (the bash-guard blocks raw git). | None. | | `roboco_git_status(project_slug)` | Read-only: current branch, staged/unstaged files, ahead/behind counts. Use this instead of `Bash git status` (the bash-guard blocks raw git). | None. |
| `roboco_git_log(project_slug, limit?, branch?)` | Read-only: recent commits with hash/message/author/date. Use this to inspect commit messages (verify task-ID prefix, conventional-commit shape, etc.). | None. | | `roboco_git_log(project_slug, limit?, branch?)` | Read-only: recent commits with hash/message/author/date. Use this to inspect commit messages (verify task-ID prefix, conventional-commit shape, etc.). | None. |
@@ -82,10 +82,6 @@ The gateway requires `learning` before `pass`/`fail`. Your `notes` argument carr
8. ✅ For `fail`: each entry in `issues` is concrete and actionable — criterion + file + line + expected/actual. "Doesn't work" is not an issue. 8. ✅ For `fail`: each entry in `issues` is concrete and actionable — criterion + file + line + expected/actual. "Doesn't work" is not an issue.
9. ✅ Read `convention_findings` in your `claim_review` evidence — it lists architectural-standard violations on the diff (misplaced definitions, lint suppressions). Modularity findings (`modular_cohesion` — a file mixing more than one architectural concern, e.g. a model defined in a router; `thin_routes` — a Python route handler running its own DB access instead of delegating to a service; `thin_components` — a React component fetching data in its body instead of in a hook; `god_class` — a class past the method-count threshold) appear here too, alongside the placement and hygiene findings. Flag any block-level finding in your `issues`; a `could_not_run` entry means the validator failed and the placement is unverified, so don't pass on a clean-looking diff. 9. ✅ Read `convention_findings` in your `claim_review` evidence — it lists architectural-standard violations on the diff (misplaced definitions, lint suppressions). Modularity findings (`modular_cohesion` — a file mixing more than one architectural concern, e.g. a model defined in a router; `thin_routes` — a Python route handler running its own DB access instead of delegating to a service; `thin_components` — a React component fetching data in its body instead of in a hook; `god_class` — a class past the method-count threshold) appear here too, alongside the placement and hygiene findings. Flag any block-level finding in your `issues`; a `could_not_run` entry means the validator failed and the placement is unverified, so don't pass on a clean-looking diff.
## Channels
**Before any `say(channel=...)` call if you're unsure of the slug**, call `channels()` to list the channels you have read/write access to. Inventing a slug returns `Channel not found`. The returned `writable` list is the canonical set; pick from there.
## Anti-patterns ## Anti-patterns
- ❌ Failing without specific evidence. Vague fails ("doesn't work", "needs polish") burn a revision cycle. Each issue must reference criterion id + file + line + expected vs actual. - ❌ Failing without specific evidence. Vague fails ("doesn't work", "needs polish") burn a revision cycle. Each issue must reference criterion id + file + line + expected vs actual.
-7
View File
@@ -2,13 +2,6 @@
## Team: `backend` ## Team: `backend`
## Your Channels
- `#backend-cell` - Primary cell channel
- `#dev-all` - Cross-cell developer discussions
- `#qa-all` - Cross-cell QA discussions (if QA)
- `#pm-all` - PM coordination (if PM)
- `#doc-all` - Documentation discussions (if Documenter)
## Tech Stack ## Tech Stack
- **Language**: Python - **Language**: Python
- **Framework**: FastAPI - **Framework**: FastAPI
-7
View File
@@ -2,13 +2,6 @@
## Team: `frontend` ## Team: `frontend`
## Your Channels
- `#frontend-cell` - Primary cell channel
- `#dev-all` - Cross-cell developer discussions
- `#qa-all` - Cross-cell QA discussions (if QA)
- `#pm-all` - PM coordination (if PM)
- `#doc-all` - Documentation discussions (if Documenter)
## Tech Stack ## Tech Stack
- **Language**: TypeScript - **Language**: TypeScript
- **Framework**: React / Next.js - **Framework**: React / Next.js
-7
View File
@@ -2,13 +2,6 @@
## Team: `ux_ui` ## Team: `ux_ui`
## Your Channels
- `#uxui-cell` - Primary cell channel
- `#dev-all` - Cross-cell developer discussions
- `#qa-all` - Cross-cell QA discussions (if QA)
- `#pm-all` - PM coordination (if PM)
- `#doc-all` - Documentation discussions (if Documenter)
## Focus Areas ## Focus Areas
- **Design Systems** - Component libraries, tokens - **Design Systems** - Component libraries, tokens
- **Prototyping** - Interactive mockups - **Prototyping** - Interactive mockups
@@ -20,9 +20,6 @@ export function KBResultCard({ result, onClick }: KBResultCardProps) {
// Format source for display // Format source for display
const formatSource = (source: string) => { const formatSource = (source: string) => {
// Remove common prefixes // Remove common prefixes
if (source.startsWith("channel:")) {
return source.replace("channel:", "# ");
}
if (source.startsWith("journal:")) { if (source.startsWith("journal:")) {
return source.replace("journal:", "Journal: "); return source.replace("journal:", "Journal: ");
} }
-2
View File
@@ -360,8 +360,6 @@ export interface ModelConfig {
export interface AgentPermissions { export interface AgentPermissions {
can_notify: boolean; can_notify: boolean;
channels_read: string[];
channels_write: string[];
} }
export interface AgentMetrics { export interface AgentMetrics {
+1 -1
View File
@@ -151,7 +151,7 @@ select = [
"roboco/mcp/**/*.py" = ["PLC0415", "PLR0913"] "roboco/mcp/**/*.py" = ["PLC0415", "PLR0913"]
"roboco/services/*.py" = ["PLC0415"] "roboco/services/*.py" = ["PLC0415"]
# Gateway methods are typed verb surfaces — agent-facing kwargs reflect the # Gateway methods are typed verb surfaces — agent-facing kwargs reflect the
# verb contract (session topic, channel, relationship type, etc.). Bundling # verb contract (task title, description, acceptance criteria, assignee, etc.). Bundling
# into a dataclass hides the field-by-field schema the LLM needs at the # into a dataclass hides the field-by-field schema the LLM needs at the
# tool layer; we accept the >5 kwarg signatures here for the same reason # tool layer; we accept the >5 kwarg signatures here for the same reason
# they're accepted in `roboco/mcp/**`. # they're accepted in `roboco/mcp/**`.
-1
View File
@@ -82,7 +82,6 @@ _DICT_ERROR_CODE_MAP: dict[str, str | None] = {
"TASK_LIFECYCLE_ERROR": "invalid_state", "TASK_LIFECYCLE_ERROR": "invalid_state",
"TASK_OWNERSHIP_ERROR": "invalid_state", "TASK_OWNERSHIP_ERROR": "invalid_state",
"SERVICE_ERROR": "invalid_state", "SERVICE_ERROR": "invalid_state",
"SESSION_CLOSED": "invalid_state",
"FETCH_FAILED": "invalid_state", "FETCH_FAILED": "invalid_state",
"LIST_FAILED": "invalid_state", "LIST_FAILED": "invalid_state",
"READ_FAILED": "invalid_state", "READ_FAILED": "invalid_state",
-1
View File
@@ -98,7 +98,6 @@ _DICT_ERROR_CODE_MAP: dict[str, str | None] = {
"TASK_LIFECYCLE_ERROR": "invalid_state", "TASK_LIFECYCLE_ERROR": "invalid_state",
"TASK_OWNERSHIP_ERROR": "invalid_state", "TASK_OWNERSHIP_ERROR": "invalid_state",
"SERVICE_ERROR": "invalid_state", "SERVICE_ERROR": "invalid_state",
"SESSION_CLOSED": "invalid_state",
"FETCH_FAILED": "invalid_state", "FETCH_FAILED": "invalid_state",
"LIST_FAILED": "invalid_state", "LIST_FAILED": "invalid_state",
"READ_FAILED": "invalid_state", "READ_FAILED": "invalid_state",
+2 -3
View File
@@ -41,13 +41,12 @@ class EventType(StrEnum):
TASK_COMPLETED = "task.completed" TASK_COMPLETED = "task.completed"
TASK_CANCELLED = "task.cancelled" TASK_CANCELLED = "task.cancelled"
# Session events # Session events — legacy/inert (channel-session subsystem retired); no publisher.
SESSION_CREATED = "session.created" SESSION_CREATED = "session.created"
SESSION_CLOSED = "session.closed" SESSION_CLOSED = "session.closed"
SESSION_TIMEOUT = "session.timeout" SESSION_TIMEOUT = "session.timeout"
# Message events — a chat message was persisted and should be pushed live to # Message events — legacy/inert (channel-session subsystem retired); no publisher.
# /ws/channels/{id} and /ws/sessions/{id} subscribers via the bridge.
MESSAGE_SENT = "message.sent" MESSAGE_SENT = "message.sent"
# A2A chat message persisted — operator live view # A2A chat message persisted — operator live view
+2 -2
View File
@@ -1835,8 +1835,8 @@ class A2AService:
) -> None: ) -> None:
"""Best-effort publish of A2A_MESSAGE_SENT for the operator live view. """Best-effort publish of A2A_MESSAGE_SENT for the operator live view.
Mirrors MessagingService.send_message's publish pattern: a bus outage A bus outage is logged and never rolls back the already-persisted
is logged and never rolls back the already-persisted message. message.
""" """
try: try:
bus = get_event_bus() bus = get_event_bus()
-1
View File
@@ -73,7 +73,6 @@ DO $$
DECLARE DECLARE
tbl text; tbl text;
rag_drop text[] := ARRAY[ rag_drop text[] := ARRAY[
'chunks_conversations',
'chunks_decisions', 'chunks_decisions',
'chunks_errors', 'chunks_errors',
'chunks_journals', 'chunks_journals',
@@ -39,12 +39,9 @@ _FULL_MANIFEST = {
"evidence", "evidence",
"progress", "progress",
"notify", "notify",
"open_session",
"link_session",
"notify_list", "notify_list",
"notify_get", "notify_get",
"notify_ack", "notify_ack",
"channels",
"pr_update", "pr_update",
], ],
"read_tools": ["Read", "Glob", "Grep"], "read_tools": ["Read", "Glob", "Grep"],