Files
roboco/docs/map/prompts-roles-taxonomy.md
T
7901ea419e Retire channels/sessions/messages; A2A becomes primary agent comms (#306)
* feat(a2a): deliver latest incoming message preview into the claim briefing

list_unread_a2a now carries last_message_preview (the latest message from the
OTHER agent, never the agent's own reply), fetched via a correlated subquery in
the same query — no N+1 on the per-verb briefing path.

* feat(a2a): read_a2a verb delivers unread message bodies to the agent

A2AService.get_unread_messages returns the caller's unread INCOMING messages
(never its own sends), marking exactly those rows read atomically so a message
arriving mid-call is preserved. Wired as the read_a2a content verb (route +
do_server tool + granted to every delivery role) — the content-bearing read the
A2A inbox lacked (read_messages only zeroed the counter).

* docs(rag): document read_a2a as the A2A content-read path

* fix(task): backlog activation no longer requires a discussion session

Removes the SessionTaskTable gate in activate() (and its dangling log field),
deletes _inherit_parent_session + its create() call, and drops the now-unused
SessionTaskTable import. Coordination rides task state; the session subsystem is
being retired. Tests updated to the new (no-session) behavior.

* fix(orchestrator): drop session sweep from _run_sweep

Removes the messaging import + sweep_timed_out_sessions call. That import sat
outside the try/except, so once messaging.py is deleted it would have killed the
entire sweep cascade (budget kill-switch, token rollups, retention, image prune,
superseded-PR reconcile). Notification sweep + all maintenance sweeps unchanged.

* release-manager --no-tags read-clone fix

* test: update evidence_repo unit test for a2a last_message_preview

* refactor(gateway): drop session propagation on delegate

Removes propagate_sessions_to_subtask from delegate(), the ChoreographerDeps
messaging field + property, and the ChoreographerDeps messaging arg in deps.py
(ContentActions messaging + import stay until the verbs are removed). Deletes the
propagation test; strips the now-invalid messaging kwarg from ChoreographerDeps
test builders.

* refactor(gateway): remove say/open_session/link_session/channels verbs

Removes the four channel/session verbs across content_actions (impls +
ContentActionsDeps.messaging), do_server (tools + registry), role_config (grants
+ _CHANNEL_DISCOVERY), do.py (routes), schemas/v1/do.py (request models), and
deps.py (MessagingService import + construction). Regenerates the prompt verb
tables. dm/notify/read_messages/read_a2a stay. Tests deleted/updated accordingly.

* uv.lock Upgrade

* refactor: remove conversation RAG indexing; Secretary announces via notification

Drops the CONVERSATIONS index (index_conversation, ConversationsIndexPlugin,
IndexType.CONVERSATIONS enum, IndexConversationParams, mentor.py type-label, the
messaging index hook) and its chunk-table manifest entries. The Secretary's
ANNOUNCE/RELAY_MESSAGE now fan out a BROADCAST notification to every agent's
inbox (NotificationService.broadcast) instead of posting to a dead channel.

* fix(panel): label RAG health error lines by subsystem

A red llm_error (e.g. the glm-5.2:cloud weekly-limit 429) rendered under
the 'Embedding: ok' header with no label, reading as an embedding failure.
Prefix each error line with LLM / Embedding / Vector store.

* refactor: remove channel/message reads from metrics, dashboard, git, events

MetricsService drops get_communication_volume + the MessageTable
message-count in get_agent_metrics (and the now-dead messages_sent_week
field). DashboardService drops get_channel_feeds/_compute_channel_status
and the message read in get_recent_activity (task activity kept);
get_auditor_metrics no longer reports communication_volume.
GitService's two primary-session-id helpers always return None now
(callers already treat None as "no primary session"). events/handlers.py
drops the SESSION_CLOSED/SESSION_TIMEOUT subscriptions + the
handle_session_boundary handler.

Forced follow-on: api/routes/dashboard.py + api/schemas/dashboard.py
dropped the now-dangling live_feeds/ChannelFeed surface and the
/metrics/communication route, which wrapped the removed service calls
directly (mypy would otherwise fail on the missing attributes).

* refactor: delete MessagingService + channel seeding

Edited db/__init__.py and services/__init__.py first (drop the unconditional
Channel/Group/Message/Session table + MessagingService re-exports), then
deleted services/messaging.py, then trimmed db/seed.py to only create_agents
(create_channels/create_channel_memberships/create_initial_messages gone).

Forced expansion: api/routes/{channels,groups,sessions,messages}.py import
roboco.services.messaging directly (not through the package __init__), as
does api/routes/tasks.py (the session-links embed on GET /tasks/{id} and the
GET /{id}/sessions route). Deleting messaging.py without addressing these
breaks `import roboco.api.app` immediately, since app.py eagerly imports all
route modules at startup. Since the 4 CRUD route files are 100%
MessagingService-backed with zero independent logic (and are wholesale
deletes in the plan's later API-routes task anyway), deleted them now +
unmounted from app.py/routes/__init__.py; tasks.py got the same surgical
trim its later task already specified (drop session-links embed +
TaskSessionLinkResponse/TaskResponse.sessions). This pulls a slice of that
later work forward — the routes/schemas for channels/groups/sessions/messages
still need their own pass, but their messaging-coupled parts are gone.

Verified with a full-suite collection sweep (12010 tests collected, zero
import errors) beyond the directly touched test dirs, given the expanded
blast radius.

* refactor: remove channel/session/message models, tables, and channel policy

Models: deleted channel.py/group.py/session.py/messaging.py wholesale
(zero external consumers besides the models/__init__.py re-export).
message.py surgically trimmed: removed MessageCreate (dead) and MessageEdit
(never instantiated; ExtractedMessage.edit_history retyped to
list[dict[str, Any]] to match how it's actually persisted — confirmed
ExtractedMessage was never written to any DB table, so MessageTable's
removal carries no functional risk to the kept extraction pipeline).
base.py: removed SessionStatus + ChannelType, kept MessageType. Also
removed the confirmed-dead channels_read/channels_write fields from
models/agent.py:AgentPermissions and models/dashboard.py:ChannelFeedData.

db/tables.py: deleted ChannelTable/GroupTable/SessionTable/SessionTaskTable/
MessageTable, TaskTable.session_links, and JournalEntryTable.session_id —
cascaded through models/journal.py, services/journal.py, and
api/schemas+routes/journals.py (22 plumbing sites).

foundation/policy/communications.py: removed the ChannelSpec/CHANNELS
catalog + TEAM_SCOPED_ROLES/_CELL_*/_AUDITOR_ONLY helpers, kept the
notification policy (Priority/parse_priority/NOTIFY_SENDER_ROLES/
ACK_REQUIRED_BY_TYPE). enforcement/channel_access.py deleted (confirmed
fully dead in production). agents_config.py: removed CHANNEL_ACCESS
(kept A2A_ALLOWED_PAIRS). seeds/initial_data.py: removed
DEFAULT_CHANNELS/CHANNEL_MEMBERSHIPS/AUDITOR_SILENT_ACCESS + the
never-consumed INITIAL_MESSAGES. config.py: removed
session_idle_timeout_seconds (zero consumers). exceptions.py: removed
dead ChannelError/ChannelAccessDeniedError/SessionClosedError.

Forced expansion beyond the original file list — ChannelType cascaded
into a live, mounted surface the plan didn't trace: agents_config.
CHANNEL_ACCESS -> services/permissions.py's channel-RBAC methods (not
models/permissions.py, which turned out to have no channel code at all)
-> two real endpoints in api/routes/stream.py (GET /permissions,
GET /permissions/channel/{name}) and two dependency factories in
api/deps.py. Removed the channel methods + fields, deleted the
channel-specific stream.py endpoint, deleted require_channel_read/write.
Also deleted api/schemas/{channels,sessions}.py (hard dependency on the
removed enums; already fully dead after the Task 10 route deletions) and
api/schemas/messages.py (a TYPE_CHECKING-only import of the deleted
MessageTable; likewise already fully dead) + its dedicated test file.

Test updates: test_permissions.py -14 channel tests (matches the planned
count exactly), test_communications.py / test_communications_consumers.py
split to keep only notification-policy coverage, test_exceptions.py -9,
test_deps.py -4, plus the journal/stream/foundation-smoke fallout. Also
fixed a pre-existing (Task 7) broken assertion in
test_foundation_phase3_smoke.py that inspected a `say()` method already
removed from ContentActions.

Verified: full-suite collection (11961 tests, zero import errors) and a
complete test run (11567 passed, 394 skipped, 0 failed) in addition to
the targeted suites.

* migration: drop channels/groups/sessions/session_tasks/messages + enum types

alembic/versions/060_drop_messaging.py: drop_column journal_entries.
session_id (sidesteps hardcoding the FK constraint name — verified
empirically against a live migrated DB that it's actually
fk_journal_entries_session_id_sessions, but drop_column doesn't care
either way); drop_table in FK order (messages -> session_tasks ->
sessions -> groups -> channels); DROP TABLE IF EXISTS chunks_conversations
(runtime-provisioned, not alembic-managed, would otherwise orphan); DROP
TYPE IF EXISTS for messagetype/sessionstatus/sessionscope/channeltype
(messagetype's Python enum stays for ExtractedMessage, but the DB type
had zero live columns left once MessageTable was dropped in the prior
commit). downgrade() raises NotImplementedError — one-way removal.

Pruned scripts/reset_runtime_state.sql + .sh: removed the DELETE/COUNT
lines for messages/session_tasks/sessions/groups/channels and the
groups.active_session_id reset block.

Verified end-to-end against a scratch Postgres DB: full migration chain
001->060 applies cleanly, alembic heads shows a single head, all 6 dropped
tables + 4 enum types + the journal_entries.session_id column are
confirmed gone, journal_entries keeps only its journal_id/task_id FKs,
downgrade correctly raises NotImplementedError without corrupting DB
state, and the pruned reset_runtime_state.sql runs clean (no errors)
against a fully-migrated DB.

* refactor(api): remove channel/session/message routes + WS streams

Most of this task's file list was already forced through in earlier
commits (routes/{channels,groups,sessions,messages}.py + app.py/__init__.py
unmounting in the MessagingService-deletion commit; tasks.py's
session-links embed + GET /{id}/sessions + schemas/tasks.py's
TaskResponse.sessions in that same commit; deps.py's require_channel_read/
write + schemas/{channels,sessions}.py in the models/tables commit). This
closes out what was left:

- api/websocket.py: deleted the channel_stream + session_stream routes,
  ConnectionManager's channel_connections/session_connections dicts,
  connect_channel/connect_session, broadcast_to_channel/broadcast_to_session,
  get_channel_subscriber_count, and their cleanup lines in disconnect().
  Agent streams, notification streams, and the operator system stream are
  untouched.
- api/websocket_bridge.py: deleted _handle_session_event +
  _handle_message_event and their SESSION_CREATED/SESSION_CLOSED/
  SESSION_TIMEOUT/MESSAGE_SENT subscriptions. The A2A live-view, rate-limit,
  usage, agent-lifecycle, and notification bridges are untouched.
- api/schemas/websocket.py: removed NewMessageBroadcast, WSMessageNew,
  WSMessageEdit, WSMessageDelete, WSSessionClosed — kept the WSMessage base
  class (still subclassed by the kept WSAgentStream/WSNotification) plus
  those two.
- api/schemas/groups.py: deleted (already fully orphaned since routes/
  groups.py was removed; its GroupResponse/GroupDetailResponse had zero
  consumers).

Updated the 5 websocket test files accordingly (removed the channel/
session-specific tests + fixed imports); test_websocket_bridge.py's
registration-coverage test dropped the SESSION_*/MESSAGE_SENT assertions.

Verified: full-suite collection (11943 tests, zero import errors) and a
complete test run (11549 passed, 394 skipped, 0 failed).

* docs: retire channels/sessions/messages from agent-facing docs + CLAUDE.md

Rewrites docs/rag (RAG-indexed) + docs/map + CLAUDE.md to reflect A2A (dm +
read_a2a) as primary agent comms; deletes the channel docs, splits messaging-tools
+ messaging-notification (renamed notification.md), swaps the WS worked example to
A2A_MESSAGE_SENT. _complete_map.md still needs regeneration (generated file).

* refactor(panel): remove Communications surface (channels/sessions)

Deletes the /communications routes, message components, task-detail Sessions tab,
use-channels + channel/session WS hooks, and the channels/sessions/messages/groups
api clients; prunes the Channel/Session/Message/Group types + mock data. (Auditor
live-feeds + dashboard.ts dead-route cleanup is a follow-up.)

* refactor(panel): drop auditor channel-feed + dead communication-metric route

* docs(map): regenerate _complete_map from updated slices

* fix(a2a): reduce get_unread_messages complexity below xenon C + stale comments

Extract the per-conversation unread-counter recompute into _reset_unread_counter
(the CI quality gate flagged get_unread_messages as rank C). Also drop the deleted
open_session from a content_actions comment and reword an evidence_repo docstring
that cited the removed messaging._notify_mentions.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-04 03:10:33 +02:00

41 KiB

Purpose

This slice is the prompt-composition pipeline and the role/team/permission taxonomy that feeds it. At agent spawn the orchestrator resolves an agent's role+team from the canonical foundation-derived maps in agents_config.py, then compose_prompt layers (in order) a tool-load directive, the autogenerated lifecycle verb surface, the universal base rules, the role prompt, the autogenerated per-role verb-signature table, the team prompt, the agent identity, and an optional architectural-conventions ambient block — writing the result to a per-agent .md file the runtime mounts. A separate prompt-injection guard (prompt_guard.py) denies poisoned incoming turns at the interactive input boundary for both Claude-SDK and Grok sessions, mirroring the bash UserPromptSubmit hook. agents_config.py is also the MCP-layer permission taxonomy (HMAC agent tokens, role/team helpers, escalation chain, A2A routing) that gates tool visibility and identity-binding at spawn.

Files

Path Role LOC
roboco/agents/factories/_base.py Layered prompt composer: loads/concatenates tool-directive + lifecycle + base + role + autogen-verbs + team + identity + ambient layers; exports PROMPTS_BASE_PATH, role/team/builtin-tool maps, compose_prompt, conventions_ambient_layer, make_slug 298
roboco/agents/factories/__init__.py One-line re-export shim pointing to _base 1
roboco/agents_config.py 691-line MCP-layer permission/taxonomy module: HMAC agent-token issue/verify, role+team+cell maps derived from foundation, escalation chain, ROLE_PERMISSION_LEVELS, ROLE_SKILLS, A2A routing helpers 691
roboco/agent_sdk/prompt_guard.py Reusable Python port of the bash injection guard: _PATTERNS regex list, detect_injection, refusal_message, CLI main (exit 1 on injection) for the grok entrypoint 93
agents/prompts/base.md Universal base layer: identity separation, gateway-verb-only action, envelope shapes, missing-key cheatsheet, resume-from-briefing, charter alignment, todo rules, ground rules 93
agents/prompts/roles/developer.md Developer role prompt: implement-only identity, verb table (give_me_work/i_will_work_on/commit/open_pr/i_am_done/sync_branch/...), workspace path, behind-base -> sync_branch guidance, conventions waiver path 25176
agents/prompts/roles/qa.md QA role prompt: review-only identity, claim_review/pass/fail/i_am_blocked verbs, ac_verdicts per criterion, circuit-breaker guidance 13355
agents/prompts/roles/documenter.md Documenter role prompt: docs-on-same-branch identity, claim_doc_task/commit/i_documented/i_am_blocked verbs, circuit-breaker guidance 10739
agents/prompts/roles/cell_pm.md Cell PM role prompt: coordinator identity, i_will_plan/delegate/complete/submit_up/unblock verbs, AC coverage gate, collision-surface declaration (intends_to_touch/adds_migration/touches_shared/depends_on), behind-base escalation 35682
agents/prompts/roles/main_pm.md Main PM role prompt: org-level coordinator, delegate/complete/submit_root/triage_all/unblock/escalate_to_ceo verbs, upstream-handoff precondition, branch-bearing vs branchless root gate 32363
agents/prompts/roles/pr_reviewer.md PR Reviewer role prompt: external-PR review (claim_pr_review/post_pr_review) + in-path gate (claim_gate_review/pr_pass/pr_fail), trust gate, conventions strictness 8129
agents/prompts/roles/board.md Board role prompt (PO/HoM/Auditor): strategic overseer, triage/escalate_to_ceo, no unblock verb, Auditor silent 12253
agents/prompts/roles/prompter.md Intake interviewer role prompt: CEO-only chat, propose_draft/propose_batch, MegaTask batch + per-cell-project-map drafting, root-subtask coordination-level AC guidance 13825
agents/prompts/roles/secretary.md Secretary role prompt: CEO chief-of-staff, gated-action confirm protocol, reading-free/prepare-direct/high-impact-bounce discipline 4338
agents/prompts/teams/backend.md Backend team layer: Python/FastAPI/Postgres stack, teammates, uv quality commands 983
agents/prompts/teams/frontend.md Frontend team layer: TS/Next.js stack, pnpm quality commands 976
agents/prompts/teams/ux_ui.md UX/UI team layer: design-system focus areas, teammates 1009
agents/prompts/identities/ 19 per-agent identity files (be-dev-1/2, fe-dev-1/2, ux-dev-1/2, be/fe/ux -qa/-doc/-pm, main-pm, product-owner, head-marketing, auditor): YAML id/name/role/team/cell/reports_to + scope blurb; loaded by slug 0
agents/prompts/_generated/lifecycle-developer.md Autogenerated lifecycle verb list for developer (regenerated from lifecycle spec by make lifecycle); lists sync_branch etc. 1498
agents/prompts/_generated/lifecycle-main_pm.md Autogenerated lifecycle verbs for main_pm; submit_root now branch-keyed not task_type-keyed 2034
agents/prompts/_generated/lifecycle-cell_pm.md Autogenerated lifecycle verbs for cell_pm 1839
agents/prompts/_generated/lifecycle-qa.md Autogenerated lifecycle verbs for qa 818
agents/prompts/_generated/lifecycle-documenter.md Autogenerated lifecycle verbs for documenter 736
agents/prompts/_generated/lifecycle-pr_reviewer.md Autogenerated lifecycle verbs for pr_reviewer 1071
agents/prompts/_generated/lifecycle-product_owner.md Autogenerated lifecycle verbs for product_owner 421
agents/prompts/_generated/lifecycle-head_marketing.md Autogenerated lifecycle verbs for head_marketing 422
agents/prompts/_generated/lifecycle-auditor.md Autogenerated lifecycle verbs for auditor (triage + i_am_idle) 325
agents/prompts/_generated/lifecycle-prompter.md Autogenerated lifecycle verbs for prompter (i_am_idle only — driver-based) 275
agents/prompts/_generated/lifecycle-secretary.md Autogenerated lifecycle verbs for secretary (i_am_idle only — driver-based) 276
agents/prompts/_generated/lifecycle-ceo.md Autogenerated lifecycle verbs for ceo (empty — human, not spawned) 181
agents/prompts/_generated/lifecycle-system.md Autogenerated lifecycle verbs for system sentinel (empty) 184
agents/prompts/_generated/developer.md Per-role autogenerated verb-signature table (Flow + Content tools) for developer, regenerated by scripts/regenerate_verb_tables.py from Pydantic schemas + role_config 2384
agents/prompts/_generated/qa.md Per-role autogenerated verb-signature table for qa (pass_review ac_verdicts carries BeforeValidator) 2040
agents/prompts/_generated/documenter.md Per-role autogenerated verb-signature table for documenter 2083
agents/prompts/_generated/cell_pm.md Per-role autogenerated verb-signature table for cell_pm; delegate signature includes intends_to_touch/adds_migration/touches_shared/depends_on 3270
agents/prompts/_generated/main_pm.md Per-role autogenerated verb-signature table for main_pm; delegate signature includes collision-surface fields 3316
agents/prompts/_generated/pr_reviewer.md Per-role autogenerated verb-signature table for pr_reviewer (claim_gate_review/pr_pass/pr_fail + claim_pr_review/post_pr_review) 1461
agents/prompts/_generated/product_owner.md Per-role autogenerated verb-signature table for product_owner (triage/escalate_to_ceo + pitch/notify) 1765
agents/prompts/_generated/head_marketing.md Per-role autogenerated verb-signature table for head_marketing 1765
agents/prompts/_generated/auditor.md Per-role autogenerated verb-signature table for auditor (triage/i_am_idle + note/evidence + approve/reject/archive_playbook) 1273
agents/prompts/_generated/verbs.md Aggregate reference doc of all per-role verb shapes (NOT injected at spawn; _base.py loads the per-role file instead); notes driver-based roles omitted 18302

Key Symbols

Name Kind File:Line Responsibility
_get_prompts_base_path function roboco/agents/factories/_base.py:17 Resolve project_root/agents/prompts/ from this file's location with a cwd-relative fallback
PROMPTS_BASE_PATH constant roboco/agents/factories/_base.py:37 Module-level cached prompts base path used by default in compose_prompt
_load_layer function roboco/agents/factories/_base.py:40 Read a prompt layer file, return '' if missing (graceful fallback)
_ROLE_LAYER_MAP dict roboco/agents/factories/_base.py:55 Maps role string -> roles/*.md filename; board roles all share board.md; prompter/secretary/pr_reviewer have own files
_TEAM_LAYER_MAP dict roboco/agents/factories/_base.py:77 Maps team string (backend/frontend/ux_ui) -> teams/*.md filename
_role_layer function roboco/agents/factories/_base.py:84 Load the role-specific prompt layer or None if role unknown
_team_layer function roboco/agents/factories/_base.py:93 Load the team prompt layer or None if unset/unknown
_autogen_verbs_layer function roboco/agents/factories/_base.py:104 Load _generated/.md autogenerated verb-signature table for the role
_BUILTIN_TOOLS_COMMON tuple roboco/agents/factories/_base.py:127 Built-in Claude Code tools every role gets: Read,Bash,Grep,Glob,TodoWrite
_BUILTIN_TOOLS_AUTHORS tuple roboco/agents/factories/_base.py:134 Authors set (developer/documenter) adds Edit,Write to the common set
_ROLE_BUILTIN_TOOLS dict roboco/agents/factories/_base.py:136 Per-role builtin-tool grant map; non-authors get common-only
_tool_load_directive_layer function roboco/agents/factories/_base.py:149 Build top-of-prompt 'your tools are ready' block; steers away from ToolSearch and shell-redirect rewrites
_lifecycle_layer function roboco/agents/factories/_base.py:187 Load _generated/lifecycle-.md canonical verb-surface fragment (from lifecycle spec, CI-gated)
compose_prompt function roboco/agents/factories/_base.py:203 Compose the full system prompt by concatenating tool-directive, lifecycle, base, role, autogen-verbs, team, identity, ambient layers with '---' separators, skipping empty layers
_AMBIENT_TOTAL_CAP constant roboco/agents/factories/_base.py:256 3000-char cap on the concatenated conventions ambient block
conventions_ambient_layer async function roboco/agents/factories/_base.py:259 Render per-project architectural-standard ambient block(s), multi-project headed, capped; None when conventions off / no projects
make_slug function roboco/agents/factories/_base.py:296 Lowercase + dash slug helper
_AUTH_SECRET_ENV constant roboco/agents_config.py:42 Env var name ROBOCO_AGENT_AUTH_SECRET for the HMAC signing key
_auth_secret function roboco/agents_config.py:45 Return HMAC secret bytes or None when unset
_signing_payload function roboco/agents_config.py:51 Canonical lowercase stripped agent_id:role:team HMAC message
issue_agent_token function roboco/agents_config.py:61 Mint hex HMAC-SHA256 token binding agent identity to role+team; returns UNSIGNED sentinel if secret unset
verify_agent_token function roboco/agents_config.py:79 Constant-time HMAC verification; fail-closed on unset secret / UNSIGNED
issue_panel_token function roboco/agents_config.py:94 Mint the CEO-identity token the panel presents (signed for CEO_AGENT_ID/ceo/empty team)
_UUID_TO_SLUG dict roboco/agents_config.py:109 Reverse map UUID->slug from AGENT_UUIDS seeds
_resolve_to_slug function roboco/agents_config.py:114 Resolve UUID or slug input to slug
AGENT_ROLE_MAP dict roboco/agents_config.py:127 slug->role.value for every non-SYSTEM agent (derived from foundation.AGENTS)
AGENT_TEAM_MAP dict roboco/agents_config.py:133 slug->team.value derived from foundation
CELL_MEMBERS dict roboco/agents_config.py:139 team.value -> sorted slug list per cell
ALL_AGENTS list roboco/agents_config.py:146 All agent slugs
BOARD_MEMBERS list roboco/agents_config.py:149 product-owner, head-marketing, auditor
ALL_DOCS list roboco/agents_config.py:152 Cross-cell documenter slugs for docs workspace perms
TASK_CREATOR_ROLES frozenset roboco/agents_config.py:159 Roles that can call task.create (cell_pm, main_pm, product_owner, head_marketing, ceo)
ESCALATION_CHAIN dict roboco/agents_config.py:170 slug -> escalation target slug (dev/qa/doc -> cell PM -> main-pm -> product-owner -> ceo)
get_agent_role function roboco/agents_config.py:204 Role string for an agent (UUID or slug); 'unknown' if missing
get_agent_team function roboco/agents_config.py:210 Team string for an agent or None
get_agent_cell function roboco/agents_config.py:216 Alias of get_agent_team
get_cell_members function roboco/agents_config.py:221 Slugs for a cell
is_pm function roboco/agents_config.py:226 cell_pm or main_pm predicate
is_board_member function roboco/agents_config.py:232 Board membership predicate by slug
is_management function roboco/agents_config.py:237 PM/Board/CEO predicate
is_ceo function roboco/agents_config.py:250 CEO predicate (full permission bypass)
can_send_notifications function roboco/agents_config.py:255 Role in foundation NOTIFY_SENDER_ROLES
can_create_tasks function roboco/agents_config.py:263 Role in TASK_CREATOR_ROLES
can_assign_tasks function roboco/agents_config.py:269 Same set as can_create_tasks
_CANCEL_ROLES set roboco/agents_config.py:276 Roles that may cancel (cell_pm/main_pm/product_owner/head_marketing — NOT ceo/auditor)
can_cancel_tasks function roboco/agents_config.py:284 Role in _CANCEL_ROLES
get_escalation_target function roboco/agents_config.py:290 Next escalation slug from ESCALATION_CHAIN
get_pm_for_team function roboco/agents_config.py:295 Cell PM slug for a team
get_pm_for_agent function roboco/agents_config.py:305 Responsible PM: cell PM for members, main-pm for cell PMs, product-owner for main PM
_slugs_for_role_set function roboco/agents_config.py:355 Expand a role-set to sorted slugs honoring optional team_scope; excludes system sentinel
ROLE_PERMISSION_LEVELS dict roboco/agents_config.py:404 Single source of truth role->permission-level (CEO/BOARD/AUDITOR/MAIN_PM/CELL_PM/CELL_MEMBER) used by PermissionService
VALID_NOTIFICATION_TYPES frozenset roboco/agents_config.py:425 Valid NotificationType values
VALID_NOTIFICATION_PRIORITIES frozenset roboco/agents_config.py:428 Valid NotificationPriority values
ROLE_SKILLS dict roboco/agents_config.py:438 Role -> A2A skill descriptor list for Agent Cards
get_agent_skills function roboco/agents_config.py:566 A2A skills for an agent by role
_BOARD_ROLES frozenset roboco/agents_config.py:584 Foundation board roles (PO/HoM/Auditor; main_pm intentionally excluded)
_MAIN_PM_TARGETS frozenset roboco/agents_config.py:585 Roles a main PM may A2A directly
_check_cell_pm_a2a function roboco/agents_config.py:590 A2A permission for cell PM (own cell / other PMs / main-pm allowed; board escalated)
_check_cell_member_a2a function roboco/agents_config.py:604 A2A permission for cell members (same-cell allowed; cross-cell via PMs)
_check_main_pm_a2a function roboco/agents_config.py:624 A2A permission for main PM (_MAIN_PM_TARGETS allowed)
can_a2a_direct function roboco/agents_config.py:632 (allowed, error) for direct A2A from one agent to another; routes CEO via notify, board/main_pm/cell-member via handlers
get_a2a_route_hint function roboco/agents_config.py:670 Human-readable routing hint for an A2A message
_PATTERNS list roboco/agent_sdk/prompt_guard.py:28 Five (regex, reason) injection patterns: ignore-previous, role-override, fake role prefix, control-token mimicry, fake executive-order
detect_injection function roboco/agent_sdk/prompt_guard.py:63 Return deny reason if text matches an injection pattern (lowercased), else None
refusal_message function roboco/agent_sdk/prompt_guard.py:72 Guidance string shown on denial (mirrors bash hook text)
main function roboco/agent_sdk/prompt_guard.py:82 CLI entry: exit 1 if argv[1] is an injection (used by grok entrypoint)

Data Flow

Spawn-time composition (synchronous, per agent): orchestrator._generate_prompt(role/team/agent_id, ambient) calls agents_config.get_agent_role/get_agent_team to resolve the canonical strings from foundation-derived AGENT_ROLE_MAP/AGENT_TEAM_MAP, converts to AgentRole/Team enums, then calls compose_prompt. compose_prompt resolves prompts_path (PROMPTS_BASE_PATH = project_root/agents/prompts) and builds an ordered list: _tool_load_directive_layer(role) (inline), _lifecycle_layer (reads _generated/lifecycle-.md), base.md, _role_layer (roles/.md via _ROLE_LAYER_MAP), _autogen_verbs_layer (_generated/.md), _team_layer (teams/.md via _TEAM_LAYER_MAP, None for board/main-pm), identities/<agent_slug>.md, then the optional ambient string. Empty/None layers are dropped; the rest are joined with "\n\n---\n\n". The composed string is written to /app/prompts-generated/<agent_id>-prompt.md (container) or $TMPDIR/roboco-prompts/ (host) and the path returned to the spawn path that mounts it as the agent's system prompt.

Ambient resolution (async, best-effort): orchestrator._resolve_conventions_ambient gates on settings.conventions_enabled, opens a DB session, resolves in-scope projects (single project_slug for delivery roles, or per-cell projects from a task's product_id for PO/Intake), and calls conventions_ambient_layer -> ConventionsService.render_ambient_block per project (ensuring a read clone), multi-project-headed, capped to 3000 chars. Any exception is caught and returns None so a compose is never blocked by conventions.

Identity binding at spawn: agents_config.issue_agent_token(agent_id, role, team) HMAC-signs the canonical lowercase agent_id:role:team with ROBOCO_AGENT_AUTH_SECRET and the orchestrator injects the token into the agent env; verify_agent_token (called server-side on X-Agent-Token headers) fail-closes on unset secret or UNSIGNED. The panel gets issue_panel_token() signed for the CEO identity.

Injection guard (runtime, per turn): IntakeDriver.send_turn calls detect_injection(text) before sending to the model; on a match it emits an error chunk with refusal_message(reason) and returns without forwarding. The grok one-shot entrypoint runs python -m roboco.agent_sdk.prompt_guard <text> and refuses start (exit 1) on a match. The same five patterns run in docker/scripts/user-prompt-hook.sh for non-SDK Claude sessions.

Callers: orchestrator.py:2971 (compose_prompt), orchestrator.py:3018/3029 (conventions_ambient_layer), intake_driver.py:374-377 (detect_injection/refusal_message). Callees from this slice: roboco.foundation.identity (AGENTS/Role/Team/slugs_for_team), roboco.foundation.policy.communications (NOTIFY_SENDER_ROLES), roboco.seeds.initial_data (AGENT_UUIDS/CEO_AGENT_ID), roboco.services.conventions (get_conventions_service), roboco.config.settings, roboco.models.base (NotificationType/Priority).

Mermaid

graph TD
    subgraph "Spawn-time prompt composition"
      O[orchestrator._generate_prompt] --> AC[agents_config.get_agent_role/team]
      AC --> FOUND[foundation.identity.AGENTS]
      O --> CP[compose_prompt]
      CP --> TLD[_tool_load_directive_layer role]
      CP --> LL[_lifecycle_layer _generated/lifecycle-role.md]
      CP --> BASE[base.md]
      CP --> RL[_role_layer roles/file.md]
      CP --> AVL[_autogen_verbs_layer _generated/role.md]
      CP --> TL[_team_layer teams/file.md]
      CP --> ID[identities/agent_slug.md]
      CP --> AMB[ambient string]
      TLD --> OUT["/app/prompts-generated/agent_id-prompt.md"]
      LL --> OUT
      BASE --> OUT
      RL --> OUT
      AVL --> OUT
      TL --> OUT
      ID --> OUT
      AMB --> OUT
    end
    subgraph "Ambient (async, best-effort)"
      OA[orchestrator._resolve_conventions_ambient] -->|settings.conventions_enabled| CAL[conventions_ambient_layer]
      CAL --> CS[ConventionsService.render_ambient_block]
      CS --> RC[ensure read clone]
      CAL -->|cap 3000| AMB
    end
    subgraph "Identity binding"
      IAT[issue_agent_token] --> HMAC[HMAC-SHA256 agent_id:role:team]
      VAT[verify_agent_token] --> HMAC
      IPT[issue_panel_token] --> IAT
    end
    subgraph "Injection guard (per turn)"
      IDV[IntakeDriver.send_turn] --> DI[detect_injection]
      DI -->|match| RM[refusal_message -> error chunk, return]
      DI -->|clean| MODEL[forward to model]
      GE[grok entrypoint] -->|CLI main exit 1| DI
      BASH[user-prompt-hook.sh] -.same 5 patterns.-> DI
    end

Logical Tree

prompts-roles-taxonomy slice
├── Prompt composition (roboco/agents/factories/)
│   ├── _base.py
│   │   ├── PROMPTS_BASE_PATH resolver
│   │   ├── _load_layer (file -> str|'')
│   │   ├── Layer maps: _ROLE_LAYER_MAP, _TEAM_LAYER_MAP
│   │   ├── Layer loaders: _role_layer, _team_layer, _autogen_verbs_layer, _lifecycle_layer
│   │   ├── Builtin-tool grant: _BUILTIN_TOOLS_COMMON/AUTHORS, _ROLE_BUILTIN_TOOLS, _tool_load_directive_layer
│   │   ├── compose_prompt (ordered join with '---')
│   │   └── conventions_ambient_layer (async, multi-project, 3000-char cap) + _AMBIENT_TOTAL_CAP
│   └── __init__.py (shim)
├── Permission taxonomy (roboco/agents_config.py)
│   ├── HMAC token layer: _auth_secret, _signing_payload, issue_agent_token, verify_agent_token, issue_panel_token
│   ├── UUID<->slug: _UUID_TO_SLUG, _resolve_to_slug
│   ├── Derived maps: AGENT_ROLE_MAP, AGENT_TEAM_MAP, CELL_MEMBERS, ALL_AGENTS, BOARD_MEMBERS, ALL_DOCS
│   ├── Role sets: TASK_CREATOR_ROLES, _CANCEL_ROLES, ROLE_PERMISSION_LEVELS, _BOARD_ROLES, _MAIN_PM_TARGETS
│   ├── Escalation: ESCALATION_CHAIN, get_escalation_target, get_pm_for_team, get_pm_for_agent
│   ├── Helpers: get_agent_role/team/cell, get_cell_members, is_pm/board_member/management/ceo, can_send_notifications/create/assign/cancel_tasks
│   ├── Notification enums: VALID_NOTIFICATION_TYPES/PRIORITIES
│   ├── A2A skills: ROLE_SKILLS, get_agent_skills
│   └── A2A routing: _check_cell_pm/cell_member/main_pm_a2a, can_a2a_direct, get_a2a_route_hint
├── Injection guard (roboco/agent_sdk/prompt_guard.py)
│   ├── _PATTERNS (5 regexes mirroring user-prompt-hook.sh)
│   ├── detect_injection, refusal_message
│   └── main (CLI for grok entrypoint)
└── Prompt corpus (agents/prompts/)
    ├── base.md (universal rules)
    ├── roles/ (9 files: developer, qa, documenter, cell_pm, main_pm, pr_reviewer, board, prompter, secretary)
    ├── teams/ (3 files: backend, frontend, ux_ui)
    ├── identities/ (19 per-agent YAML+blurb files)
    └── _generated/ (regenerated artifacts)
        ├── lifecycle-<role>.md (x14; from lifecycle spec via make lifecycle; CI-gated no-drift)
        ├── <role>.md verb-signature tables (x12; from schemas + role_config via regenerate_verb_tables.py)
        └── verbs.md (aggregate reference doc; NOT injected at spawn)

Dependencies

  • Internal: roboco.foundation.identity (AGENTS, Role, Team, CELL_TEAMS, BOARD_ROLES, slugs_for_team), roboco.foundation.policy.communications (NOTIFY_SENDER_ROLES), roboco.seeds.initial_data (AGENT_UUIDS, CEO_AGENT_ID), roboco.models.base (AgentRole, Team, NotificationType, NotificationPriority), roboco.config.settings (conventions_enabled), roboco.services.conventions (get_conventions_service, ConventionsService.render_ambient_block/resolve_workspace), roboco.db.base (get_session_factory), roboco.db.tables (ProjectTable), roboco.runtime.orchestrator (_generate_prompt, _resolve_conventions_ambient, _resolve_ambient_projects), roboco.agent_sdk.intake_driver (IntakeDriver.send_turn consumer), scripts/regenerate_verb_tables.py (regenerates _generated/.md + verbs.md), scripts/build_lifecycle_artifacts.py (regenerates _generated/lifecycle-.md; make lifecycle), docker/scripts/user-prompt-hook.sh (canonical bash guard mirrored by prompt_guard.py), roboco.api.schemas.v1 (Pydantic verb schemas the autogen tables derive from), roboco.services.gateway.role_config (role->verb config the autogen tables derive from)
  • External: pathlib.Path, hmac / hashlib (HMAC-SHA256 tokens), os.environ (ROBOCO_AGENT_AUTH_SECRET), re (injection regexes), sys (prompt_guard CLI), sqlalchemy.ext.asyncio.AsyncSession (ambient layer), typing.Final, argparse-less sys.argv CLI

Entry Points

Name File Trigger
orchestrator._generate_prompt roboco/runtime/orchestrator.py Called per agent spawn to compose + write the system-prompt .md file; calls compose_prompt (line 2971)
orchestrator._resolve_conventions_ambient roboco/runtime/orchestrator.py Async, called from the spawn path before _generate_prompt to resolve the optional ambient block; calls conventions_ambient_layer (line 3029)
IntakeDriver.send_turn roboco/agent_sdk/intake_driver.py Per interactive turn (Intake/Secretary Claude-SDK and Grok sessions); calls detect_injection before forwarding to the model (line 374)
python -m roboco.agent_sdk.prompt_guard roboco/agent_sdk/prompt_guard.py CLI invoked by the grok one-shot entrypoint on ROBOCO_INITIAL_PROMPT; exit 1 denies start
make lifecycle scripts/build_lifecycle_artifacts.py Developer/CI target regenerating _generated/lifecycle-*.md; CI gates on git diff --exit-code
scripts/regenerate_verb_tables.py scripts/regenerate_verb_tables.py Developer target regenerating _generated/.md + verbs.md after role_config/schema changes

Config Flags

  • ROBOCO_AGENT_AUTH_SECRET (env) — HMAC signing secret for agent/panel tokens; unset => verify_agent_token fail-closes (rejects every token), issue_*_token returns UNSIGNED
  • ROBOCO_CONVENTIONS_ENABLED — gates whether conventions_ambient_layer resolves + injects the architectural-standard ambient block; off => compose_prompt omits the ambient layer entirely
  • ROBOCO_SDK_URL (env, default http://localhost:9000) — used by the bash user-prompt-hook.sh (sister guard), not prompt_guard.py directly
  • ROBOCO_INITIAL_PROMPT (env) — the one-shot prompt the grok entrypoint hands to prompt_guard CLI main
  • PROJECT_HOST_PATH (orchestrator) — selects container (/app/prompts-generated) vs host ($TMPDIR/roboco-prompts) output dir for composed prompts

Gotchas

  • Layer ORDER matters and is load-bearing: tool-directive FIRST, then lifecycle, then base, then role, then autogen-verbs, then team, then identity, then ambient. The lifecycle fragment is intentionally before base so the agent reads its allowed verb surface before any other instruction. Reordering would change model attention priority.
  • Empty/missing layers are silently dropped (compose_prompt skips falsy layers). An unknown role yields _role_layer=None AND _autogen_verbs_layer=None AND _lifecycle_layer=None — the agent would still spawn with just tool-directive + base + identity + ambient, missing its entire role+verb surface. The orchestrator guards upstream (raises ValueError on unknown role), but a typo in _ROLE_LAYER_MAP silently degrades to a roleless prompt rather than failing.
  • _ROLE_LAYER_MAP maps all three board roles (product_owner/head_marketing/auditor) to the SAME board.md file. The per-role distinction (PO vs HoM vs Auditor) comes only from the identity file + the _generated/.md verb table, not from the role layer. A board role missing its identity file would lose its role-specific scope.
  • verbs.md is the aggregate reference doc but is NOT injected at spawn — _base.py loads the per-role _generated/.md file instead. Editing verbs.md has zero prompt effect; it is a documentation/CI artifact only. The per-role files are the load-bearing ones.
  • Identity YAML files carry a stale role: label (e.g. main-pm.md says role: pm, product-owner.md says role: board) that does NOT match the AgentRole enum values (main_pm/product_owner). The composition pipeline ignores this field entirely (loads identity by slug only); the real role comes from agents_config.AGENT_ROLE_MAP. Do not trust the identity YAML role label for enforcement.
  • Board members (product_owner/head_marketing/auditor) have team=None, so _team_layer returns None for them — they get no team layer. main-pm likewise. Only cell members (dev/qa/doc/cell_pm) get a team layer.
  • The autogen verb tables (_generated/.md) and lifecycle fragments are REGENERATED artifacts (make lifecycle / regenerate_verb_tables.py) gated on CI (git diff --exit-code). Hand-editing them is futile and will fail CI; change the source (lifecycle spec / Pydantic schemas / role_config) and regenerate.
  • prompt_guard.py mirrors user-prompt-hook.sh patterns but is a separate implementation. The two must be kept in sync manually — there is no shared source. Drift between them means Claude (bash hook) and Grok/SDK (Python guard) apply different deny rules.
  • detect_injection lowercases the text and uses loose anchoring (^|[\s>]) so injected content mid-message is caught, but the regexes are intentionally narrow (5 patterns). False negatives are expected by design — this is a classic-jailbreak denylist, not a comprehensive classifier; content that doesn't match still reaches the model.
  • conventions_ambient_layer is best-effort and wraps the whole resolution in a try/except in the orchestrator (_resolve_conventions_ambient). A conventions resolution failure degrades silently to no ambient layer — a compose is never blocked by conventions. This means a conventions regression could quietly stop injecting the standard with no error surface.
  • _AMBIENT_TOTAL_CAP (3000) truncates the ambient block with a trailing ellipsis. A large multi-project spawn (PO spanning several cells) can have its architectural standard silently truncated mid-block, leaving the agent with a partial standard.
  • HMAC token verification fail-closes when ROBOCO_AGENT_AUTH_SECRET is unset — every agent token is rejected. issue_agent_token returns the literal sentinel 'UNSIGNED' which verify_agent_token also rejects. Deploying without the secret bricks all agent API auth (by design).

Drift from CLAUDE.md

  • CLAUDE.md Project Overview states '25 AI agents + 1 human CEO', but agents/prompts/base.md line 3 says '22 AI agents + 1 human CEO'. The base prompt agent count is stale relative to CLAUDE.md (memory notes a 20->22 update on 2026-06-16; CLAUDE.md later moved to 25). A spawned agent reads '22' in its system prompt while the org chart it sees has 25.
  • CLAUDE.md's verb-surface table lists i_am_blocked for the qa and documenter roles. The role prompts qa.md and documenter.md ONLY added the i_am_blocked verb row in commit 15effce0 (this slice's baseline diff) — before that the role prompts omitted it even though the gateway accepted it. The prompts are now aligned, but the documenter.md circuit-breaker section previously explicitly said 'you don't have an i_am_blocked verb', which was false vs the gateway and vs CLAUDE.md. Fixed in 15effce0.
  • CLAUDE.md says the lifecycle is defined in roboco/foundation/policy/lifecycle.py with a shim at roboco/enforcement/task_lifecycle.py. _base.py:_lifecycle_layer (line 187-200) docstring says the lifecycle fragment is regenerated from roboco/lifecycle/spec.py by make lifecycle. The actual source path the regenerator uses is roboco/lifecycle/spec.py (per the docstring), which is not mentioned in CLAUDE.md's lifecycle section — minor doc-path drift, not behavioral.
  • CLAUDE.md describes the prompt composition as 'base + role + team + identity prompts' and an ambient 'Architectural Standard' block at spawn. The actual compose_prompt order (line 239-248) is tool-directive + lifecycle + base + role + autogen-verbs + team + identity + ambient — i.e. THREE additional layers (tool-directive, lifecycle, autogen-verbs) not named in CLAUDE.md's composition description. CLAUDE.md undersells the actual layer stack.
  • Identity files declare role: pm / role: board (e.g. identities/main-pm.md, identities/product-owner.md) which do not match the canonical AgentRole enum values (main_pm, cell_pm, product_owner, head_marketing, auditor) that CLAUDE.md and agents_config use. The pipeline ignores this label so it is cosmetic, but it is inconsistent with the canonical taxonomy CLAUDE.md documents.

Changes Since Baseline

SHA Subject Impact
15effce0 Chore: 141 Gaps fill-in (#283) — sole commit touching this slice since fd10cc86 Prompt-surface alignment with gateway/spec: (1) developer.md + lifecycle-developer.md + verbs.md add the new sync_branch verb and rewrite the behind-base guidance on developer/cell_pm/main_pm to point devs at sync_branch instead of i_am_blocked/escalate_up (cell/root integration branches still escalate). (2) qa.md and documenter.md add the i_am_blocked verb row and rewrite the circuit-breaker section to use i_am_blocked instead of 'you don't have an i_am_blocked verb -> unclaim' — corrects a false prompt claim. (3) cell_pm.md delegate signature + verbs.md add the collision-surface fields intends_to_touch/adds_migration/touches_shared/depends_on and a new 'Collision surface' section instructing the PM to declare them on every code subtask so siblings sequence. (4) pr_reviewer.md adds the in-path gate verbs claim_gate_review/pr_pass/pr_fail + an 'In-path gate review' section. (5) prompter.md adds MegaTask root-subtask coordination-level AC guidance (task_type=planning, coordination-level ACs). (6) lifecycle-main_pm.md submit_root description changes from 'Only for code roots' to 'branch-bearing roots; gate is branch-keyed not task_type-keyed'. (7) note verb schema in all autogen tables gains done/next/where_to_look top-level string params; pass_review ac_verdicts and delegate covers_parent_criteria/intends_to_touch now show BeforeValidator in the signature.

Post-snapshot updates (since 2026-06-29): 536bbb64 (Chore/all/logical gaps sweep #286) — (a) agents_config.py: _TEAM_SCOPED_ROLES deduped: was inline-defined, now re-exported as _comms.TEAM_SCOPED_ROLES from foundation.policy.communications (values unchanged: dev/qa/doc/cell_pm); (b) _generated/cell_pm.md, main_pm.md, qa.md, verbs.md: BeforeValidator repr cleaned from delegate/pass_review signatures — now renders list[str] | None = None instead of the memory-address-bearing BeforeValidator literal; (c) lifecycle spec: PRECONDITION_ROOT_NOT_CODE added to submit_root extra_preconditions, backing the branch-keyed / planning-typed claim the prompt asserts. aba57359 ([chore] lifecycle artifacts regenerate, foundation-check) — lifecycle-cell_pm.md, lifecycle-developer.md, lifecycle-documenter.md, lifecycle-main_pm.md, lifecycle-qa.md: unclaim description expanded with "A PR reviewer who claimed an external/gate review and cannot finish releases the claim here rather than wedging the lane"; lifecycle-cell_pm.md + lifecycle-main_pm.md: complete description clarified "The merge runs BEFORE the complete transition" ordering.

Regression Risks

Title File:Line Claim Severity
Collision-surface declaration is prompt-only, not gate-enforced agents/prompts/roles/cell_pm.md:141 The new 'Collision surface' section tells the cell PM to fill intends_to_touch/adds_migration/touches_shared on every code subtask so the analyzer can sequence colliding siblings, but the section explicitly says 'leave it empty only for a research/design subtask' — there is no documented gate that REFUSES a code delegate with empty intends_to_touch. If a PM omits it on a code subtask, two siblings editing the same file run in parallel and collide (the exact 2026-06-27 out-of-order break this was added to prevent). The protection hinges on agent compliance with prompt prose, not a hard gate. medium
sync_branch guidance contradicting the i_am_done gate for behind-base branches agents/prompts/roles/developer.md:126 developer.md now says 'call sync_branch as soon as roboco_git_status shows your branch behind, OR when i_am_done refuses with your branch is N behind'. If the i_am_done gate's behind-base check and sync_branch's rebase disagree on what 'base' means (e.g. base resolved from the recorded branch vs the parent task's head), a dev could sync_branch successfully and still hit the i_am_done behind-base refusal, looping. The prompt assumes both use the same base resolution; a divergence there would trap the dev. No fallback to i_am_blocked is offered anymore (the prompt explicitly forbids it for a plain behind-base condition), removing the previous escape hatch. medium
documenter/qa circuit-breaker now directs to i_am_blocked — verify the verb is actually granted to those roles VERIFIED OK agents/prompts/roles/documenter.md:100 The circuit-breaker section was rewritten from 'you don't have an i_am_blocked verb -> unclaim' to 'i_am_blocked(task_id, reason=...) to escalate'. This relies on i_am_blocked being genuinely callable by qa and documenter at the gateway. The autogen verbs.md shows i_am_blocked for qa but the documenter section in verbs.md (and _generated/documenter.md) must also list it — if the role_config does not grant i_am_blocked to documenter, the new prompt guidance sends the agent to a verb that will return not_authorized, trapping them on a circuit_open with no documented fallback (the old unclaim-only path was removed). **Verified 2026-07-01: lifecycle spec `i_am_blocked.allowed_roles = frozenset(_DEV_ROLES _QA_ROLES
submit_root description changed from code-root to branch-bearing-root semantics RESOLVED (536bbb64) agents/prompts/_generated/lifecycle-main_pm.md:14 The lifecycle fragment now says submit_root is 'For branch-bearing roots' and 'The gate is branch-keyed, not task_type-keyed — a Main-PM root is planning-typed, never code'. If the gateway's submit_root implementation still keys off task_type=code (the old contract), a planning-typed branch-bearing root would be rejected by the gate while the prompt tells the Main PM to call submit_root on it — a loop. The prompt now asserts a behavior the gateway must match; mismatch breaks Main-PM root submission. Fixed 2026-06-30: 536bbb64 added PRECONDITION_ROOT_NOT_CODE (_p_root_not_code: checks task_type != code) to submit_root.extra_preconditions in the lifecycle spec, and the spec description was updated to match; prompt and gateway are now aligned. high
note schema advertises done/next/where_to_look the gateway must accept agents/prompts/_generated/developer.md:25 All autogen verb tables now show note(...) with done/next/where_to_look top-level params. If the Pydantic note schema (the regenerator source) was updated but the gateway's note handler / DB journal model does not persist these fields, agents will pass them, the schema accepts them, but they are silently dropped — the handoff/quick_context fields the meltdown #1 fix intended to surface top-level would never reach downstream briefings. The prompt advertises params that may be no-ops at the persistence layer. medium
BeforeValidator rendering in verb signatures leaks into the prompt RESOLVED (536bbb64) agents/prompts/_generated/verbs.md:58 The regenerated verb tables now render BeforeValidator(func=<function coerce_str_list at 0x109dbdee0>, json_schema_input_type=PydanticUndefined) literally into the agent's system prompt for pass_review.ac_verdicts, delegate.covers_parent_criteria and delegate.intends_to_touch. This is a memory-address-bearing repr of an internal Pydantic validator injected into every qa/cell_pm/main_pm prompt. It is noise the model must parse around and the address is non-deterministic across runs, which could in principle perturb caching/reprompt determinism. Not a correctness bug but a prompt-hygiene regression introduced by the regenerator. **Fixed 2026-06-30: 536bbb64 cleaned the regenerated tables; all three fields now render `list[str] None = None`.**
cell_pm.md behind-base guidance split between dev sync_branch and cell-branch escalate_up agents/prompts/roles/cell_pm.md:178 The rewritten behind-base section tells the PM to direct devs to sync_branch for their leaf but to escalate_up for the cell integration branch. If a PM mis-classifies a behind-base condition (tells a dev to escalate_up instead of sync_branch, or calls escalate_up on a dev's leaf), the dev waits on a platform action that won't come (sync_branch is the dev's own verb). The split is correct but easy to misapply; a misroute strands the dev. low

Health

The composition pipeline is well-structured and deterministic: a single ordered join over gracefully-degrading layers, with CI-gated autogenerated artifacts (lifecycle + verb tables) that cannot silently drift from the spec, and a clean separation between the prompt corpus (markdown), the taxonomy (agents_config.py, derived from foundation so it cannot drift from the org chart), and the guard (prompt_guard.py, mirroring the bash hook). The main open integrity risks are (a) the prompt-only enforcement of the new collision-surface declaration on delegate — the 2026-06-27 out-of-order break this was added to fix can still recur if a PM omits intends_to_touch on a code subtask; (b) the stale agent-count in base.md (22 vs CLAUDE.md's 25) and the cosmetic but inconsistent role: labels in identity YAML. Previously flagged risks (b/c as of 2026-06-29 snapshot) have been closed: documenter/qa i_am_blocked is confirmed granted by the lifecycle spec (_DEV_ROLES|_QA_ROLES|_DOC_ROLES), and submit_root's branch-keyed claim is now backed by PRECONDITION_ROOT_NOT_CODE in the spec gate (536bbb64). BeforeValidator repr in prompt tables also cleaned (536bbb64).