Files
roboco/docs/map/product-strategy-research-pitch.md
401f8a2cc9 feat(board): Board Programs — the complete twelve-program catalog (Phases 1-3) (#699)
* feat(board): Pest Control — the first project-scoped Board Program

The Product Owner hunts latent defects (what the org records but nobody
reads): a weekly cycle — accelerated off-schedule when the trailing-7-day
rework rate crosses pest_rework_threshold, with the cheap dedup/scope gates
evaluated before the metrics queries — opens one held exploration task
against the least-recently-explored opted-in project (deterministic
round-robin; opted_in_projects gains a stable ORDER BY), with server-
assembled evidence in the spawn prompt (rework hotspots, recurring-findings
and waived-minor ledger aggregates, all capped) plus prior-cycle LEARN
context. The PO calls the new PO-only propose_bug_hunt verb once: ≤5 items,
evidence required per item, targets validated against pest_control
participation. CEO decides per item — approve materializes a BACKLOG task
(source pest_control, never auto-starts), reject records the reason; both
feed the LEARN ledger by exploration task id; all-terminal completes the
cycle. Telegram queue pushes carry working Approve/Reject handlers
mirroring the roadmap kind. Doctrine: board.md Pest Control section +
product-owner verb entry + regenerated verb tables.

* feat(panel): Pest Control review queue

Command Center gains the pest review queue (per-item approve/reject with
reason, mirroring the roadmap queue); the Programs card and the project
settings participates-in checkboxes pick the new program up registry-driven
— the settings section renders for the first time now that a project-scoped
program exists.

* feat(board): Periscope — HoM market-research brief program

Weekly org-scoped cycle: a solo HoM spawn researches the market (web
research with mandatory source URLs — uncited findings are rejected) and
files one structured brief via the new HoM-only propose_market_brief verb:
headline, cited findings, threats/opportunities, positioning note, all
soup-checked and screened through the injection guard at persist time
(web-derived text later reaches prompts; flags recorded, content never
dropped). A brief is a report, not a proposal: the verb completes the
exploration in the same call (the x_feature asymmetry), the cycle ledger
auto-closes, and the CEO gets a best-effort notification with no
approve/reject surface (periscope deliberately never joins Telegram's
action kinds). The latest brief is injected into the roadmap exploration
prompt — Periscope feeds Printer, the first cross-role program input.

* feat(panel): Market Briefs tab (read-only)

Business page gains a Market Briefs tab listing Periscope briefs —
headline, cited findings, threats/opportunities — read-only by design; a
report has nothing to approve.

* feat(board): Coroner — event-triggered Auditor postmortems

The first EVENT program: no cron — three best-effort hooks open an autopsy
when a task bounces to its 3rd revision (the audit chokepoint), is
cancelled after work started, or is budget-blocked; all gated on arming +
one-open-autopsy dedup, none can fail the underlying transition. A solo
Auditor spawn reads the incident (server-assembled findings + transition
context) and files one propose_postmortem: incident summary, root cause,
failed stage (validated against the real status vocabulary), and ONE
process change — a playbook-kind change drafts via PlaybookService
directly into the normal pending-curation queue; the briefed draft_playbook
manifest grant was deliberately NOT added, preserving the existing
'auditor curates but never drafts' invariant test. Complete-at-propose
(report asymmetry), cycle ledger auto-closes, CEO notified link-only.
Integrated as a union with Periscope across the shared program surfaces.

* feat(panel): Coroner postmortems card

Read-only postmortems list under Business → Programs — incident, root
cause, failed stage, process change; nothing to approve, the process-change
artifact (a draft playbook) rides the existing curation queue.

* feat(board): Sentinel — Auditor drift-watch quality reports

Weekly org-scoped cycle: a solo Auditor spawn receives a server-assembled
drift context (waived-findings trend, open findings by severity,
conventions-violation hotspots, top spend — all capped, pure ORM) and files
one propose_quality_report: headline, 1-7 area-validated items with
evidence and suggested actions, overall assessment. Report semantics —
complete-at-propose, cycle auto-closes, CEO notified display-only (never on
Telegram's approve/reject surface); items are structured so a later
convert-to-task control is cheap. Integration adopts Sentinel's module-
level dict-dispatch for board-program routing (xenon-driven), folding all
prior programs in; app router mounting extracted to a helper for the same
budget.

* feat(panel): Quality Reports tab (read-only)

Business page gains the Sentinel quality-reports tab — headline, per-area
observations with evidence and suggested actions; read-only, a report has
nothing to approve.

* feat(board): Spackle — gap-fill audit program

Biweekly project-scoped PO cycle over the half-shipped surface area: API
routes without panel surfaces (and vice versa), armed flags without docs,
docs promises the code doesn't keep, dead-end tabs — the inventory diffing
is the PO's own read-tool work, ordered by the spawn prompt with file:line
citations required; the server injects only prior-cycle LEARN and the
rotation target. Rotation is now a shared module-level helper
(pick_rotation_target, parameterized by source) both project-scoped
engines use — pest_control delegates to it, behavior-identical, with a
cross-pollution test proving the two programs' rotations stay independent.
propose_gap_fill mirrors the bug-hunt verb (≤5 items, two-sided evidence
required, participation gate); per-item CEO decide materializes BACKLOG
source=spackle tasks; full Telegram kind incl. approve/reject handlers.
All seven program routers now mount from one helper.

* feat(panel): Spackle gap-fill review queue

Command Center gains the gap-fill queue mirroring the pest-control one —
per-item approve/reject with the two-sided gap evidence rendered.

* feat(board): Scales — monthly portfolio rebalance

Org-scoped PO cycle over the stale backlog: the spawn receives a capped
stale-task snapshot (BACKLOG/PENDING unclaimed >30 days) plus the charter
and prior-cycle LEARN, and files one propose_rebalance — 1-7 items, each a
resolvable task_ref with action reprioritize (validated new priority) or
cancel, rationale required. Per-item CEO decide: approve EXECUTES the
action (audited priority update, or the normal cancel path) — the first
program whose materializer mutates existing tasks instead of creating
them; reject records the reason; LEARN by exploration task id;
all-terminal completes the cycle. Full Telegram decide-kind wiring.
Integrated as the eight-program union (registry, dict dispatch, routers
helper, teardown enumerations).

* feat(panel): Scales rebalance review queue

Command Center gains the rebalance queue — per-item approve/reject with
the action, target task, and rationale rendered.

* feat(board): Mirror — quarterly positioning audit

Project-scoped HoM cycle over messaging surfaces: README claims vs shipped
reality, docs-site promises vs code, charter alignment — the audit is the
HoM's own read-tool work with citations required; the server injects the
charter, prior-cycle LEARN, and the shared rotation target. propose_
messaging_fixes mirrors the gap-fill verb (≤5 items, drift evidence naming
claim + contradicting reality, participation gate); per-item CEO decide
materializes BACKLOG source=mirror documentation tasks; full Telegram
decide-kind wiring. Nine-program union across the shared surfaces.

* feat(panel): Mirror messaging-fixes review queue

* feat(board): Megaphone — HoM standing editorial calendar

Cron cycle (3 days, org-scoped, gated on X credentials — drafting content
nobody can post is pointless): the HoM receives a shipped-this-week digest
plus Unreleased changelog bullets and files one propose_editorial_post
(angle-validated, ≤280, brand voice) that materializes a held x_editorial
draft through the SAME X-queue origination chokepoint release posts use —
zero new approval surface, notifications and CEO decide for free.
Complete-at-propose; cycle auto-closes. Ten-program union.

* feat(panel): x_editorial source labels in the X queue surfaces

* feat(board): Librarian — proactive playbook mining

Biweekly org-scoped Auditor cycle: mines recurring non-private learning
journals (≥2-count grouping with a recency fallback) against the existing
playbook-title inventory and files one propose_playbook_drafts — 1-3
drafts, each with the repeated-pattern evidence that justifies it,
duplicate titles rejected in-batch and against the live store. Drafts are
created via PlaybookService directly (the Coroner precedent — the
'auditor curates but never drafts' do-verb invariant stays intact and
tested) and land in the normal pending-curation queue the Auditor's own
triage already surfaces; no new panel surface. Complete-at-propose;
display-only CEO notification. Eleven-program union.

* feat(board): War Room — release campaign planning

EVENT program with a REAL originator (unlike coroner's stub): a release
publish hooks a campaign brief beside the release-post seam, and the CEO's
run-now originates on demand — the cron loop never fires it. The HoM
designs a 2-6 post arc (teaser → launch → follow-up → spotlight; 280-cap,
future strictly-ascending publish_after, stage vocabulary) and one
propose_campaign call materializes each post as a held x_campaign draft
through the X-queue chokepoint. V1 is manual-cadence by design: publish_
after renders as queue guidance and the CEO approves each post at its
moment — nothing auto-posts, ever; the auto-schedule upgrade is a
documented ceiling. Twelve-program union: full registry complete.

* feat(panel): x_campaign labels + publish-after guidance in the X queue

* feat(board): Barfly — adjacent-conversation replies

Cron cycle (2 days, org-scoped, X-credentials gated): the engine searches
X for conversations where RoboCo is relevant but unmentioned (new OAuth-
signed search_recent on the client; queries + candidate cap configurable),
screens every fetched tweet through the injection guard (stored unclamped
— a clamp was truncating the candidate under the envelope, caught by the
dev's own tests), dedupes via the existing x_seen_mentions ledger (no
migration; also prevents double-drafting against the mentions poll), and
opens one held HoM exploration carrying the screened candidates. propose_
conversation_replies enforces candidate-id-only replies (≤5, 280-cap);
each materializes a held x_barfly draft through the X-queue chokepoint,
threaded via a new in_reply_to seam on post_tweet that only x_barfly
drafts use. The X redraft machinery is now dict-dispatch over per-source
extractors with reply-ref carry for x_barfly. Thirteen-program registry.
War Room's test fakes gained the new abstract search_recent stub.

* feat(board): Dogfood — the PO walks the product

The fourteenth and final registry entry, completing the catalog. EVENT
program (release-publish hook beside the war-room hook + CEO run-now, both
through the same real originator; the cron loop never fires it), project-
scoped with shared rotation. The permission surface is the careful part:
the PO's dogfood spawn — and ONLY that spawn — gets the Playwright MCP
mounted, via a task-scoped fail-closed probe mirroring the video-authoring
precedent (a PO spawned for roadmap/pest/scales never sees browser tools;
tested both ways); the PM agent image bakes chromium unconditionally like
the ux image, the mount stays task-gated in code. The walk targets the
rotation target's live surfaces (panel_base_url only when the target is
the org's own project, honest degradation otherwise); propose_friction_
fixes files ≤5 walked-path-evidenced items; per-item CEO decide
materializes BACKLOG source=dogfood tasks; full Telegram decide kind.
Also: megaphone/librarian/war_room arming keys restored to the settings
validator — their panel toggles would have been rejected (dropped in
earlier unions; the same silent-arming class the drill killed once
already).

* feat(panel): Dogfood friction review queue

* chore(board): final whole-branch sweep fixes

The night's closing adversarial pass over the integrated fourteen-program
registry found ONE functional defect — the war-room test fakes' post_tweet
predated Barfly's in_reply_to_tweet_id kwarg (LSP violation, the only red
in an otherwise fully green gate) — plus doc/test drift, all fixed: the
source-parity test completes to fourteen (spackle/mirror were silently
absent while its neighboring comment claimed full coverage), the PO
identity doc gains its missing Dogfood verb, the auditor quick-list gains
propose_postmortem, three stale comments corrected (rotation docstring,
panel registry header, X source enumerations), the dogfood release-hook
gains the exception-swallow test its four sibling hooks already had, and
the CHANGELOG's Unreleased section documents the whole Board Programs
train. Full make quality: exit 0, all gates green.

* docs: full documentation sweep for the Board Programs train

CLAUDE.md's roadmap-engine entry superseded by the Board Program registry
entry (all fourteen programs, arming, scoping, LEARN, guardrails) with the
role verb tables and playwright row refreshed; docs/rag gains the agent-
facing architecture doc plus full propose_* call-shape sections in the
three board role docs, and corrects the strategy-engine section to shipped
reality (only idle→roadmap is wired); docs/map covers the registry + all
twelve engines with flags, gotchas, and drift notes. The 0.27.0 reference
inventory confirmed only the release-executor's canonical set carries the
version — left for the 0.28.0 cut.

* feat(board): human titles + descriptions on every program surface

Raw registry keys rendered as bare panel labels — an operator reading
x_feature had no idea what enabling or running it does. The registry
dataclass gains title/description (test-enforced non-empty for every
entry, unique titles), the API passes them through, and every surface
renders title-with-description-tooltip instead of the key: the Programs
card (label, toggle hint, run-now toast), and the project settings
participates-in/excluded-from checkboxes.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-25 17:13:32 +02:00

78 KiB
Raw Permalink Blame History

Slice Map — product-strategy-research-pitch

Purpose

The product / strategy / research / pitch slice covers the "company layer" above the delivery lifecycle: registering the git repositories agents work on (Project), mapping cells to repos within a product (Product), rendering role-specific kanban views (Kanban), the singleton company charter (CompanyGoals), the dormant goal-drift watcher (StrategyEngine), the pluggable web-search capability for Board/PM agents (Research + ResearchQuota), Board pitches with CEO-approve → auto-provision (Pitch), and the single GitHub repo-creation service that backs provisioning (GitHubProvisioning). Together these are the CEO/Board-facing surface that originates work and feeds it into the normal delivery lifecycle, plus the per-cell routing keystone that the gateway delegate path consults at runtime.

Files

Path Role approx LOC
roboco/services/project.py CRUD + git-token encryption + cell access control for Projects (git repos) 604
roboco/services/product.py Product CRUD + per-cell project_for routing resolver + idempotent cell-map replace 152
roboco/services/kanban.py Role-specific kanban board views (dev/qa/documenter/pm/main-pm/board) from task data 587
roboco/services/company_goals.py CRUD for the singleton company charter (north star + objectives + constraints + policy + brand_voice + company_name); resolve_product_name is the shared product-name fallback chain XEngine/VideoEngine both call 110
roboco/services/strategy_engine.py Dormant "engine 2": assesses company state vs goals, notify-only to CEO 111
roboco/services/research.py Pluggable web-search/fetch — provider adapters (Tavily/Brave/Exa/Null) + clamping service 431
roboco/services/research_quota.py Per-agent UTC-daily Redis quota counter for research calls (fail-open) 78
roboco/services/pitch.py Board pitch CRUD + CEO approve → provision repos/Projects(+Product) + seed Main-PM task 274
roboco/services/github_provisioning.py The only service that CREATES repos for pitch provisioning — now provider-aware (GitHub/Gitea/GitLab, Phase 4 forge parity), despite the GitHub-flavored name (kept for backward compatibility) 232
roboco/services/roadmap_engine.py Dormant weekly engine: originates ONE held roadmap-exploration task for the Product Owner (default off) 111
roboco/services/roadmap_service.py CEO's per-item approve/reject glue over a held roadmap cycle; approve materializes a BACKLOG task 211
roboco/api/routes/roadmap.py CEO-only routes: list open cycles, approve/reject one item 124
roboco/services/x_engine.py Dormant "engine 4": drafts X (Twitter) release posts (event hook), mention replies (poll), and feature-spotlight explorations (dormant interval, spawns Head of Marketing), ALL held for CEO approval (default off); prompt builders take a product_name param resolved via CompanyGoalsService.resolve_product_name instead of hardcoding "RoboCo" 871
roboco/services/x_post_service.py CEO's approve/reject over a held X draft; approve posts via a Redis single-flight lock, idempotent on already-posted AND on already-rejected (CANCELLED) 298
roboco/services/x_client.py OAuth 1.0a HMAC-SHA1 X API client (LiveXClient) + NullXClient (no creds, never egresses) + build_x_client factory 318
roboco/services/x_credentials.py Singleton Fernet-encrypted OAuth 1.0a credential CRUD; decrypts server-side only 140
roboco/api/routes/x.py CEO-only routes: list open X posts, approve/reject one draft 164
roboco/api/schemas/x.py XPostResponse + XMentionRefModel / XFeatureRefModel response shapes 73
roboco/foundation/policy/board_programs.py Board Program registry: BoardProgram dataclass, PROGRAMS (14 entries), program_due/project_participates/validate_board_programs_field (pure) 295
roboco/services/board_programs.py BoardProgramEngine — trigger/dedup/originate/LEARN over every registered program; program_armed settings-store chokepoint; pick_rotation_target for project-scoped round-robin 566
roboco/api/routes/board_programs.py CEO-only routes: list every program's live status, POST /{key}/run-now 103
roboco/services/pest_control_engine.py PestControlEngine — weekly+metric cron, project-scoped; evidence context (rework hotspots, recurring/waived findings) 199
roboco/services/spackle_engine.py SpackleEngine — biweekly cron, project-scoped gap-fill audit 142
roboco/services/scales_engine.py ScalesEngine — monthly cron, org-scoped portfolio rebalance; stale-backlog snapshot 176
roboco/services/dogfood_engine.py DogfoodEngine — event-only, project-scoped; real _ORIGINATORS binding (unlike Coroner's stub) 152
roboco/services/periscope_engine.py PeriscopeEngine — weekly cron, org-scoped market-research brief; latest_brief_context feeds Printer 157
roboco/services/megaphone_engine.py MegaphoneEngine — 3-day cron, org-scoped editorial calendar; shipped-this-week digest + Unreleased changelog 191
roboco/services/mirror_engine.py MirrorEngine — quarterly cron, project-scoped messaging-drift audit 143
roboco/services/barfly_engine.py BarflyEngine — 2-day cron, org-scoped; screens X search candidates through injection_guard, marks seen 223
roboco/services/war_room_engine.py WarRoomEngine — event-triggered (open_for_release release hook + CEO run-now); campaign posts w/ publish_after 231
roboco/services/coroner_engine.py CoronerEngine — event-only (open_for_incident, no run_cycle/cron path); incident + transition-history context 237
roboco/services/sentinel_engine.py SentinelEngine — weekly cron, org-scoped drift report (waivers/findings/conventions/spend) 261
roboco/services/librarian_engine.py LibrarianEngine — biweekly cron, org-scoped proactive playbook mining 232

Key Symbols

Name Kind File:Line Responsibility
ProjectService class project.py:23 Project CRUD, slug lookup, cell listing, workspace path, git-token encrypt/decrypt, agent access control
ProjectService._assert_git_url_allowed method project.py:40 Reject git_url matching a protected/denylisted repo
ProjectService.create method project.py:56 Register project, encrypt token, best-effort conventions scaffold
ProjectService._maybe_scaffold_conventions method project.py:126 Lazy-imports conventions service; scaffold hiccup is non-fatal
ProjectService.update method project.py:166 Selective field apply; empty-string git_token clears, None leaves unchanged
ProjectService.delete method project.py:226 Abandons ACTIVE work sessions first; optional on-disk workspace cleanup; DB RESTRICT on tasks
ProjectService.get_decrypted_token / _by_slug method project.py:441 / 471 On-demand Fernet decrypt of git token (never cached)
ProjectService.check_agent_access method project.py:559 Cell membership + optional allowed_agents allowlist
get_project_service factory project.py:602 Session-bound constructor
ProductService class product.py:15 Product CRUD + cell→project routing
ProductService.project_for method product.py:87 Per-cell routing keystone — resolve Project for (product, team); None → caller falls back to parent task's project
ProductService.distinct_project_ids method product.py:103 Distinct repos a product spans (one Main-PM integration branch each)
ProductService._replace_cells method product.py:122 Idempotent full cell-map replace via cascade collection; flushes DELETEs before INSERTs to avoid uq_product_projects_product_team 409
get_product_service factory product.py:150 Session-bound constructor
KanbanService class kanban.py:30 Role-specific board generation with optional swimlanes
KanbanService._load_subtask_counts method kanban.py:47 Batch-count direct children per parent in ONE grouped query (fixes the always-0 stub; #198)
KanbanService._task_to_card method kanban.py:61 Task → KanbanCard; accepts optional subtask_counts dict for real subtask counts
KanbanService.get_dev_board method kanban.py:117 Dev cell board with optional priority/assignee swimlanes
KanbanService.get_qa_board / get_documenter_board / get_pm_board method kanban.py:335 / 366 / 400 Role-filtered flat boards
KanbanService.get_main_pm_board / _flat method kanban.py:419 / 435 Cross-cell view (team swimlanes / team columns)
KanbanService.get_board_kanban method kanban.py:538 Board roadmap (P0/P1 only)
KanbanService.get_board_stats method kanban.py:558 Status-count aggregation
CompanyGoalsService class company_goals.py:35 Singleton charter CRUD
SINGLETON_ID constant company_goals.py:23 Fixed UUID 0…0 — charter is one row
CompanyGoalsService.get / upsert method company_goals.py:38 / 44 Partial-key upsert; caller commits
StrategyEngine class strategy_engine.py:47 Assess company state vs goals
StrategyEngine.assess method strategy_engine.py:52 Pure read; emits idle + stranded_blocked observations
StrategyEngine.run_cycle method strategy_engine.py:92 No-op unless flag on; assess + notify CEO (notify-only, never spends/builds)
StrategyObservation dataclass strategy_engine.py:38 Frozen (kind, summary, detail)
ResearchService class research.py:337 Provider-agnostic entry point; clamps result/char caps
SearchProvider ABC research.py:80 Adapter base: shared/owned httpx client, _request_json, abstract search, default-unsupported fetch
TavilyProvider / BraveProvider / ExaProvider class research.py:162 / 212 / 242 Concrete adapters (Brave has no fetch)
NullProvider class research.py:288 Graceful stub — empty results, configured=False, never raises
build_provider func research.py:317 Select adapter by name; NullProvider when no key or unknown name
get_research_service factory research.py:391 Build service from settings (provider + caps)
SearchHit / SearchOutcome / FetchOutcome dataclass research.py:45 / 55 / 65 Normalised result shapes
ResearchError / ResearchUnsupportedError exception research.py:32 / 36 Provider failure vs unsupported-op (route maps 502 vs 501)
ResearchQuotaTracker class research_quota.py:34 Per-agent/day Redis INCR counter, 24h expiry, fail-open
ResearchQuotaTracker.check_and_consume method research_quota.py:51 Atomic INCR-then-compare; over-limit still bumps counter
QuotaStatus dataclass research_quota.py:24 (allowed, used, limit, day)
PitchService class pitch.py:56 Pitch CRUD + approve/reject
PitchService.approve method pitch.py:109 Provision repos → register topology → seed Main-PM task; idempotent on re-approval
PitchService._provision_repos method pitch.py:163 One repo per target cell (multi-cell suffixes cell); reuses existing Project by slug
PitchService._register_topology method pitch.py:201 Multi-cell → Product (reuse existing by slug + refresh cell map); single-cell → seed project only
PitchService._seed_main_pm_task method pitch.py:234 Creates PENDING Main-PM CODE task (source="pitch", confirmed_by_human=True)
PitchService._proposed_or_raise method pitch.py:152 404 if missing, 409 if not proposed (no re-deciding)
GitHubProvisioningService class github_provisioning.py:81 Create private repos for pitch provisioning — Phase 4 forge parity: provider-dispatched via _build_provider, not GitHub-only despite the class name
GitHubProvisioningService.enabled prop github_provisioning.py:123 True only when master switch + token + org all set; ALSO requires ROBOCO_PROVISIONING_HOST when the provider is gitlab/gitea (self-hosted needs a host, github.com doesn't)
GitHubProvisioningService.create_repo method github_provisioning.py:142 Provider-dispatched repo creation with auto_init=true; handles the "already exists" response idempotently across all three forges via _fetch_existing_repo/_is_already_exists (#83/#84)
GitHubProvisioningService._fetch_existing_repo method github_provisioning.py:203 GET the existing repo and reconstruct ProvisionedRepo — called on an "already exists" response to reuse an orphaned repo from a rolled-back prior approval
_build_provider func github_provisioning.py:68 Picks the concrete provider (GitHubProvider/GiteaProvider/GitLabProvider — a Union, not the GitProvider ABC, since provisioning needs client=/timeout= kwargs the ABC doesn't declare) by ROBOCO_PROVISIONING_PROVIDER
_is_already_exists func github_provisioning.py:61 Matches GitHub's 422, Gitea's 409/422, and GitLab's reshaped 422 "already exists"/"has already been taken" by status+phrase
ProvisionedRepo / ProvisioningError / ProvisioningDisabledError dataclass/exc github_provisioning.py Result + error types
RoadmapEngine class roadmap_engine.py:49 Dormant "engine 3": mirrors the release-manager "detect → originate a CEO-gated artifact → hold" shape, but the artifact is a cycle the PO authors, not a report the engine assembles
RoadmapEngine.run_cycle method roadmap_engine.py:54 No-op unless roadmap_engine_enabled, a cycle is already open (list_open_roadmap_cycles), or the RoboCo project isn't resolvable; else opens ONE held PENDING exploration task assigned to the Product Owner
RoadmapService class roadmap_service.py:50 List / approve / reject items within the open roadmap cycle(s)
RoadmapService.approve_item method roadmap_service.py:59 Materialize one proposed item as a BACKLOG task via PrompterService.create_task_from_draft; idempotent per item
RoadmapService.reject_item method roadmap_service.py:108 Record the CEO's reason; idempotent; an already-approved item cannot be rejected
RoadmapService._find_item method roadmap_service.py:146 Resolve (exploration task, deep-copied cycle payload, one item) — deep copy so mutation doesn't poison SQLAlchemy's dirty-check before markers.set_roadmap_cycle reassigns
RoadmapService._maybe_complete_cycle staticmethod roadmap_service.py:202 Completes the exploration task once every item on it is terminal (approved/rejected)
RoadmapItemResult dataclass roadmap_service.py:37 Outcome of one approve/reject call (status/item_id/materialized_task_id/detail)
get_roadmap_engine / get_roadmap_service factory roadmap_engine.py:109 / roadmap_service.py:209 Session-bound constructors
XEngine class x_engine.py:230 Dormant "engine 4": mirrors the release-manager "detect → originate a CEO-gated artifact → hold" shape across THREE responsibilities — release posts, mention replies, feature spotlights
XEngine._voice_guide method x_engine.py:259 _voice_guide(product_name): baseline house-voice constant (_HOM_VOICE) plus the CEO's company_goals.brand_voice sample when set — feeds release/reply prompts AND is the mechanism the HoM identity file points to for its own drafting; product_name is resolved once per call site via CompanyGoalsService.resolve_product_name(project) (project's own name → charter company_name → "RoboCo" literal), not hardcoded
_hom_voice / _HOM_VOICE_GUIDE func / const x_engine.py The baseline voice fed into every local-model draft prompt (release/reply) — ported from the reasoning-backed Head-of-Marketing identity's own VOICE GUIDE (previously a one-sentence stub reaching only the off-by-default spotlight path) plus a slop-ban list (banned words, em-dash ban, exclamation-pileup ban, "X isn't just Y" ban, rule-of-three ban) adapted from the ux_ui team's "AI tells to avoid", and three fixed style exemplars. Both prompts now target well-under-240-chars so the 280 clamp never truncates mid-sentence.
changelog_highlights func x_engine.py Pure extraction of the bold feature-lead lines (- **Headline (#N).**) from a curated CHANGELOG release entry, stripping PR refs and trailing periods, capped at limit; approve() prefers these over raw change_summary (per-commit subjects) for the release-post caption, falling back to change_summary only when the changelog yields nothing
XEngine.redraft_from_rejection method x_engine.py Routes a CEO's non-blank rejection reason into a fresh held draft of the SAME source (release/reply/spotlight), mirroring VideoEngine.reauthor_from_rejection — reads the rejected draft's source-specific reference marker, asks the local model to revise the rejected body with the reason folded in as guidance via _revision_prompt; a local-model failure or empty output originates NOTHING (a degraded copy is worse than none). Deduped per underlying item (_redraft_already_open, excludes CANCELLED so a further redraft is allowed once THAT one is also rejected) under a per-identity Redis lock (_acquire_redraft_lock, mirroring XPostService's lock style) so two racing rejects can't both originate.
XEngine.draft_release_post method x_engine.py:279 Event-driven hook (called from ReleaseProposalService.approve's publish-success branch); local-model-drafted, deduped per version, capped by x_max_open_posts; resolves and threads product_name from the release's own project
XEngine.run_cycle method x_engine.py:353 Periodic mentions poll; no-op unless x_engine_enabled AND x_replies_enabled; filters bot-like/low-engagement mentions, dedupes by mention id (XSeenMentionTable); each mention's text is run through screen_external_text before the local-model reply prompt sees it; resolves product_name once per cycle and threads it through _originate_reply/_draft_reply_body/_reply_prompt
screen_external_text function foundation/policy/injection_guard.py:95 Shared screen-and-neutralize guard for unattended attacker-writable text feeds (X mentions, vault inbox notes): wraps the text in an untrusted-content envelope and flags any matched injection-pattern LINE inline — nothing is removed, so the CEO-facing draft still shows what the source really said
XEngine.open_feature_spotlight_exploration method x_engine.py:487 No-ops unless x_engine_enabled AND x_feature_spotlight_enabled, no creds, a cycle already open, the open-post cap reached, or project unresolvable; else opens ONE held PENDING exploration task for the Head of Marketing (source=x_feature_exploration) carrying a x_seen_features marker snapshot; the description is built by _feature_exploration_description(product_name), no longer the fixed _FEATURE_EXPLORATION_DESCRIPTION string
XEngine.materialize_feature_spotlight method x_engine.py:841 Called from the propose_feature_spotlight do-tool: marks the feature slug seen (XSeenFeatureTable), creates the held draft (source=x_feature, identical shape to a release/reply draft), completes the exploration task
XPostService.approve method x_post_service.py:92 The ONLY caller of x_client.post_tweet; Redis single-flight lock, re-reads task under lock, idempotent on an already-posted draft (already_posted); a CANCELLED draft is refused both pre-lock and re-checked under lock (already_rejected) — a stale approve (e.g. a queued Telegram button) can't resurrect a draft the CEO already rejected
XPostService.reject method x_post_service.py:251 Records the CEO's reason; cancels the held draft; a non-blank reason schedules XEngine.redraft_from_rejection via defer_after_commit (a fresh session, never blocks or fails the HTTP response) so the feedback re-enters the draft flow instead of dying with the cancel
XClient / NullXClient / LiveXClient ABC/class x_client.py:150 / 166 / 186 NullXClient.configured is False (no creds) — drafting still runs (content nobody can post is a no-op upstream), just never originates; LiveXClient signs OAuth 1.0a HMAC-SHA1
build_x_client factory x_client.py:306 Returns LiveXClient when credentials decrypt, else NullXClient
XCredentialsService.set_credentials / .get_decrypted method x_credentials.py:61 / 116 All-or-nothing Fernet-encrypted singleton credential set/clear; decrypts server-side only, never exposed to agents
get_x_engine factory x_engine.py:869 Session-bound constructor (optional injected XClient for tests)
CompanyGoalsService.resolve_product_name method company_goals.py:79 The shared product-name fallback chain: project.name if set, else the charter's company_name, else the "RoboCo" literal — single source so XEngine/VideoEngine can't drift apart on branding
task_project_fields func api/schemas/project_fields.py:19 (project_slug, project_name) or (None, None) for a task response — sa_inspect(task).unloaded guard before touching task.project (a freshly-created task can have an unloaded relationship); shared by the X and video queue response builders so a multi-project CEO can tell drafts apart via the panel's ProjectBadge
BoardProgram dataclass foundation/policy/board_programs.py:31 Frozen registry entry: key/role/trigger/source/default_interval_seconds/max_items_per_cycle/scope
PROGRAMS dict foundation/policy/board_programs.py:45 All 14 registered programs, keyed by key
program_due func foundation/policy/board_programs.py:225 Pure cron-due check; METRIC/EVENT programs always return False (opened by their own hooks, never the loop)
project_participates func foundation/policy/board_programs.py:241 Dual-polarity scope predicate — affirmative opt-in for scope="project", opt-out ("!key") for scope="org"
validate_board_programs_field func foundation/policy/board_programs.py:258 Rejects an unknown key or a polarity mismatched to the program's own scope
BoardProgramEngine class services/board_programs.py:293 Trigger/dedup/originate/LEARN over every registered program
BoardProgramEngine.run_due_programs method services/board_programs.py:302 Originates a cycle for every enabled+due CRON program, then every metric predicate that fires off-schedule; one program's failure never blocks the rest
BoardProgramEngine.open_program_cycle method services/board_programs.py:373 Enabled+scope+dedup only, no cron-due check — the CEO "run now" / strategy-engine idle-trigger seam
BoardProgramEngine.record_decision / prior_cycle_context method services/board_programs.py:422 / 466 LEARN: accrue a CEO approve/reject onto the cycle row; render the last N closed cycles for the next exploration prompt
program_armed func services/board_programs.py:274 THE arming chokepoint — settings-store board_program.{key}.enabled, falling back to a legacy flag only for roadmap/x_feature
pick_rotation_target func services/board_programs.py:191 Shared round-robin for project-scoped programs (Pest Control/Spackle/Mirror/Dogfood): never-explored first, else oldest last_opened_at
PestControlEngine.run_cycle / evidence_context method pest_control_engine.py:79 / 152 Weekly+metric cron; server-assembles rework-hotspot/recurring/waived-finding evidence for the PO's prompt
SpackleEngine.run_cycle method spackle_engine.py:71 Biweekly cron, project-scoped gap-fill audit
ScalesEngine.run_cycle / _stale_backlog_snapshot method scales_engine.py:70 / 140 Monthly cron; snapshots BACKLOG/PENDING tasks older than 30 days for the rebalance prompt
DogfoodEngine.run_cycle method dogfood_engine.py:78 Event-only; real originator (unlike Coroner's stub) — needs no external incident id, picks the next opted-in project via pick_rotation_target
PeriscopeEngine.run_cycle / latest_brief_context method periscope_engine.py:71 / 126 Weekly cron; the latest closed brief is injected into Printer's own exploration prompt
MegaphoneEngine.run_cycle / digest_context method megaphone_engine.py:74 / 133 3-day cron; server-assembles the shipped-this-week digest + Unreleased CHANGELOG section
MirrorEngine.run_cycle method mirror_engine.py:71 Quarterly cron, project-scoped messaging-drift audit
BarflyEngine.run_cycle / _screen_and_mark method barfly_engine.py:88 / 145 2-day cron; screens each X search candidate through injection_guard.screen_external_text before it reaches the HoM's prompt
WarRoomEngine.run_cycle / open_for_release method war_room_engine.py:111 / 120 run_cycle is the CEO on-demand blank-brief path (reachable via open_program_cycle); open_for_release bypasses _ORIGINATORS entirely, called from the release-publish hook with pre-curated highlights
CoronerEngine.open_for_incident / incident_context method coroner_engine.py:75 / 195 The ONLY way a Coroner cycle opens — called directly from three chokepoints (bounce>=3, cancel-after-work, budget-block), never the cron loop; _ORIGINATORS["coroner"] is an always-None stub that only exists so the dict covers the registry 1:1
SentinelEngine.run_cycle / evidence_context method sentinel_engine.py:79 / 131 Weekly cron; server-assembles waiver-trend/open-findings/conventions-hotspot/spend evidence
LibrarianEngine.run_cycle / mining_context method librarian_engine.py:89 / 140 Biweekly cron; server-assembles recurring learning-journal topics + existing playbook titles to mine against
get_pest_control_engine / get_spackle_engine / get_scales_engine / get_dogfood_engine / get_periscope_engine / get_megaphone_engine / get_mirror_engine / get_barfly_engine / get_war_room_engine / get_coroner_engine / get_sentinel_engine / get_librarian_engine / get_board_program_engine factory each engine's own file Session-bound constructors, one per engine

Data Flow

Two distinct flows originate work into the delivery lifecycle:

Pitch flow (CEO-driven origination). A Board member creates a pitch (PitchService.createPitchTable status proposed). The CEO approves via POST /api/pitch/{id}/approvePitchService.approve. Approval calls GitHubProvisioningService.create_repo once per target cell (repo name {slug}-{cell} when multi-cell, else {slug}), then ProjectService.create to register each repo as a Project (git token stored from settings.provisioning_token). For multi-cell pitches, ProductService.create registers a Product with the cell→project map; for single-cell, the lone project is the seed. _seed_main_pm_task then creates a PENDING Main-PM CODE task (source="pitch", confirmed_by_human=True) assigned to main-pm, which the normal dispatcher picks up. The pitch row moves to provisioned with provisioned_product_id / provisioned_project_ids / seed_task_id recorded.

Strategy flow (dormant watcher). Orchestrator._strategy_engine_loop (created at startup) returns immediately unless strategy_engine_enabled; otherwise each strategy_engine_interval_seconds it opens a DB context and calls StrategyEngine.run_cycleassess. assess reads TaskService.list_in_progress_or_claimed and list_long_running_blocked against CompanyGoalsService.get(); if idle-with-goals or stranded-blocked, it sends the CEO an ack-notification via NotificationService.send_ack_notification. Notify-only — never originates work.

Research flow (on-demand agent capability). A Board/PM agent calls the roboco-search MCP tool (mounted only when research_enabled and role is research-eligible, orchestrator line 2914) → /api/research/{search,fetch} route. The route enforces the per-agent daily quota via the module-level ResearchQuotaTracker singleton (Redis INCR, fail-open), then calls get_research_service()ResearchService.search/fetch → selected provider adapter. Result count and char size are clamped to research_max_results / research_fetch_max_chars. The provider key lives only server-side; the agent never egresses.

Routing flow (runtime keystone). ProductService.project_for(product_id, team) is called from the gateway delegate path to resolve which Project a cell works on within a product; None falls back to the parent task's project.

Roadmap flow (dormant weekly originator, default off). Orchestrator._roadmap_engine_loop returns immediately unless roadmap_engine_enabled; otherwise each roadmap_interval_seconds (default weekly) it opens a DB context and calls RoadmapEngine.run_cycle, which no-ops if a roadmap-source task is already open or the RoboCo project isn't resolvable, else opens ONE held PENDING exploration task (source=board_roadmap, confirmed_by_human=False) assigned to the Product Owner. The normal board one-shot dispatch (_dispatch_roadmap_exploration) spawns the PO, who explores the charter/releases/metrics/projects and calls the propose_roadmap do-tool exactly once with a themed goal + 3-7 item drafts (persisted as an orchestration_markers payload). The CEO reviews the cycle in the panel's Roadmap Review Queue and approves/rejects each item individually via /api/roadmap/cycles/{id}/items/{id}/{approve,reject}RoadmapService; an approved item materializes as a BACKLOG task (source=roadmap) through PrompterService.create_task_from_draft — nothing auto-starts, normal PM activation takes it from BACKLOG. Once every item is terminal, the exploration task itself completes.

X (Twitter) flow (three originators, one held queue, default off). Unlike every other engine on this page, XEngine never spawns an agent for release posts or mention replies — draft_release_post (event hook off ReleaseProposalService.approve's publish-success branch) and run_cycle (periodic mentions poll, Orchestrator._x_mentions_poll_loop) both draft via a raw local-model chat completion, never a cloud LLM. The feature-spotlight half is the exception: Orchestrator._x_feature_spotlight_loop (dormant unless BOTH x_engine_enabled AND x_feature_spotlight_enabled) opens a DB context each x_feature_spotlight_interval_seconds and calls XEngine.open_feature_spotlight_exploration, which no-ops on the usual guards (creds, one-open-cycle dedup, the shared x_max_open_posts cap, project resolvability) or else opens ONE held PENDING exploration task (source=x_feature_exploration) assigned to the Head of Marketing, carrying a snapshot of already-covered feature slugs (x_seen_features marker). The board dispatcher's _dispatch_pm_work special-cases this source (mirroring ROADMAP_SOURCE) to call _dispatch_feature_spotlight_exploration, a one-shot spawn of the real Head-of-Marketing agent (full read tools) who investigates CHANGELOG.md/feature-flags/docs/map/charter/KB and calls the propose_feature_spotlight do-tool exactly once; that verb materializes a brand-new held draft task (source=x_feature) and completes the exploration task as a side effect — a deliberate asymmetry from propose_roadmap, which instead writes a marker onto the SAME task and leaves it open. Every draft from all three paths — release, reply, spotlight — lands in the identical held-task shape (TaskTable, confirmed_by_human=False, assigned_to=secretary-1, body in orchestration_markers.x_draft_body) rendered by the panel's X Post Queue and acted on only by XPostService.approve/.reject; nothing here ever calls x_client.post_tweet itself. XEngine._voice_guide (a live CompanyGoalsService.get() read, never hardcoded) feeds a baseline house-voice constant plus the CEO's optional brand_voice charter sample into every one of the two local-model prompts, and the Head of Marketing's own identity prompt points it at the same charter field for its cloud-LLM-authored spotlight body.

Board Program flow (registry, no master flag, default off per program). The orchestrator's _board_program_loop ticks BoardProgramEngine.run_due_programs on a floor interval (shortest registered cadence, clamped 300s-3600s). Per CRON program: program_armed (settings-store board_program.{key}.enabled, falling back to a legacy flag only for roadmap/x_feature) → _scope_gate (a scope="project" program needs at least one project with the key in projects.board_programs) → dedup against board_program_cycles (migration 087, one open row per program, auto-closed once its exploration task goes terminal) → program_due_ORIGINATORS[key] calls that program's own run_cycle, which opens ONE held PENDING exploration task assigned to the program's role (Product Owner: Pest Control/Spackle/Scales/Dogfood; Head of Marketing: Periscope/Megaphone/Mirror/Barfly/War Room; Auditor: Sentinel/Librarian) and records a fresh board_program_cycles row. run_due_programs separately evaluates every registered metric predicate (_METRIC_PREDICATES, today only Pest Control's 7-day rework-rate check against ROBOCO_PEST_REWORK_THRESHOLD) after the same scope/dedup gates, so an off-schedule accelerator never re-pays a multi-query metric check on a tick that was always going to be rejected. open_program_cycle(key) is the same path minus cron-due — used by the CEO panel's "run now" (POST /api/board-programs/{key}/run-now), the Strategy Engine's idle observation (Printer only — the design's stranded_blocked → Coroner fold was never wired), and Dogfood's release-publish hook. Coroner is the exception to the whole loop: its trigger=event means program_due always refuses it, and its ONLY real entry point is CoronerEngine.open_for_incident, called directly from three chokepoints — TaskService's bounce-past-revision_count>=3 transition, TaskService's cancel-after-work path, and the orchestrator's budget-block path — never the cron loop. War Room's release cycle similarly bypasses _ORIGINATORS via open_for_release, called from the same release-publish hook as draft_release_post/Dogfood, carrying pre-curated highlights so campaign posts never invent a feature.

Every exploration task dispatches through _dispatch_board_program_exploration — a dict-dispatch table (not an if/elif chain, xenon budget) keyed by task['source'], routing to a dedicated one-shot spawner (_dispatch_pest_control_exploration, etc.) that bypasses _handle_board_assigned_task's two-reviewer board-review gate entirely; every dispatcher shares the _board_dispatched one-shot tracker + respawn breaker. The agent calls its program's ONE proposal verb (propose_bug_hunt/propose_gap_fill/propose_rebalance/propose_friction_fixes for the PO; propose_market_brief/propose_editorial_post/propose_messaging_fixes/propose_campaign/propose_conversation_replies for the HoM; propose_postmortem/propose_playbook_drafts/propose_quality_report for the Auditor — all in roboco/services/gateway/content_actions.py) exactly once. Materialization varies by program: most (Pest Control/Spackle/Mirror/roadmap) create BACKLOG tasks with a per-item CEO decision identical to the roadmap flow; Scales instead MUTATES a live task in place on approval (reprioritize or cancel — never creates one); Periscope/Sentinel complete their exploration task in the same call as a held report with no per-item queue; Megaphone/Barfly/War Room/spotlight land in the existing X held-draft queue; Coroner materializes a held process-change item or drafts straight into the pending-playbook queue (kind='playbook'); Librarian drafts 1-3 real DRAFT playbooks directly via PlaybookService, bypassing draft_playbook entirely (an explicit invariant: the Auditor curates but does not draft, except here). LEARN closes the loop: BoardProgramEngine.record_decision accrues each CEO verdict onto the cycle row's decisions jsonb, and prior_cycle_context renders the last two closed cycles back into the NEXT cycle's exploration prompt.

Project-scoped programs (Pest Control/Spackle/Mirror/Dogfood) additionally use pick_rotation_target to round-robin across their opted-in projects — never-explored beats explored, else oldest last_opened_at wins, read from the programs' own exploration tasks (not the LEARN ledger, since a project-scoped engine's run_cycle can be called directly, outside the loop). projects.board_programs (migration 088) governs opt-in/opt-out with dual polarity per project_participates — a plain key for a scope="project" program, "!key" to exclude a project from a scope="org" program's default-eligible output.

Read-only views. KanbanService builds role-specific boards from TaskTable queries on demand for the kanban API; CompanyGoalsService.get is read by the briefing injector into every agent's context_briefing.

Mermaid

flowchart TD
    Board[Board member] -->|create| Pitch[PitchService.create]
    CEO -->|approve /api/pitch/:id/approve| Approve[PitchService.approve]
    Approve --> Prov[GitHubProvisioningService.create_repo]
    Prov --> GH[(GitHub org repos)]
    Approve --> ProjReg[ProjectService.create per cell]
    Approve --> Topo{_register_topology}
    Topo -->|multi-cell| ProdCreate[ProductService.create]
    Topo -->|single-cell| SeedProj[seed_project_id]
    Approve --> Seed[_seed_main_pm_task]
    Seed --> MainPmTask[PENDING Main-PM task source=pitch]
    MainPmTask --> Dispatcher[normal delivery lifecycle]

    subgraph StrategyLoop[dormant — strategy_engine_enabled]
        Loop[Orchestrator._strategy_engine_loop] -->|interval| Cycle[StrategyEngine.run_cycle]
        Cycle --> Assess[assess]
        Assess --> Goals[(CompanyGoalsService.get)]
        Assess --> Tasks[(TaskService list_in_progress / long_running_blocked)]
        Assess -->|notify-only| NotifyCEO[CEO notification]
    end

    subgraph ResearchFlow[on-demand — research_enabled]
        Agent[Board/PM agent] -->|roboco-search MCP| Route[/api/research/]
        Route --> Quota[ResearchQuotaTracker.check_and_consume]
        Quota --> Redis[(Redis INCR/day)]
        Route --> RS[ResearchService]
        RS --> Prov2{"Tavily|Brave|Exa|Null"}
        Prov2 -->|provider API| Web[(Web)]
    end

    subgraph RoadmapLoop[dormant — roadmap_engine_enabled]
        RLoop[Orchestrator._roadmap_engine_loop] -->|interval, default weekly| RCycle[RoadmapEngine.run_cycle]
        RCycle -->|held PENDING task| PO[Product Owner spawn]
        PO -->|propose_roadmap do-tool| Payload[(orchestration_markers cycle payload)]
        CEO -->|approve/reject per item /api/roadmap| RSvc[RoadmapService]
        RSvc -->|approve| Backlog[BACKLOG task via PrompterService]
        RSvc -->|all items terminal| Complete[exploration task completes]
    end

    subgraph XEngineFlow[dormant — x_engine_enabled]
        RelHook[ReleaseProposalService.approve publish] --> XDraftRelease[XEngine.draft_release_post]
        XMentLoop[Orchestrator._x_mentions_poll_loop] -->|interval| XRunCycle[XEngine.run_cycle]
        XDraftRelease --> XChat[local-model chat, _voice_guide]
        XRunCycle --> XChat
        XSpotLoop["Orchestrator._x_feature_spotlight_loop (x_feature_spotlight_enabled)"] -->|interval, default 3d| XOpen[XEngine.open_feature_spotlight_exploration]
        XOpen -->|held PENDING task| HoM[Head of Marketing spawn]
        HoM -->|propose_feature_spotlight do-tool| XMat[XEngine.materialize_feature_spotlight]
        XMat --> XQueue[(held X post/reply/feature draft)]
        XDraftRelease --> XQueue
        XRunCycle --> XQueue
        CEO -->|approve/reject /api/x/posts| XSvc[XPostService]
        XSvc -->|approve, single-flight lock| Tweet[(x_client.post_tweet)]
    end

    subgraph BoardProgramLoop["Board Program registry — 14 entries, per-program settings-store arming"]
        BPLoop[Orchestrator._board_program_loop] -->|floor interval| RunDue[BoardProgramEngine.run_due_programs]
        RunDue --> Armed{program_armed settings-store}
        Armed -->|CRON, scope+dedup+due| Origin[program._ORIGINATORS run_cycle]
        RunDue --> MetricCheck[_run_due_metric_predicates: pest_control rework-spike]
        MetricCheck --> Origin
        CoronerHook["TaskService bounce/cancel + budget-block hook"] --> CoronerOpen[CoronerEngine.open_for_incident]
        ReleaseHook[ReleaseProposalService.approve publish] --> WarRoomOpen[WarRoomEngine.open_for_release]
        ReleaseHook --> DogfoodRun[DogfoodEngine.run_cycle]
        Origin -->|held PENDING task| Explorer["PO / HoM / Auditor spawn (solo, board-review gate bypassed)"]
        CoronerOpen -->|held PENDING task| Explorer
        WarRoomOpen -->|held PENDING task| Explorer
        Explorer -->|ONE propose_* verb| ProposeVerb[ContentActions.propose_*]
        ProposeVerb --> Materialize{materializer}
        Materialize -->|backlog tasks, per-item decision| Backlog[(BACKLOG task)]
        Materialize -->|held report, no queue| Report[(CEO report)]
        Materialize -->|held X draft| XQueue2[(X post queue)]
        Materialize -->|mutate live task| LiveTask[(reprioritize / cancel)]
        Materialize -->|playbook draft| PlaybookQ[(pending-playbook queue)]
        CEO -->|approve/reject per item| Learn[BoardProgramEngine.record_decision]
        Learn -->|LEARN| Ledger[(board_program_cycles.decisions)]
        Ledger -->|prior_cycle_context| Origin
    end

Logical Tree

product-strategy-research-pitch
├── project.py — ProjectService
│   ├── CRUD (create/get/get_by_slug/get_or_raise/update/delete)
│   ├── _assert_git_url_allowed (protected denylist)
│   ├── _maybe_scaffold_conventions (flag-gated, non-fatal)
│   ├── queries (list_all / list_by_cell)
│   ├── workspace (set_workspace_path / update_sync_state)
│   ├── git token (get_decrypted_token / _by_slug) — Fernet, on-demand
│   └── access control (add/remove_allowed_agent / check_agent_access)
├── product.py — ProductService
│   ├── CRUD
│   ├── project_for (per-cell routing keystone)
│   ├── distinct_project_ids (Main-PM integration-branch set)
│   └── _replace_cells (idempotent map replace, DELETE-before-INSERT flush)
├── kanban.py — KanbanService
│   ├── _load_subtask_counts (batch child-count query, fixes always-0 stub)
│   ├── _task_to_card (accepts subtask_counts map)
│   ├── dev board (flat / swimlane by priority|assignee)
│   ├── qa / documenter / pm boards (flat, status-filtered; qa excludes VERIFYING; documenter scoped to task_type=documentation)
│   ├── main-pm board (cross-cell swimlane / flat team columns + Coordination column)
│   ├── board roadmap (P0/P1)
│   └── board stats
├── company_goals.py — CompanyGoalsService (singleton SINGLETON_ID)
│   ├── get (empty defaults if unset)
│   └── upsert (partial-key)
├── strategy_engine.py — StrategyEngine (dormant)
│   ├── assess (idle + stranded_blocked observations)
│   └── run_cycle (flag-gated, notify-only)
├── research.py
│   ├── SearchProvider ABC + Tavily/Brave/Exa/Null adapters
│   ├── ResearchService (clamping entry point)
│   ├── build_provider / get_research_service
│   └── helpers (_as_float / _truncated_fetch)
├── research_quota.py — ResearchQuotaTracker (Redis, fail-open)
│   └── check_and_consume (INCR-then-compare)
├── pitch.py — PitchService
│   ├── CRUD + list_pitches
│   ├── reject / approve
│   ├── _provision_repos (idempotent on re-approval)
│   ├── _register_topology (Product vs seed-project)
│   └── _seed_main_pm_task (PENDING Main-PM task)
├── github_provisioning.py — GitHubProvisioningService
│   ├── enabled (master+token+org)
│   └── create_repo (POST /orgs/{org}/repos, auto_init)
├── roadmap_engine.py — RoadmapEngine (dormant, roadmap_engine_enabled)
│   └── run_cycle (one held exploration task for the Product Owner; one-open-cycle dedup)
├── roadmap_service.py — RoadmapService
│   ├── list_open_cycles
│   ├── approve_item (materialize BACKLOG task, idempotent)
│   ├── reject_item (record reason, idempotent)
│   └── _maybe_complete_cycle (completes exploration task once all items terminal)
├── x_engine.py — XEngine (dormant, x_engine_enabled)
│   ├── _voice_guide (baseline + CEO brand_voice charter sample, live DB read)
│   ├── draft_release_post (event hook; local-model; dedup per version)
│   ├── run_cycle (mentions poll; x_replies_enabled sub-switch; bot/engagement filter)
│   ├── open_feature_spotlight_exploration (x_feature_spotlight_enabled sub-switch; one-open-cycle dedup; seen-features marker)
│   ├── materialize_feature_spotlight (called from propose_feature_spotlight; marks seen, holds draft, completes exploration)
│   └── _originate_post (shared held-task origination, all three sources)
├── x_post_service.py — XPostService
│   ├── approve (single-flight lock, idempotent, only caller of x_client.post_tweet)
│   └── reject (record reason, cancel draft)
├── x_client.py — XClient ABC / NullXClient / LiveXClient
│   └── build_x_client (creds present → LiveXClient, else NullXClient)
├── x_credentials.py — XCredentialsService (singleton, Fernet-encrypted)
│   ├── set_credentials (all-or-nothing)
│   └── get_decrypted (server-side only)
├── foundation/policy/board_programs.py — pure registry (no IO)
│   ├── BoardProgram (frozen dataclass) + PROGRAMS (14 entries)
│   ├── program_due (cron-due check)
│   ├── project_participates (dual-polarity scope predicate)
│   └── validate_board_programs_field
├── services/board_programs.py — BoardProgramEngine
│   ├── run_due_programs / _run_due_metric_predicates (cron + metric pass)
│   ├── open_program_cycle (enabled+scope+dedup, no cron-due — "run now"/idle-trigger seam)
│   ├── _scope_gate / opted_in_projects
│   ├── _dedup_state / _maybe_close / _latest_cycle (board_program_cycles ledger)
│   ├── record_decision / prior_cycle_context (LEARN)
│   ├── program_armed (settings-store arming chokepoint)
│   └── pick_rotation_target (shared project-scoped round-robin)
├── pest_control_engine.py / spackle_engine.py / scales_engine.py / dogfood_engine.py — Product Owner programs
│   └── each: run_cycle (CRON) + a program-specific evidence/context builder; dogfood_engine also binds a real _ORIGINATORS entry despite being event-only
├── periscope_engine.py / megaphone_engine.py / mirror_engine.py / barfly_engine.py / war_room_engine.py — Head of Marketing programs
│   └── each: run_cycle + context builder; war_room_engine also exposes open_for_release (release-hook bypass of _ORIGINATORS); barfly_engine screens candidates through injection_guard
├── coroner_engine.py / sentinel_engine.py / librarian_engine.py — Auditor programs
│   └── coroner_engine: open_for_incident is the ONLY real entry point (event-only, no run_cycle path through the loop); sentinel_engine/librarian_engine: run_cycle (CRON) + context builder
└── api/routes/board_programs.py — CEO-only status + run-now routes

Dependencies

Internal (roboco):

  • roboco.config.settings — all flags/caps (every file).
  • roboco.services.base.BaseServiceProject/Product/Kanban/CompanyGoals/Strategy/Pitch (log + session).
  • roboco.db.tablesProjectTable, ProductTable, ProductProjectTable, TaskTable, AgentTable, CompanyGoalsTable, PitchTable, WorkSessionTable.
  • roboco.models.*ProjectCreate/Update, ProductCreate/Update/ProductCellMapping, TaskCreateRequest, PitchCreate/PitchStatus, base (Team/TaskStatus/TaskType/Complexity/TaskNature), kanban.
  • roboco.foundation.identity.Team (product), roboco.foundation.policy… indirectly via task.
  • roboco.utils.cryptoencrypt_token/decrypt_token/EncryptionError (project).
  • roboco.utils.convertersrequire_uuid/to_python_uuid (kanban, pitch).
  • roboco.services.conventions — lazy-imported in ProjectService._maybe_scaffold_conventions.
  • roboco.services.work_session — lazy in ProjectService.delete.
  • roboco.services.workspace — lazy in ProjectService.delete (delete_workspaces).
  • roboco.services.taskStrategyEngine (list_in_progress_or_claimed, list_long_running_blocked), PitchService._seed_main_pm_task; RoadmapEngine/RoadmapService (ROADMAP_SOURCE/ROADMAP_ITEM_SOURCE, list_open_roadmap_cycles, TaskCreateRequest).
  • roboco.services.prompterRoadmapService._materialize lazy-imports get_prompter_service (create_task_from_draft, the same confirmed-by-CEO-approval path pitch items use).
  • roboco.foundation.policy.content.markersRoadmapService/api/routes/roadmap.py (get_roadmap_cycle/set_roadmap_cycle, the cycle payload persisted on orchestration_markers).
  • roboco.foundation.identityRoadmapEngine._originate (AGENTS["product-owner"]/AGENTS["system"]).
  • roboco.services.agentPitchService (get_by_slug("main-pm")).
  • roboco.services.notificationStrategyEngine.run_cycle.
  • roboco.services.github_provisioningPitchService.approve.
  • roboco.services.project / productPitchService.
  • roboco.runtime.orchestrator — runs _strategy_engine_loop + _roadmap_engine_loop/_dispatch_roadmap_exploration + _board_program_loop/_dispatch_board_program_exploration; mounts roboco-search MCP when research_enabled, playwright MCP task-scoped for Dogfood.
  • roboco.services.gateway.content_actions.ContentActions — the fourteen propose_* do-verbs (one per program) that author each program's proposal; roboco.api.schemas.v1.do — the matching *Input/Propose*Request pydantic schemas.
  • roboco.services.metrics.MetricsService.get_rework_metrics — Pest Control's off-schedule metric predicate.
  • roboco.foundation.policy.injection_guard.screen_external_text — Barfly screens every candidate conversation through it before the HoM's prompt sees it.
  • roboco.services.playbook.PlaybookService — Coroner (kind='playbook') and Librarian both draft directly into it, never through the draft_playbook do-tool.

External:

  • sqlalchemy (async ext) — all DB-backed services.
  • httpx — research providers + GitHub provisioning (async client, owned-or-injected).
  • redis.asyncioResearchQuotaTracker.
  • pydantic (via models) — ProjectCreate/Update etc.
  • Provider HTTP APIs: Tavily, Brave Search, Exa, GitHub REST.

Entry Points

  • Routes (roboco/api/routes/):
    • project.py — Project CRUD endpoints → get_project_service.
    • product.py — Product CRUD endpoints → get_product_service.
    • kanban.py — 7 board endpoints (dev/qa/documenter/pm/main-pm/main-pm-flat/board + stats) → get_kanban_service.
    • company_goals.pyGET /api/company-goals, PUT /api/company-goals (CEO) → get_company_goals_service.
    • pitch.py — pitch CRUD + POST /api/pitch/{id}/approve (CEO) + /rejectget_pitch_service.
    • research.pyPOST /api/research/search, /fetchget_research_service + module-level ResearchQuotaTracker.
    • prompter_live.pyget_project_service for project lookup during intake.
    • dashboard.pyget_product_service / get_project_service for dashboard views.
    • roadmap.pyGET /api/roadmap/cycles, POST /cycles/{id}/items/{id}/{approve,reject} (CEO-only) → get_roadmap_service.
    • x.pyGET /api/x/posts, POST /posts/{id}/{approve,reject} (CEO-only) → get_x_post_service.
    • board_programs.pyGET /api/board-programs (list all 14 with live status), POST /api/board-programs/{key}/run-now (CEO-only) → get_board_program_engine.
  • Orchestrator loop tick: _strategy_engine_loop (orchestrator.py:6360) — created at start() (line 1010), cancelled in shutdown (line 1075); ticks every strategy_engine_interval_seconds, calls StrategyEngine.run_cycle. _roadmap_engine_loop (orchestrator.py:7462) — same lifecycle shape, ticks every roadmap_interval_seconds (default weekly), calls RoadmapEngine.run_cycle; _dispatch_roadmap_exploration (orchestrator.py:10284) spawns the Product Owner once per open exploration task. _x_mentions_poll_loop (orchestrator.py:7509) ticks every x_mentions_interval_seconds, calls XEngine.run_cycle. _x_feature_spotlight_loop (orchestrator.py:7571) — same lifecycle shape, dormant unless BOTH x_engine_enabled AND x_feature_spotlight_enabled, ticks every x_feature_spotlight_interval_seconds (default 3 days), calls XEngine.open_feature_spotlight_exploration; _dispatch_feature_spotlight_exploration (orchestrator.py:10424) spawns the Head of Marketing once per open exploration task — _dispatch_pm_work routes source=x_feature_exploration to it BEFORE the generic _BOARD_AGENTS check (mirroring the roadmap source's own early branch), so it never falls into the two-reviewer board-review gate. _board_program_loop (orchestrator.py:9224) — same lifecycle shape, ticks on a floor interval (_board_program_interval_seconds: shortest registered program cadence, clamped 300s-3600s), calls BoardProgramEngine.run_due_programs; _dispatch_board_program_exploration (a module-level dict-dispatch function, not a method — orchestrator.py:948) routes each program's held exploration task to its own one-shot dispatcher (_dispatch_pest_control_exploration, _dispatch_periscope_exploration, etc.), each spawning its program's role solo, bypassing the two-reviewer board-review gate exactly like the roadmap/spotlight dispatchers already did.
  • MCP mount (orchestrator spawn): roboco-search MCP mounted into Board/PM agent containers only when research_enabled (orchestrator.py:2914); the MCP server calls the /api/research/* routes. playwright MCP mounted task-scoped (not role-blanket) for a board_dogfood spawn only, via _is_dogfood_spawn (orchestrator.py:3834).
  • Event hooks (bypass the loop entirely): TaskService's bounce-into-needs_revision transition and cancel-after-work path both call CoronerEngine.open_for_incident directly (services/task.py:812 / :1392); the orchestrator's budget-block path calls it too (orchestrator.py:8311); ReleaseProposalService.approve's publish-success branch calls WarRoomEngine.open_for_release (services/release_proposal.py:330) alongside the pre-existing XEngine.draft_release_post hook.
  • Service-to-service: ProjectService called by WorkspaceService, GitService, PitchService, task, docs, cockpit, secretary, gateway choreographer; ProductService.project_for called from gateway delegate path; CompanyGoalsService.get called by briefing injector; BoardProgramEngine called by every program's own engine (dedup/record) and by StrategyEngine.run_cycle (Printer's idle trigger).
  • No CLI / lifespan entry points for this slice.

Config Flags

Flag Default File:Line Effect
ROBOCO_CONVENTIONS_ENABLED False config.py:227 ProjectService.create best-effort scaffolds .roboco/conventions.yml PR; inert when off
ROBOCO_RESEARCH_ENABLED True config.py:245 Mounts roboco-search MCP into Board/PM containers (orchestrator:2914)
ROBOCO_RESEARCH_PROVIDER tavily config.py:252 tavily/brave/exa/null adapter selection
ROBOCO_RESEARCH_API_KEY None config.py:262 Server-side only; unset → NullProvider
ROBOCO_RESEARCH_MAX_RESULTS 5 config.py:269 Hard cap on web_search results (120)
ROBOCO_RESEARCH_FETCH_MAX_CHARS 20000 config.py:275 Hard cap on web_fetch extracted chars
ROBOCO_RESEARCH_TIMEOUT_SECONDS 15.0 config.py:280 Outbound provider HTTP timeout
ROBOCO_RESEARCH_DAILY_QUOTA_PER_AGENT 50 config.py:286 Per-agent/day call ceiling (Redis, fail-open)
ROBOCO_PROVISIONING_ENABLED True config.py:309 Pitch auto-provisioning master switch (still inert without token+org)
ROBOCO_PROVISIONING_TOKEN "" config.py:316 GitHub PAT for repo creation (server-side only)
ROBOCO_PROVISIONING_ORG "" config.py:323 GitHub org where repos are provisioned
ROBOCO_GITHUB_API_BASE_URL https://api.github.com config.py:327 Override for GitHub Enterprise
ROBOCO_PROVISIONING_TIMEOUT_SECONDS 30.0 config.py:331 Outbound GitHub provisioning timeout
ROBOCO_PROVISIONING_REPO_PRIVATE True config.py:336 Whether provisioned repos are private
ROBOCO_PROVISIONING_PROVIDER github config.py:570 Phase 4 forge parity: github/gitlab/gitea selects the concrete provisioning target via _build_provider
ROBOCO_PROVISIONING_HOST "" config.py:579 Self-hosted forge host (e.g. gitlab.example.com); .enabled additionally requires this when provisioning_provider is gitlab/gitea (ignored for github/gitlab.com)
ROBOCO_STRATEGY_ENGINE_ENABLED False config.py:348 Master switch — loop never starts when off
ROBOCO_STRATEGY_ENGINE_INTERVAL_SECONDS 1800 config.py:354 Seconds between strategy assessment passes
ROBOCO_STRATEGY_STRANDED_BLOCKED_MINUTES 120 config.py:360 Blocked-task threshold for "stranded" observation
ROBOCO_PROTECTED_GIT_URLS [] config.py:770 Denylist — ProjectService rejects git_url containing any entry
ROBOCO_ENCRYPTION_KEY "" config.py:295 Fernet key for git-token encrypt/decrypt
ROBOCO_ROADMAP_ENGINE_ENABLED False config.py:865 Master switch — _roadmap_engine_loop never opens an exploration cycle when off
ROBOCO_ROADMAP_INTERVAL_SECONDS 604800 config.py:875 Seconds between roadmap-exploration cycles (default weekly)
ROBOCO_ROADMAP_MIN_ITEMS_PER_CYCLE 3 config.py:880 Minimum item drafts propose_roadmap must submit for a themed cycle
ROBOCO_ROADMAP_MAX_ITEMS_PER_CYCLE 7 config.py:885 Maximum item drafts per cycle
ROBOCO_PEST_REWORK_THRESHOLD 0.3 config.py:1432 7-day rework rate above which Pest Control's metric predicate opens a cycle off-schedule, on top of its weekly cron. The ONLY env-settable knob among the twelve new Board Programs — every other one arms exclusively via its own settings-store row (board_program.{key}.enabled, no ROBOCO_*_ENABLED flag exists for them)

Gotchas

  • Pitch partial-failure orphans GitHub repos — RESOLVED (536bbb64). GitHubProvisioningService.create_repo now treats a GitHub 422 "name already exists" response as an idempotent signal: it calls _fetch_existing_repo and returns the existing repo's ProvisionedRepo instead of erroring. Combined with the Project-by-slug and Product-by-slug reuse already in place, re-approval is now idempotent end-to-end — no manual intervention needed. The initial partial failure still leaves an orphaned GitHub repo, but the re-approval path recovers it automatically.
  • ResearchQuotaTracker INCRs before the limit check (research_quota.py:65). An over-limit call still increments the counter (documented as fine for a ceiling). It also fails open on any Redis error (allowed=True) — research must not break because the cache is down. The route-level _quota_tracker is a module-level singleton sharing one Redis connection across requests.
  • ProductService._replace_cells flushes DELETEs before INSERTs (product.py:143). This is load-bearing: SQLAlchemy otherwise orders INSERTs before DELETEs for the same table, which would collide the new (product_id, team) rows with not-yet-deleted old ones on uq_product_projects_product_team and 409 on any re-mapping of a team. Refactoring away the intermediate flush reintroduces the 409.
  • KanbanService._task_to_card hardcodes subtask_count = 0 — FIXED (c71f9b3b / 536bbb64). The new _load_subtask_counts method (kanban.py:47) batch-counts direct children per parent in a single grouped SQL query and passes the result map into each _task_to_card call; has_subtasks and subtask_count now reflect real data.
  • KanbanService.get_main_pm_board_flat drops non-backend/frontend/ux_ui tasks silently — FIXED (536bbb64 / b3558d4e). A "Coordination" column (kanban.py:480) now catches non-cell-team tasks (Main PM, Board, fullstack, system, …). The column routing uses a dict-dispatch (status-key wins over team-key, fallback "coordination") so no card is built and discarded.
  • ProjectService.delete is gated by DB RESTRICT on tasks (project.py:282). Callers must cancel tasks first or the DB raises IntegrityError (route maps to 409). Active work sessions are abandoned first; delete_workspaces=True does shutil.rmtree on resolved paths — destructive, opt-in, best-effort.
  • ProjectService.update skips None-set fields — FIXED (536bbb64). git_token semantics unchanged (empty string clears, None leaves unchanged). All other fields now use model_dump(exclude_unset=True, exclude={"git_token"})exclude_none=True was removed (#197), so a field the caller explicitly sets to None now clears the stored value instead of being silently skipped.
  • Strategy loop sleeps a full interval before the first cycle (orchestrator.py:6375). await asyncio.sleep(interval) runs before the first run_cycle, so on startup there is a guaranteed strategy_engine_interval_seconds delay before the first assessment.
  • StrategyEngine.run_cycle catches nothing itself; the orchestrator wraps each cycle in except Exception (orchestrator.py:6380). A failing assess is logged and retried forever on the next tick — the CEO is never notified that the engine itself is broken.
  • build_provider returns NullProvider for an unknown provider name (research.py:326- 328). A typo in ROBOCO_RESEARCH_PROVIDER (validated by pydantic pattern, so unlikely) would silently degrade to empty results rather than erroring.
  • GitHubProvisioningService.enabled requires master + token + org (github_provisioning.py:64). provisioning_enabled defaults True, so the flag alone is not enough — an operator who toggles the flag without setting token/org still gets enabled=False and approve raises ProvisioningDisabledError.
  • PitchService._seed_main_pm_task requires a main-pm agent row (pitch.py:241-243). If the agent slug is missing it raises ValidationError after provisioning has already happened — another partial-failure window (repos + Product created, no seed task).
  • The Strategy Engine's stranded_blocked → Coroner fold was designed but never wired. The internal design spec (docs/internal/specs/2026-07-24-board-programs-design.md §3) proposed both StrategyEngine signals becoming Board Program triggers — idle → Printer (roadmap) and stranded_blocked → Coroner. Only the idle→roadmap half shipped (strategy_engine.py:95-101's own docstring: "stranded_blocked stays notify-only (Coroner is Phase 2 — its event hook lands then)"). Coroner is reachable only through its own three chokepoints (bounce/cancel/budget-block); a long-stranded blocked task never triggers an autopsy on its own. Not a bug — a deliberately scoped-down Phase 1, but a real gap between the design doc and the shipped code worth knowing before assuming the fold is complete.
  • XPostService.approve did NOT check for a CANCELLED (already-rejected) task before Wave 5 (11915f36, PR #551). Before the fix, approving a draft the CEO had already rejected would proceed straight to posting it — reachable via the Telegram inbound bridge's inline Approve button (targets a draft by id regardless of its current status) and equally via a replayed HTTP POST /api/x/posts/{id}/approve. The guard now returns already_rejected both before acquiring the lock and again after re-reading the task under lock.

Drift from CLAUDE.md

  • Research provider set vs CLAUDE.md. CLAUDE.md's "Technology Stack" / feature-flags section lists web research under ROBOCO_RESEARCH_ENABLED only; it does not enumerate the provider adapters (tavily/brave/exa/null) or the per-agent daily quota (ROBOCO_RESEARCH_DAILY_QUOTA_PER_AGENT, default 50). Code: config.py:252/286, research.py:310. Not a contradiction — an omission in the doc.
  • CLAUDE.md says the strategy engine "never spends, builds, or auto-approves" and is default-OFF. Code matches exactly (strategy_engine_enabled default False, config.py:348; run_cycle notify-only, strategy_engine.py:92-106). No drift.
  • CLAUDE.md says pitch provisioning is gated by ROBOCO_PROVISIONING_*. Code matches (provisioning_enabled + _token + _org + _repo_private + _timeout_seconds, config.py:309-336; GitHubProvisioningService.enabled requires all three, github_provisioning.py:64). No drift.
  • CLAUDE.md does not mention ProductService.project_for as the per-cell routing keystone, though it does describe product cell-routing as a feature. Code: product.py:87, called from the gateway delegate path. Doc omission, not contradiction.
  • CLAUDE.md does not mention ROBOCO_PROTECTED_GIT_URLS (the project denylist, config.py:770, project.py:40). Doc omission.
  • CLAUDE.md's service table does not list KanbanService, CompanyGoalsService, StrategyEngine, ResearchService, PitchService, GitHubProvisioningService. The CLAUDE.md "Services" table is explicitly a non-exhaustive "Core services" list, so this is an acknowledged omission rather than drift.
  • No contradictions between CLAUDE.md claims and actual code were found in this slice. All documented flags, defaults, and behaviors (default-off strategy engine, server-side- only keys, notify-only engine, pitch→provision→normal-lifecycle, CEO-only approve) match the code.
  • CLAUDE.md's "Board Program registry" entry documents the shipped scope accurately, including the stranded_blocked→Coroner gap. Code matches: program_armed has no master flag (services/board_programs.py:274), the strategy-engine fold is idle-only (strategy_engine.py:95-101). No drift.

Changes Since Baseline

git log --oneline fd10cc862c2020b3f639cdb686d427b0198a2441..HEAD -- <slice files> and git diff --stat for the nine in-scope files both return empty — no commit between the baseline (fd10cc86 "Update ci.yml") and HEAD (3aff6e04 "Chore: Close gaps (#285)") touched any file in this slice. The two commits ahead of baseline (15effce0 "141 Gaps fill-in (#283)", 3aff6e04 "Chore: Close gaps (#285)") modified other files only.

No logic-touching commits to list. Impact: none — this slice is byte-for-byte unchanged since the baseline.

Post-snapshot updates (since 2026-06-29): three commits landed on this slice's files.

  • 536bbb64 (Chore/all/logical gaps sweep #286, 2026-06-30): github_provisioning.py — added _GITHUB_REPO_EXISTS_STATUS = 422 sentinel and _fetch_existing_repo method; create_repo now handles 422 "already exists" idempotently, resolving the orphaned-repo partial-failure risk (#83/#84). pitch.py docstring updated to reflect new idempotency guarantee. project.py update() — removed exclude_none=True from model_dump so explicit-None fields now clear stored values (#197).
  • c71f9b3b ([chore] logical-gaps: kanban board column coverage + status-class fixes, 2026-06-30): kanban.py — added _load_subtask_counts batch query; _task_to_card now takes a subtask_counts dict and populates real subtask counts (#198). Added "Other" fallback column in _build_columns to prevent any task-card from being built-then-discarded. get_qa_board: removed VERIFYING from QA statuses (dev self-verification, not a QA state). get_documenter_board: added task_type == DOCUMENTATION filter. get_main_pm_board_flat: broadened status filter to include PENDING/CLAIMED/COMPLETED and added proper column routing (incoming/distributed/done). Added "Coordination" column for non-cell-team tasks (#196).
  • b3558d4e ([chore] complexity: split 5 C-rank blocks to <=B, 2026-06-30): kanban.py get_main_pm_board_flat — refactored if/elif routing to a dict-dispatch (status_col + team_col maps) for xenon complexity gate; no functional change.
  • v0.18.0 (2026-07-04): the X feature-spotlight content in this slice (XEngine feature-spotlight methods, _x_feature_spotlight_loop/_dispatch_feature_spotlight_exploration, migration 061, x_feature_spotlight_enabled) was authored directly into this file's Files/Key Symbols/Data Flow/Mermaid/Logical Tree/Entry Points sections at implementation time rather than landing as a dated delta — noted here for changelog continuity; the body text above is current as of this date. (Config Flags is unchanged — the X-engine flags live in deployment-tooling.md's comprehensive list, not here.)
  • 11915f36 (PR #551, Telegram V2 security follow-up, 2026-07-17): x_post_service.pyXPostService.approve/_approve_locked add a CANCELLED-task guard (pre-lock and re-checked under lock) returning a new already_rejected status, closing a live-reproduced approve-after-reject hole reachable via a stale Telegram Approve button (or a replayed HTTP call).
  • 57b9e76b (#607, "release caption uses curated CHANGELOG headlines, not commit subjects"): draft_release_post used to feed highlights=list(report.change_summary) — raw per-commit subjects — so the announcement model parroted the top commit's literal text. New pure changelog_highlights() extracts the bold feature leads from the curated release entry (report.drafted_changelog); approve() prefers those, falling back to change_summary only when the changelog yields nothing. The video pipeline's captions were already good (the authoring dev reads the changelog directly) — this brings X captions to the same source.
  • 16fa018a (#615, "real voice guide, slop ban, and caption craft"): release/reply drafts previously ran on a one-sentence voice stub while the reasoning-backed Head-of-Marketing voice guide only reached the off-by-default spotlight path. _hom_voice/_HOM_VOICE_GUIDE port the full VOICE GUIDE (confident-not-hedgy, one idea per post, no emoji spam, plain text, never invent facts) plus a slop-ban list (em dashes, "game-changer"/"seamless"/etc., exclamation pileups, "X isn't just Y" constructions, rule-of-three chains) and three fixed style exemplars into every drafting prompt; both prompts target well-under-240-chars so the 280 clamp never truncates mid-sentence. An empty brand_voice now nudges the CEO exactly once (a durable system_settings marker, _BRAND_VOICE_NUDGE_KEY) instead of silently shipping baseline voice forever; a failed reply draft now skips origination instead of shipping a generic "Thanks for the mention!".
  • 4585a248 (#648, "redraft loop on CEO reject"): XPostService.reject with a non-blank reason now schedules XEngine.redraft_from_rejection after its commit (defer_after_commit, fresh session, never blocks/fails the reject) — mirroring VideoEngine.reauthor_from_rejection. Deduped via _redraft_already_open (excludes CANCELLED, so a further redraft is allowed once THAT one is itself rejected) under a per-identity Redis lock (_acquire_redraft_lock/_REDRAFT_RELEASE_SCRIPT, mirroring XPostService's own lock style) so two racing rejects can't stack duplicate drafts. A local-model failure or empty revision output originates nothing — a degraded copy is worse than none.
  • 7e01c0ce (PR #570, "project-branded drafts + project badges", 2026-07-18): migration 075 adds company_goals.company_name; CompanyGoalsService.resolve_product_name (company_goals.py:79) is the new single fallback chain (project name → charter company_name → "RoboCo") consumed by both XEngine._voice_guide/draft_release_post and VideoEngine (see docs/map/video-engine.md) so their prompt builders stop hardcoding "RoboCo". New roboco/api/schemas/project_fields.py's task_project_fields helper adds project_slug/project_name to the X and video post-queue API responses (api/routes/x.py, api/routes/video.py); the panel renders them via a shared ProjectBadge — see docs/map/panel.md.
  • 461a6e1a+96401f4c+5f32d876 (Phases 1/2-3/4, 2026-07-18/19, #571/#575/#581) — Phase 4 makes GitHubProvisioningService provider-aware: _build_provider (github_provisioning.py:68) dispatches to GitHubProvider/GiteaProvider/GitLabProvider by ROBOCO_PROVISIONING_PROVIDER, .enabled additionally requires ROBOCO_PROVISIONING_HOST for gitlab/gitea, and _is_already_exists (github_provisioning.py:61) matches the "already exists" idempotency signal across all three forges' differing status codes/phrasing. The forge transport package itself (GitProvider/ForgeRouter/provider implementations) is documented in docs/map/worksession-git.md — this slice only covers the provisioning consumer.
  • a0baf94b ("agnosticism-residue", agnosticism audit items B6/B8): x_engine.py's remaining hardcoded "RoboCo" literals (the reply-prompt builder and the feature-spotlight exploration description — draft_release_post/_voice_guide were already fixed by 7e01c0ce above) are threaded out: _reply_prompt gains a product_name param, _FEATURE_EXPLORATION_DESCRIPTION (a module constant) becomes _feature_exploration_description(product_name) (a function), and run_cycle/open_feature_spotlight_exploration each resolve product_name once via resolve_product_name and thread it through.
  • Board Program registry (2026-07-24, #689/#699 + the Phase 2/3 program train). The single largest addition to this slice since the baseline: foundation/policy/board_programs.py (BoardProgram/PROGRAMS/program_due/project_participates) + services/board_programs.py (BoardProgramEngine) + api/routes/board_programs.py generalize the roadmap/spotlight shape into one registry-driven engine (migrations 087 board_program_cycles LEARN ledger, 088 projects.board_programs scoping column), migrating roadmap and x_feature onto it byte-for-byte (Phase 1) before adding twelve new programs across all three Board roles (Phase 2/3): Pest Control/Spackle/Scales/Dogfood (pest_control_engine.py/spackle_engine.py/scales_engine.py/dogfood_engine.py, Product Owner), Periscope/Megaphone/Mirror/Barfly/War Room (periscope_engine.py/megaphone_engine.py/mirror_engine.py/barfly_engine.py/war_room_engine.py, Head of Marketing), and Coroner/Sentinel/Librarian (coroner_engine.py/sentinel_engine.py/librarian_engine.py, Auditor). Arming has no master flag — program_armed reads a per-program settings-store row exclusively, except roadmap/x_feature's legacy env-flag fallback. StrategyEngine.run_cycle's idle observation now also triggers a Printer cycle via BoardProgramEngine.open_program_cycle("roadmap") (the stranded_blocked→Coroner half of the same design was NOT built — see Gotchas). Fourteen new propose_* do-verbs land in content_actions.py + api/schemas/v1/do.py; the Playwright MCP grant is task-scoped to Dogfood only, not a role-wide product_owner grant.

Regression Risks

No commit since fd10cc86 modified any file in this slice, so there are no recent-change regressions to flag. The table below lists standing structural risks already present in the code (not introduced by recent changes) that a future change in this slice or a caller could trip.

Title File:Line Claim Severity
Pitch partial-failure orphans GitHub repos RESOLVED 536bbb64 pitch.py / github_provisioning.py create_repo now handles GitHub 422 "already exists" by fetching the existing repo; re-approval is idempotent end-to-end. Initial partial failure still orphans the repo on GitHub, but re-approval recovers it automatically. medium
Seed-task failure after provisioning pitch.py:241-243 _seed_main_pm_task raises ValidationError if main-pm agent is missing — after repos + Product are already created. Another partial-failure window with no rollback. medium
Strategy engine failure is silent orchestrator.py:6380, strategy_engine.py:92 A failing assess is caught by the orchestrator's broad except Exception, logged, and retried next tick; the CEO is never notified that the engine is broken — looks dormant while actually erroring. low
Quota INCR-then-compare + fail-open research_quota.py:51-73 Over-limit calls still bump the counter (documented); Redis outage fails open (allowed=True), so a quota bypass during a Redis outage is by design. low
_replace_cells flush ordering is load-bearing product.py:135-143 The intermediate flush() (DELETEs before INSERTs) prevents a 409 on uq_product_projects_product_team. Refactoring it away reintroduces the unique-constraint collision on any team re-mapping. low
Flat main-PM board drops non-cell teams RESOLVED 536bbb64/b3558d4e kanban.py Added "Coordination" column; dict-dispatch routing ensures all tasks are placed. low
ProjectService.update skips None-set fields RESOLVED 536bbb64 project.py exclude_none=True removed from model_dump; explicit-None fields now clear stored values (#197). low
subtask_count always 0 in kanban cards RESOLVED c71f9b3b/536bbb64 kanban.py _load_subtask_counts batch-loads real child counts; _task_to_card uses them. low

Health

This slice is internally coherent and consistent with CLAUDE.md: every documented flag, default, and behavior matches the code, and the two slices-of-flow (CEO-driven pitch origination into the normal lifecycle; dormant notify-only strategy watcher) are cleanly separated and default-safe. The services follow a uniform BaseService + session-bound factory pattern, provider/research quotas fail open where cost-control (not security) is the goal, and the provisioning path is inert without token+org. The pitch approval path's external-side-effect non-atomicity remains (GitHub repo creation cannot roll back with the DB transaction), but re-approval is now idempotent end-to-end: create_repo handles GitHub 422 "already exists" by fetching the existing repo, and Project/Product rows are reused by slug, so a CEO re-approving after a partial failure recovers cleanly. The remaining open risk is _seed_main_pm_task failing after repos are already created (missing main-pm agent row). Post-snapshot three commits updated this slice's files, resolving four standing risks (kanban subtask counts, flat-board dropped cards, project.update None-field skip, and the pitch re-approval collision).