Phase 3 of the AI-workforce engine (TODO-agent-2):
- engine/manager.py: ManagerTurns.chat (CREATE_TASK/PAUSE/RESUME/CANCEL,
HIRE/UPDATE_EMPLOYEE/FIRE, KNOWLEDGE), triage (ASSIGN/REJECT/ASK_USER),
final_review (COMPLETE/REVISE +cycle), error_review (RESOLVE_ERROR/CANCEL),
bootstrap_team; post_user_message now delegates to it.
- One billing path: call_model/BillingScope in runner.py is the single model
entry point (fixes the agybot chat-billing bug).
- Team changes capped at 3/turn, employee cap, last manager cannot be fired,
hires pick a granted model of the requested tier strictly.
- container_engine.py wires the real WorkforceBootstrapPort.
- Fixes: REVISE is a body tag (prompt and parser now agree); ASSIGN
employee="" means pick-for-me; the runner's protocol-error retry is capped by
engine_max_protocol_retries (was unbounded until the turn timeout).
- 20 new manager tests + regression tests; report in
docs/plan/reports/agent-2-phase-3.md.
Phase 2 of TODO-agent-2: one employee, one turn, real tools in the sandbox.
- engine/prompt.py: the stable, cache-friendly prefix in the plan's order
(rules → role protocol → toolkit → app profile → memory), a task brief
capped at 1200 chars, role-specific stage context (spec for the developer,
the diff only for the reviewer, test output for the tester) and a hard
prompt cap that drops the oldest tool results first.
- engine/tools.py: READ/WRITE/EDIT/LIST_DIR/SEARCH/RUN executed inside the
sandbox through SandboxProviderPort, rooted at the worktree's sandbox path.
Path escapes, .. and .git are refused; git push/remote/config are refused
with "the platform does git"; RUN clamps its timeout (120s default, 600s
max) and keeps the tail of its output; every execution writes a tool_calls
row so the approvals UI shows real work.
- engine/runner.py: the loop — model call, parse tags, execute tool tags,
feed results back — until a terminal tag, the tool cap, the turn timeout or
a cancellation. The employee's model is checked against ModelGrantPort
before the first call (ungranted → error_kind=auth, no spend), errors are
classified transient vs fatal, and every model call writes usage events
through the one shared record_model_usage path (extracted from
WorkforceService so manager chat counts too). Writes engine_turns, run_steps
and tool_calls.
- engine/events.py: RunFeed batches run.log (≤1 event / 500 ms / stream, ≤200
lines) and emits run.tool and task.gate through the generic envelope.
- tests: engine_fakes.py (ScriptedModel, FakeSandbox, FakeGrants,
RecordingMemory) plus 54 new tests in test_engine_runner,
test_engine_prompt and test_engine_tools.
Suite: 648 passed, ruff clean, mypy clean.
Phase 1 of TODO-agent-2: pure logic plus the tables it writes to.
- domain/workforce/pipeline.py: Stage/PipelineKey/Outcome, the four pipelines,
TRANSITIONS, next_stage, STAGE_ROLE (role+mode), resolve_pipeline with the
no-reviewer fallback, task-kind defaults, stage -> coarse tasks.status, and
the TaskPipelineState record (with the optimistic version).
- application/engine/protocol.py: the [[TAG attr="v"]]body[[/TAG]] parser and
its self-closing form, the per-role/mode allow-list, literal bodies (a
bracket inside a body is not a tag), typed readers (ASSIGN/CREATE_TASK/
team changes/KNOWLEDGE/EDIT_FILE blocks) and the per-reply limits
(3 tasks, 3 team changes, 3 knowledge items).
- application/engine/state.py: advance() with the optimistic compare-and-set
and park_dependents() so a failed dependency never deadlocks its children.
- db/models_engine.py + migration a2b3c4d5e6f2: task_pipeline_state,
employee_profiles, engine_turns, gate_results as side tables (tasks and
employees stay locked). Linear head; alembic check reports no drift.
- repositories/engine.py + 58 new tests (test_engine_protocol,
test_engine_pipeline) covering every pipeline's happy path, illegal
transitions, exactly-one-winner concurrent advance, dependency parking and
all parser edge cases.
Suite: 594 passed, ruff clean, mypy clean.