docs: v2 plan (shared plan + foundation + 3 agent TODO files)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mjalmoudy94
2026-10-13 22:57:06 +03:30
co-authored by Claude Sonnet 5.5
parent 8eb631aedf
commit 3a68b0fa07
5 changed files with 787 additions and 0 deletions
+155
View File
@@ -0,0 +1,155 @@
# TODO — Agent 2: AI Workforce Engine (manager AI, employees, dynamic pipelines, test/commit/push)
- **Worktree:** `/projects/smart-org-wt/agent2` · **Branch:** `agent2/ai-workforce` (already created, already installed)
- **Read first:** `docs/plan/PLAN-v2.md` (all of it, especially §3 and §5). Then read fully:
`apps/api/app/application/services/workforce_service.py`, `app/domain/workforce/models.py` + `tools.py`,
`app/interfaces/http/routers/workforce.py`, `app/infrastructure/repositories/workforce.py`,
`app/application/ports/model_provider.py`, `app/application/ports/realtime.py`, `app/workers/ai_runtime.py`,
`tests/test_workforce.py`.
- **Reference (concepts only, NEVER import, never edit):** `/projects/agybot/org_manager.py` (lines ~23-101
roles/pipelines/statuses, `_WORK_TRANSITIONS` ~413) and `/projects/agybot/org_orchestrator.py`
(`ROLE_TAGS`/`MODE_TAGS` ~206-223, `STATUS_ROLE` ~178, `DEFAULT_EFFORT_BY_ROLE` ~292,
`DIRECT_MANAGER_SYSTEM_PROMPT` ~662, `_manager_pipeline` ~7671). Read with grep or by line range; those files are huge.
- **Your files:** only the "Agent 2" column of PLAN-v2 §6, your block in `config.py` / `.env.example`, and the
migration `alembic/versions/a2b3c4d5e6f2_ai_workforce.py` (`down_revision = "a0f0e1d2c3b4"`).
New tables go in `app/infrastructure/db/models_engine.py`. **`db/models.py` is locked**, so new task and
employee fields go into new 1:1 side tables (see 1.3), never into `tasks`/`employees` columns.
- **Never touch:** the locked files and the other agents' files. Need something there? → `CHANGE REQUEST` in your report.
- **Do NOT commit, push, merge or rebase.** Never `cd` outside the worktree, never write to `/tmp`.
- **After each phase:** run all checks (PLAN-v2 §7.4), write `docs/plan/reports/agent-2-phase-<K>.md`, then STOP and wait.
You consume: `ModelProviderPort` (existing), `ModelGrantPort`, `MemoryPort`, `RepoWorkspacePort`,
`SandboxProviderPort` (existing), `DeliveryHooks.on_branch_pushed`, `EventPublisher`. Get them only from
the builders in `app/application/container.py`. Tests use the Fake/Null versions plus a **scripted fake model**
(you write it in `tests/engine_fakes.py`: returns queued responses in order and records the prompts it got).
You implement: `WorkforceBootstrapPort` (wired in `container_engine.py`).
**Important behaviour rules**
- The AI never runs git. It edits files and runs commands inside the sandbox; the platform does git through `RepoWorkspacePort`.
- Every model call writes a usage event exactly as the current `start_run` does (keep that code path).
Manager chat calls count too (agybot forgot this).
- Users never see model names. Store `ai_model_id` internally; APIs for non-owners return `tier` only.
- Nothing in this engine deploys to production. The furthest it goes is "merged and pushed to `dev`".
---
## Phase 1 — Engine core: protocol, state machine, pipelines (pure logic, no I/O)
Create package `app/application/engine/` (and `app/domain/workforce/` additions where the logic is pure):
- [ ] 1.1 `domain/workforce/pipeline.py`:
- `Stage` enum: `pending_manager_review`, `pending_analysis`, `pending_implementation`, `pending_review`, `pending_test`, `pending_final_review`, `merge_request`, `completed`, plus side states `pending_user_decision`, `error_review`, `paused`, `cancelled`, `failed`.
- `PIPELINES`: `plan_code_test` (default), `plan_code_review_test`, `code_test`, `analysis_only` (no worktree, no merge). Each is an ordered list of stages.
- `TRANSITIONS: dict[Stage, frozenset[Stage]]` and `next_stage(pipeline, current, outcome)`.
- `STAGE_ROLE`: stage → (role, mode). Manager modes are `triage`, `final`, `error`.
- `resolve_pipeline(manager_choice, task_kind, app_default, has_active_reviewer)`: priority manager > kind > app default > `plan_code_test`. A review pipeline without an active reviewer falls back to `plan_code_test`.
- `TASK_KINDS`: `feature`, `bugfix`, `refactor`, `docs`, `analysis` → default pipeline + risk.
- `RoleKey.DEVELOPER` is our "programmer". Keep the existing enum values; do not rename them.
- [ ] 1.2 `engine/protocol.py`: parser for `[[TAG attr="v" ...]]body[[/TAG]]` and self-closing `[[TAG attr="v"/]]`. Tolerant of whitespace; rejects unknown tags; returns typed dataclasses. Allowed tags per role/mode:
- manager/triage: `ASSIGN`, `REJECT`, `ASK_USER`, `KNOWLEDGE`
- manager/final: `COMPLETE`, `REVISE`, `KNOWLEDGE`
- manager/error: `RESOLVE_ERROR`, `CANCEL`, `KNOWLEDGE`
- manager/chat: `CREATE_TASK`, `PAUSE_TASK`, `RESUME_TASK`, `CANCEL_TASK`, `HIRE`, `UPDATE_EMPLOYEE`, `FIRE`, `KNOWLEDGE`
- planner: `PLAN_SPEC`, `SPLIT`, `ASK_USER`
- developer: `WORK_REPORT`
- reviewer: `REVIEW_PASS`, `REVIEW_CHANGES`
- tester: `TEST_PASS`, `TEST_FAIL`
- tool tags (all working roles): `READ_FILE path`, `WRITE_FILE path` (body = content), `EDIT_FILE path` (body = `<<<OLD … === … NEW>>>` search/replace blocks), `LIST_DIR path`, `SEARCH pattern path`, `RUN cmd timeout`
Disallowed tags are ignored and reported back to the model as an error line. Limits: max 3 `CREATE_TASK` per reply, max 3 team changes per turn, max 3 `KNOWLEDGE` per turn.
- [ ] 1.3 Tables in `models_engine.py`:
- `task_pipeline_state` (task_id PK/FK, pipeline, kind, risk, stage, cycle, max_cycles, branch, last_diff_hash, same_diff_count, retry_count, waiver_reason, parent_task_id, depends_on JSON, version int for optimistic updates, updated_at)
- `employee_profiles` (employee_id PK/FK, ai_model_id, effort, roles CSV, hired_by `user|manager`, busy_task_id)
- `engine_turns` (id, task_id, run_id, employee_id, role, mode, stage, prompt_chars, reply_excerpt ≤ 4000, tags JSON, tool_calls int, ok, error_kind, started_at, finished_at)
- `gate_results` (id, task_id, gate `tests|diff_guard|no_progress|green|lint`, ok, detail ≤ 8000, created_at)
Map the coarse `tasks.status` from the stage: new / running / waiting_approval / done / failed / cancelled. Existing APIs keep working.
- [ ] 1.4 `engine/state.py`: `advance(task_id, expected_from, to)` with an optimistic check (`version`). It fails cleanly when another worker moved the task first. A failed or cancelled **dependency** moves the dependents to `pending_user_decision`, never leaving them stuck (agybot bug M1).
- [ ] 1.5 Tests `tests/test_engine_protocol.py`, `tests/test_engine_pipeline.py`: every pipeline walks start→completed on the happy path; every illegal transition is refused; concurrent advance (two sessions) → exactly one wins; the dependency failure rule; tag parsing edge cases (nested brackets in a body, unknown tags, the per-role matrix, limits).
---
## Phase 2 — Agent turn runner (one employee, one turn, real tools in the sandbox)
- [ ] 2.1 `engine/prompt.py`, a prompt builder with a **stable prefix** (cache-friendly), in this order:
1. platform rules (short, yours)
2. role protocol (allowed tags + format examples)
3. toolkit description
4. app profile (from `AppProfile`: language, framework, commands; never secrets)
5. `MemoryPort.build_context(org, role, query=task text, budget_chars=2500)`
6. task brief (≤ 1,200 chars) + stage-specific context (the planner's spec for the developer; the **diff only** for the reviewer; test output for the tester)
7. the conversation so far in this turn (tool results)
Hard cap on total characters; truncate oldest tool results first.
- [ ] 2.2 `engine/tools.py`: executes tool tags **inside the sandbox** through `SandboxProviderPort`, rooted at `WorktreeRef.sandbox_path`.
- Path safety: normalise; reject absolute paths outside the root, `..` escapes and anything under `.git`.
- `RUN` has a timeout (default 120s, max 600s) and output capped (last 8,000 chars). The command is passed to the sandbox as-is (the sandbox is the security boundary), but `git push`, `git remote` and `git config` are refused with a message ("the platform handles git").
- `EDIT_FILE` applies search/replace blocks and fails with a clear message when OLD is not found exactly once.
- Each tool call writes a `tool_calls` row (existing table) with status `succeeded`/`failed`, so the existing approvals UI finally sees real executions.
- [ ] 2.3 `engine/runner.py`, `run_turn(task, employee, role, mode) -> TurnResult`. Loop:
- model call → parse tags → execute tools → feed results back → until a terminal protocol tag appears, or `ENGINE_MAX_TOOL_CALLS_PER_TURN`, or `ENGINE_TURN_TIMEOUT_SECONDS`.
- Model = the employee's `ai_model_id`, which must be granted (`ModelGrantPort.is_granted`; if not, fail with `error_kind=auth` and tell the manager).
- Error kinds `rate_limit`, `unavailable`, `network`, `timeout` are **transient** (retry later, up to 2 retries); `auth`, `model`, `protocol` are not.
- Writes `engine_turns`, `run_steps` and usage events, and publishes `run.log` / `run.tool` / `run.status` (batched, PLAN-v2 §5).
- Cancellation: `cancel_run` must stop the loop between steps.
- [ ] 2.4 Tests `tests/test_engine_runner.py` with the scripted fake model + `FakeRepoWorkspace` + a fake sandbox (dict filesystem + scripted command results; put it in `tests/engine_fakes.py`). Cover: the write → run → report loop; path escape refused; `git push` refused; the tool-call cap; timeout; transient vs fatal errors; usage events counted per model call; an ungranted model refused; no secret from settings ever appears in a prompt (assert on the fake model's recorded prompts).
---
## Phase 3 — Manager AI (orders → tasks → team)
- [ ] 3.1 Manager chat: rewrite `post_user_message` so the manager replies **and** may emit `CREATE_TASK` (title, kind, priority, depends_on, optional pipeline), `PAUSE_TASK` / `RESUME_TASK` / `CANCEL_TASK`, and `HIRE` / `UPDATE_EMPLOYEE` / `FIRE`. Tasks created this way get a `task_pipeline_state`, stage `pending_manager_review`, and are linked to the thread. The manager's prompt must ask for **atomic tasks that do not touch the same files**. Count chat usage.
- [ ] 3.2 Team management: `HIRE role= tier= name=` picks a granted model matching the tier/capabilities (`ModelGrantPort.granted_models`). Respect max employees (setting + later org policy), max 3 changes per turn, and "the last manager cannot be fired". Publish `team.changed`. Users can still hire and fire from the UI (existing endpoints) but only choose **role + tier**, never a model.
- [ ] 3.3 Triage turn (manager, mode triage): decide pipeline + assignee (`ASSIGN employee= pipeline=`), or `REJECT`, or `ASK_USER` (→ `pending_user_decision`, message posted in the thread). Default employee selection: least busy active employee with the needed role; `ASSIGN employee="name"` uses fuzzy name matching inside the org only.
- [ ] 3.4 Final turn (manager, mode final): sees the plan, a diff summary, gate results and the test output → `COMPLETE` (→ `merge_request`) or `REVISE` (→ back to implementation, cycle+1). The error turn (mode error) handles failed gates and errors → `RESOLVE_ERROR` (retry with a hint) or `CANCEL`.
- [ ] 3.5 `KNOWLEDGE` tags → `MemoryPort.record(kind=fact|rule, source=manager)`.
- [ ] 3.6 `WorkforceBootstrapPort.bootstrap_team(org, project)`: creates one manager + one planner + one developer + one tester on the strongest/balanced/fast granted tiers (whatever is granted; if nothing is granted, create the employees "inactive" and post a system message "منتظر تخصیص مدل توسط مالک سامانه"). Idempotent. Wire it in `container_engine.py`.
- [ ] 3.7 Tests `tests/test_engine_manager.py`: an order → ≤ 3 tasks with deps; HIRE limits; FIRE the last manager refused; ASK_USER path; idempotent bootstrap; cross-org isolation (the manager of org A can never assign an employee of org B, even by exact name).
---
## Phase 4 — Pipeline execution, gates, commit, merge, push
- [ ] 4.1 `engine/scheduler.py`, `tick()`. Each tick:
- finds tasks whose stage needs a turn and whose deps are completed, and a free employee with the role (one task per employee at a time)
- honours org automation (if Agent 1's policy says `automation_enabled=false`, tasks wait in `pending_manager_review`; read it through `ModelGrantPort` when the CHANGE REQUEST lands, until then assume enabled)
- runs turns concurrently with a per-org cap (3) and a global cap (setting)
`workers/ai_runtime.py` calls `tick()` in its loop every `ENGINE_TICK_SECONDS` (keep the one-pass mode for cron). Safe with two worker processes thanks to the optimistic `advance`.
- [ ] 4.2 Worktree lifecycle: on the first developer/planner turn, `RepoWorkspacePort.create_task_worktree(project, task, base="dev")`; the branch is `task/<task_id>`. `analysis_only` gets none. The worktree is removed after completion, cancellation or failure.
- [ ] 4.3 Gates after `WORK_REPORT` (each writes a `gate_results` row and publishes `task.gate`):
- **diff guard**: `RepoWorkspacePort.diff`. Fail on changes under `.git`, CI files (`.github/`, `.gitlab-ci.yml`, `.gitea/`), symlinks pointing outside the repo, or files > 1 MB. An empty diff for a code task also fails.
- **no-progress**: hash the diff; 2 identical hashes in a row → `error_review`.
- **tests**: run the app profile's `test_command` in the sandbox (timeout 600s), store the tail of the output. Red → back to the developer with the failure tail (one escalation to a stronger granted tier on the 2nd red), counting cycles. Green → tester stage.
- **green gate**: the tester's `TEST_PASS` is accepted only if the tests gate is green, OR `TEST_PASS waive="reason"` with a non-empty reason (stored in `waiver_reason`, shown in UI).
- `max_cycles` reached → `error_review`.
- [ ] 4.4 Commit + merge + push on `merge_request`:
- `commit` (message: `task/<id>: <title>`, author = employee display name + platform email), then `sync_with_base`, then re-run the tests if base changed (a semantic conflict is only caught by retests).
- Then `merge_into(target="dev")` and `push(branch="dev")`.
- A conflict → back to the developer with the conflicting files (max 3 times, then `error_review`).
- On success: stage `completed`, `DeliveryHooks.on_branch_pushed(org, project, "dev", sha, source="engine")`, `MemoryPort.on_task_finished(TaskOutcome)` (never let its failure fail the task).
- [ ] 4.5 Budget: before each turn, check the org credit wallet (existing billing service public functions; read, do not edit) and the per-task cap. Out of budget → `paused` with a Persian reason.
- [ ] 4.6 Tests `tests/test_engine_flow.py`, the end-to-end engine with all fakes. The scripted model plays manager → planner → developer → tester → manager; the fake sandbox makes tests red once and then green. Assert the stage sequence, gate rows, cycles, the commit/merge/push calls on `FakeRepoWorkspace`, `on_branch_pushed` called once with the right sha, `on_task_finished` called once, and usage events. Plus the failure paths: no-progress, max cycles, merge conflict ×3, cancel mid-turn, a dependency failure.
---
## Phase 5 — UI (orders, live task view, team)
- [ ] 5.1 `lib/api-engine.ts`: typed client functions for everything new (task detail with pipeline state, turns, gates, team with tier, hire/fire with role+tier).
- [ ] 5.2 `/manager`: the chat shows task cards created by the manager inline (title, stage badge, link), live via `org:` + `thread:` channels; an "در انتظار تصمیم شما" banner for `pending_user_decision` with a reply box.
- [ ] 5.3 `/tasks`: a board grouped by stage (columns: بررسی مدیر، تحلیل، پیاده‌سازی، بازبینی، تست، ادغام، انجام‌شده، نیازمند تصمیم) with a live update; filters; pause/resume/cancel (with confirm).
- [ ] 5.4 `/tasks/[id]`:
- a pipeline stepper (stages of its pipeline, current one highlighted)
- the turn timeline (who, role, when, outcome)
- a **live log** panel (subscribe to `run:<id>`, auto-scroll, pause-scroll)
- gate results (green/red with expandable output)
- a diff viewer (read-only, from the latest diff)
- the waiver reason when present
- a link to the pipeline run in `/app` once pushed (just a link with the sha; that page is Agent 3's)
- [ ] 5.5 `/workforce`: employees with role, tier, status, current task; hire (role + tier) and fire with confirm. **No model names anywhere** for non-owners (test it).
- [ ] 5.6 `/dashboard` + `/traces`: show active tasks, a stage distribution and recent completions; keep the existing widgets working.
- [ ] 5.7 Web tests `src/test/engine-*.test.tsx`: board grouping, live log append and batching, no model name rendered (render with a fixture that contains a model name and assert it is absent).
---
## Final report (after Phase 5)
`docs/plan/reports/agent-2-final.md`: summary, the engine architecture (one diagram in text), the tag protocol
reference table, every endpoint added or changed, tables, settings used, CHANGE REQUESTs still open (expected:
org policy in `ModelGrantPort`, the worker hook for memory expiry), manual QA steps, known limitations.