Replace the 4-line `bash + node -e + fetch POST /api/companies/:id/
agents` + `POST /api/companies/:id/approvals` imperative with two
sentences that point at the Team Building Phase SOP and the new
`hire_agent` / `file_approval` tools (registered on the CEO in part
3/5). The cue is now `"This wake: your team is empty." +
"File 3-5 hire_agent requests and 1 approve_ceo_strategy via your
assigned tools. Execute now, do not write a plan document."`
All five pre-C4 couplings removed from this turn:
- Tool name (`bash`) — gone; cue references the tool surface generically
- Implementation detail (`node -e '...'` + `fetch`) — gone
- API paths (`/api/companies/:id/agents` + `/approvals`) — gone
- Business numbers (3-5 hires + 1 strategy) stay, moved into the cue
- Imperative tone (`NOW / Do not`) — dropped in favour of "execute now"
The fail-through path on the CEO lookup used to be `} catch {}`
(silent swallow). Migrated to `logAppError` under a recoverable
classification so a stale agent/company read still logs a structured
line instead of vanishing. The fallback behaviour (generic timer turn)
is unchanged — pi agent construction must not crash on a prompt-
assembly blip.
This commit is the first one to change model-observable behaviour.
The ceo-bootstrap-empty eval fixture (recorded against the old cue)
is expected to drift to 4/5 until re-recorded; IC / manager / non-CEO
fixtures stay green because they do not hit this code path.
tests: pnpm typecheck + pnpm test:unit (529) + pnpm test:eval (8/8) green
Rewrite ~376 call sites for messages, runs, approvalsMod, and company
domains and delete the corresponding pass-through methods on
AppDatabase / AppDatabaseBase. Structural type references
(ReturnType<HandlerDeps["db"]["getRun"]>) and SweepStaleReviewsDeps-style
mocks are updated to route through the owning sub-module.
Rewrite ~525 call sites from `db.<method>()` to `db.<module>.<method>()`
for tasks and agents domains (including `getAgent`/`findAgent`/`getTask`
which live on the `entityCrud` field). Delete the corresponding
pass-through methods on AppDatabaseBase. Structural types that narrowed
`db` to a bag of flat methods (CapabilityMatchDb, EscalationDeps,
SweepStaleReviewsDeps) are reshaped to match the new namespace. Internal
callback references inside database.ts/database-base.ts now invoke the
owning module directly (e.g. `this.agents.saveAgent`) rather than the
deleted pass-through.
The codemod lives at scripts/codemod/remove-appdatabase-passthrough.mjs
and filters by owning module + optional explicit method list. It is
idempotent and does not re-rewrite already-namespaced calls.
Consolidate the two runtime messaging modules under src/main/messaging/. Storage stays in database/messages.ts — that is the SQL layer and belongs with the other DB modules; messaging/ covers the dispatch/normalize runtime around it.
- message-dispatch.ts -> messaging/dispatch.ts
- message-normalize.ts -> messaging/normalize.ts
- add messaging/index.ts barrel for future consumers (existing call sites import the concrete module path directly)
- 10 call sites updated across src/main and tests
advanceTaskStatus cleared checkout_run_id when transitioning out of
in_progress but left active_run_id alone. Heartbeat selects candidate
tasks by !activeRunId, so a task recovered from a crash (moved
blocked → todo by the bootstrap recovery) could carry a dead
active_run_id pointer and remain silently unpickable by every future
wake, even though the run it points to is long since interrupted.
interruptStaleRuns normally clears active_run_id for tasks whose
owning run is now interrupted, but the guarantee relied on a single
startup pass. Make advanceTaskStatus defensive: when the terminal
state is todo or backlog, clear the pointer too. The invariant holds
end-to-end instead of relying on interruptStaleRuns having seen every
edge case.
Also harden the new-CEO team-building cue: make it an explicit
imperative ("call the bash tool to run the fetch POSTs now") and
route app_restart_resume through the same branch so a startup-crash
recovery wake lands on the same focused action script.
The timer initial user turn hard-coded the priority sequence
"deliverable reviews first, unblocking reports second, next step on
assigned work third" — a ranking that only makes sense for agents
with a team and tasks. A brand-new CEO in team_building mode has
none of those, so the model read the cue, matched nothing, and
returned without firing the hire POSTs its system prompt explicitly
described.
Detect that branch in resolveInitialUserTurn: if the trigger is a
timer or bootstrap wake and the agent is a top-level CEO with zero
active direct reports, emit a cue that points at the Team Building
operating instructions. Also soften the generic timer cue so it
defers to the agent's current operating mode instead of dictating a
task-centric priority order that doesn't fit every role.
Top-level tasks submitted to in_review fired the same
subtask_completed wake as the subtree leaves, so the reviewer's
prompt framed the work as "a subtask you delegated". Add a dedicated
task_review_requested wake reason with a reviewer-specific title and
step list, and route the parent-less branch of the task PATCH handler
through it.
Replace the worker's generic `"Continue."` default with a dispatch-time
`initialUserTurn` that is resolved per trigger. Message and peer-request
wakes now enumerate the actual unread messages (id, sender, channel,
subject, reply-to) so the agent's first user turn carries the entities
it is expected to act on, instead of idling into a status summary.
Drop the system-prompt `heartbeatFallbackSection`: its "respond with a
brief status summary" line fought the wake-reason block above it and
was the main reason communication wakes degraded into self-narration.
The wake-reason section plus the bound initial user turn now cover
every case that section was carrying.