Skill: zdp1117/team-tasks-skip (version 1.0.0)

--- name: team-tasks description: "Coordinate multi-agent development pipelines using shared JSON task files. Use when dispatching work across dev team agents (code-agent, test-agent, docs-agent, monitor-bot), tracking pipeline progress, or running sequential/parallel workflows. Covers project init, task assignment, status tracking, agent dispatch via sessions_send, and result collection. Supports two modes: linear (sequential pipeline) and dag (dependency graph with parallel execution)." --- # Team Tasks — Multi-Agent Pipeline Coordination ## Overview Coordinate dev team agents through shared JSON task files + AGI dispatch. AGI is the command center — agents never talk to each other directly. **Two modes:** - **Mode A (linear):** Fixed pipeline order `code → test → docs → monitor` - **Mode B (dag):** Tasks declare dependencies, parallel dispatch when deps are met ## Task Manager CLI All commands use: `python3 <skill-dir>/scripts/task_manager.py <command> [args]` Where `<skill-dir>` is the directory containing this SKILL.md. ### Quick Reference | Command | Mode | Usage | Description | |---------|------|-------|-------------| | `init` | both | `init <project> -g "goal" [-m linear\|dag]` | Create project | | `add` | dag | `add <project> <task-id> -a <agent> -d <deps>` | Add task with deps | | `status` | both | `status <project> [--json]` | Show progress | | `assign` | both | `assign <project> <task> "desc"` | Set task description | | `update` | both | `update <project> <task> <status>` | Change status | | `next` | linear | `next <project> [--json]` | Get next stage | | `ready` | dag | `ready <project> [--json]` | Get all dispatchable tasks | | `graph` | dag | `graph <project>` | Show dependency tree | | `log` | both | `log <project> <task> "msg"` | Add log entry | | `result` | both | `result <project> <task> "output"` | Save output | | `reset` | both | `reset <project> [task] [--all]` | Reset to pending | | `list` | both | `list` | List all projects | ### Status Values - `pending` — waiting for dispatch - `in-progress` — agent is working - `done` — stage completed - `failed` — stage failed (pipeline blocks) - `skipped` — intentionally skipped ## Pipeline Workflow (Mode A: Linear) ### Step 1: Initialize Project ```bash python3 scripts/task_manager.py init my-project \ -g "Build a REST API with tests and docs" \ -p "code-agent,test-agent,docs-agent,monitor-bot" ``` Default pipeline order: `code-agent → test-agent → docs-agent → monitor-bot` ### Step 2: Assign Tasks to All Stages ```bash python3 scripts/task_manager.py assign my-project code-agent "Implement REST API with Flask: GET/POST/DELETE /items" python3 scripts/task_manager.py assign my-project test-agent "Write pytest tests for all endpoints, target 90%+ coverage" python3 scripts/task_manager.py assign my-project docs-agent "Write README.md with API docs, setup guide, examples" python3 scripts/task_manager.py assign my-project monitor-bot "Verify code quality, check for security issues, validate deployment readiness" ``` ### Step 3: Dispatch Agents Sequentially For each stage, AGI follows this loop: ``` 1. Check next stage: task_manager.py next <project> --json 2. Mark in-progress: task_manager.py update <project> <agent> in-progress 3. Dispatch agent: sessions_send(sessionKey="agent:<agent>:telegram:group:<id>", message=<task>) 4. Wait for reply (sessions_send returns the agent's response) 5. Save result: task_manager.py result <project> <agent> "<summary>" 6. Mark done: task_manager.py update <project> <agent> done 7. Repeat from 1 (currentStage auto-advances) ``` ### Step 4: Handle Failures If an agent fails: ```bash python3 scripts/task_manager.py update my-project code-agent failed python3 scripts/task_manager.py log my-project code-agent "Failed: syntax error in main.py" ``` To retry: ```bash python3 scripts/task_manager.py reset my-project code-agent python3 scripts/task_manager.py update my-project code-agent in-progress # Re-dispatch... ``` ### Step 5: Check Progress Anytime ```bash python3 scripts/task_manager.py status my-project ``` Output: ``` 📋 Project: my-project 🎯 Goal: Build a REST API with tests and docs 📊 Status: active ▶️ Current: test-agent ✅ code-agent: done Task: Implement REST API with Flask Output: Created /home/ubuntu/projects/my-project/app.py 🔄 test-agent: in-progress Task: Write pytest tests for all endpoints ⬜ docs-agent: pending ⬜ monitor-bot: pending Progress: [██░░] 2/4 ``` ## Agent Dispatch Details ### Session Keys (Dev Team) | Agent | Session Key | |-------|-------------| | code-agent | `agent:code-agent:telegram:group:-5189558203` | | test-agent | `agent:test-agent:telegram:group:-5218382533` | | docs-agent | `agent:docs-agent:telegram:group:-5253138320` | | monitor-bot | `agent:monitor-bot:telegram:group:-5193935559` | ### Dispatch Template When dispatching to an agent, include: 1. **Project context** — what the project is about 2. **Specific task** — what this agent should do 3. **Working directory** — where to create/find files 4. **Previous stage output** — if relevant (e.g., test-agent needs to know what code-agent built) Example dispatch message: ``` Project: my-project Goal: Build a REST API with tests and docs Your task: Write pytest tests for all endpoints in /home/ubuntu/projects/my-project/app.py Target: 90%+ coverage, test GET/POST/DELETE /items Working directory: /home/ubuntu/projects/my-project/ Previous stage (code-agent) output: Created app.py with Flask REST API, 3 endpoints ``` ### Delivery Context Fix ⚠️ If an agent's session was first created via `sessions_send`, its `deliveryContext` is `webchat`, not `telegram`. Agent replies won't appear in the Telegram group. **Workaround**: After getting the agent's reply via `sessions_send`, use the `message` tool to relay key results to the group: ``` message(action="send", channel="telegram", target="-5189558203", message="✅ code-agent 完成: Created app.py") ``` ## Mode B: DAG Workflow (Parallel Dependencies) ### Step 1: Initialize DAG Project ```bash python3 scripts/task_manager.py init my-project -m dag -g "Build REST API with parallel workstreams" ``` ### Step 2: Add Tasks with Dependencies ```bash TM="python3 scripts/task_manager.py" # Root tasks (no deps — can run in parallel) $TM add my-project design -a docs-agent --desc "Write API spec" $TM add my-project scaffold -a code-agent --desc "Create project skeleton" # Tasks with dependencies (blocked until deps are done) $TM add my-project implement -a code-agent -d "design,scaffold" --desc "Implement API" $TM add my-project write-tests -a test-agent -d "design" --desc "Write test cases from spec" # Fan-in: depends on multiple tasks $TM add my-project run-tests -a test-agent -d "implement,write-tests" --desc "Run all tests" $TM add my-project write-docs -a docs-agent -d "implement" --desc "Write final docs" # Final gate $TM add my-project review -a monitor-bot -d "run-tests,write-docs" --desc "Final review" ``` ### Step 3: View DAG Graph ```bash $TM graph my-project ``` ``` ├─ ⬜ design [docs-agent] │ ├─ ⬜ implement [code-agent] │ │ ├─ ⬜ run-tests [test-agent] │ │ │ └─ ⬜ review [monitor-bot] │ │ └─ ⬜ write-docs [docs-agent] │ └─ ⬜ write-tests [test-agent] └─ ⬜ scaffold [code-agent] └─ ⬜ implement (↑ see above) ``` ### Step 4: Dispatch Ready Tasks ```bash $TM ready my-project # Shows all tasks whose deps are met ``` For each ready task, AGI follows this loop: ``` 1. Get ready tasks: task_manager.py ready <project> --json 2. For each ready task (can dispatch in parallel): a. Mark in-progress: task_manager.py update <project> <task> in-progress b. Dispatch agent: sessions_send(sessionKey=..., message=<task + dep outputs>) 3. When agent replies: a. Save result: task_manager.py result <project> <task> "<summary>" b. Mark done: task_manager.py update <project> <task> done c. Check newly unblocked tasks (printed automatically) 4. Repeat until all done ``` ### Key DAG Features - **Parallel dispatch**: `ready` returns ALL tasks whose deps are satisfied — dispatch them simultaneously - **Dep outputs forwarding**: `ready --json` includes `depOutputs` — previous stage results to pass to agents - **Auto-unblock notification**: When a task completes, shows which tasks are newly unblocked - **Cycle detection**: `add` rejects tasks that would create circular dependencies - **Partial failure**: If one task fails, unrelated branches continue; only downstream tasks block - **Graph visualization**: `graph` shows tree view with status icons and dedup markers ## Custom Pipelines ### Linear (Mode A) ```bash # Code + test only python3 scripts/task_manager.py init quick-fix -g "Hotfix" -p "code-agent,test-agent" # Docs first, then code python3 scripts/task_manager.py init spec-driven -g "Spec-driven dev" -p "docs-agent,code-agent,test-agent" ``` ### DAG (Mode B) ```bash # Diamond pattern: 2 parallel branches merge for review $TM init diamond -m dag -g "Parallel dev" $TM add diamond code -a code-agent --desc "Write code" $TM add diamond test -a test-agent --desc "Write tests" $TM add diamond integrate -a code-agent -d "code,test" --desc "Integration" $TM add diamond review -a monitor-bot -d "integrate" --desc "Final review" ``` ## Choosing Between Modes | | Mode A (linear) | Mode B (dag) | |---|---|---| | **When** | Sequential tasks, simple flows | Parallel workstreams, complex deps | | **Dispatch** | One at a time, auto-advance | Multiple simultaneous, dep-driven | | **Setup** | `init -p agents` (one command) | `init -m dag` + `add` per task | | **Best for** | Bug fixes, simple features | Large features, spec-driven dev | ## Data Location Task files: `/home/ubuntu/clawd/data/team-tasks/<project>.json` ## ⚠️ Common Pitfalls ### Mode A: Stage ID is agent name, NOT a number In linear mode, the stage ID is the **agent name** (e.g., `code-agent`), not a numeric index like `1`, `2`, `3`. ```bash # ❌ WRONG — will error "stage '1' not found" python3 scripts/task_manager.py assign my-project 1 "Build API" python3 scripts/task_manager.py update my-project 1 done # ✅ CORRECT — use agent name as stage ID python3 scripts/task_manager.py assign my-project code-agent "Build API" python3 scripts/task_manager.py update my-project code-agent done python3 scripts/task_manager.py result my-project code-agent "Created main.py" ``` This applies to all stage-referencing commands: `assign`, `update`, `result`, `log`, `reset`. The pipeline order is defined by `-p` at `init` time (e.g., `-p "code-agent,test-agent,docs-agent"`), and `next` automatically advances through them in order — but you always reference stages by agent name. ## Tips - **One project per task** — keep scope focused; create multiple projects for parallel work - **Meaningful project slugs** — `rest-api-v2`, `bug-fix-auth`, `refactor-db` (not `project1`) - **Save results** — always `result` before `update done`; this is the inter-agent context - **Log liberally** — `log` is cheap; helps debug failed pipelines - **Reset and retry** — `reset --all` for clean reruns; `reset <stage>` for targeted retry - **DAG fan-out** — one root task can unblock many parallel tasks - **DAG fan-in** — a task can depend on multiple predecessors (all must complete)

path: docs/AGENT_TEAMS_OFFICIAL_DOCS.md
content: # Claude Code Agent Teams — Official Documentation
Source: https://docs.anthropic.com/en/docs/claude-code/agent-teams (fetched 2026-02-08)

## Overview
Agent teams let you coordinate multiple Claude Code instances working together. One session acts as the team lead, coordinating work, assigning tasks, and synthesizing results. Teammates work independently, each in its own context window, and communicate directly with each other.

Unlike subagents (which run within a single session and can only report back to the main agent), you can also interact with individual teammates directly without going through the lead.

## When to use agent teams
Agent teams are most effective for tasks where parallel exploration adds real value:
- Research and review: multiple teammates investigate different aspects simultaneously, then share and challenge each other's findings
- New modules or features: teammates each own a separate piece without stepping on each other
- Debugging with competing hypotheses: teammates test different theories in parallel and converge faster
- Cross-layer coordination: changes that span frontend, backend, and tests, each owned by a different teammate

## Compare with subagents
| Feature | Subagents | Agent Teams |
|---------|-----------|-------------|
| Context | Own context window; results return to caller | Own context window; fully independent |
| Communication | Report results back to main agent only | Teammates message each other directly |
| Coordination | Main agent manages all work | Shared task list with self-coordination |
| Best for | Focused tasks where only result matters | Complex work requiring discussion and collaboration |
| Token cost | Lower: results summarized back | Higher: each teammate is separate instance |

## Architecture
An agent team consists of:
- **Team lead**: Main session that creates the team, spawns teammates, coordinates work
- **Teammates**: Separate Claude Code instances that work on assigned tasks
- **Task list**: Shared list of work items that teammates claim and complete
- **Mailbox**: Messaging system for communication between agents

## Key Features

### Teammate Communication
- **message**: send to one specific teammate
- **broadcast**: send to all teammates simultaneously
- **Automatic message delivery**: messages delivered automatically to recipients
- **Idle notifications**: when teammate finishes, automatically notifies the lead
- **Shared task list**: all agents see task status and claim available work

### Task Management
- Tasks have three states: pending, in progress, completed
- Tasks can depend on other tasks (blocked until dependencies complete)
- Lead can assign tasks explicitly, or teammates self-claim
- Task claiming uses file locking (prevents race conditions)
- Task dependencies auto-unblock when prerequisites complete

### Delegate Mode
- Restricts lead to coordination-only tools
- Lead focuses on orchestration: spawning, messaging, managing tasks
- Prevents lead from implementing tasks itself

### Plan Approval
- Teammates can be required to plan before implementing
- Teammate works in read-only plan mode until lead approves
- Lead reviews and approves/rejects plans
- If rejected, teammate revises and resubmits

### Quality Gates (Hooks)
- **TeammateIdle**: runs when teammate about to go idle; exit code 2 sends feedback
- **TaskCompleted**: runs when task being marked complete; exit code 2 prevents completion

### Display Modes
- **In-process**: all teammates in main terminal, Shift+Up/Down to select
- **Split panes**: each teammate in own pane (requires tmux or iTerm2)

## Use Case Examples

### Parallel Code Review
Spawn 3 reviewers: security, performance, test coverage. Each applies different filter to same PR. Lead synthesizes findings.

### Competing Hypotheses (Debate)
Spawn 5 teammates to investigate different hypotheses. They talk to each other to try to disprove each other's theories, like a scientific debate. The theory that survives is more likely to be correct.

### Cross-layer Feature Development
Spawn teammates for frontend, backend, and tests. Each owns their layer. Shared task list with dependencies coordinates the work.

## Data Storage
- Team config: `~/.claude/teams/{team-name}/config.json`
- Task list: `~/.claude/tasks/{team-name}/`
- Members array: name, agent ID, agent type

## Permissions
- Teammates inherit lead's permission settings
- Can change individual modes after spawning
- Can't set per-teammate modes at spawn time

## Context
- Each teammate has own context window
- Loads same project context: CLAUDE.md, MCP servers, skills
- Receives spawn prompt from lead
- Lead's conversation history does NOT carry over


path: docs/debate-mode-flow.svg
content: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 920" font-family="'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif">
  <defs>
    <!-- 渐变定义 -->
    <linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" style="stop-color:#0f0c29"/>
      <stop offset="50%" style="stop-color:#1a1a3e"/>
      <stop offset="100%" style="stop-color:#24243e"/>
    </linearGradient>
    <linearGradient id="headerGrad" x1="0%" y1="0%" x2="100%" y2="0%">
      <stop offset="0%" style="stop-color:#667eea"/>
      <stop offset="100%" style="stop-color:#764ba2"/>
    </linearGradient>
    <linearGradient id="agiGrad" x1="0%" y1="0%" x2="0%" y2="100%">
      <stop offset="0%" style="stop-color:#f093fb"/>
      <stop offset="100%" style="stop-color:#f5576c"/>
    </linearGradient>
    <linearGradient id="agentAGrad" x1="0%" y1="0%" x2="0%" y2="100%">
      <stop offset="0%" style="stop-color:#4facfe"/>
      <stop offset="100%" style="stop-color:#00f2fe"/>
    </linearGradient>
    <linearGradient id="agentBGrad" x1="0%" y1="0%" x2="0%" y2="100%">
      <stop offset="0%" style="stop-color:#43e97b"/>
      <stop offset="100%" style="stop-color:#38f9d7"/>
    </linearGradient>
    <linearGradient id="agentCGrad" x1="0%" y1="0%" x2="0%" y2="100%">
      <stop offset="0%" style="stop-color:#fa709a"/>
      <stop offset="100%" style="stop-color:#fee140"/>
    </linearGradient>
    <linearGradient id="phaseGrad" x1="0%" y1="0%" x2="100%" y2="0%">
      <stop offset="0%" style="stop-color:#a18cd1"/>
      <stop offset="100%" style="stop-color:#fbc2eb"/>
    </linearGradient>
    <linearGradient id="synthGrad" x1="0%" y1="0%" x2="0%" y2="100%">
      <stop offset="0%" style="stop-color:#ffd200"/>
      <stop offset="100%" style="stop-color:#f7971e"/>
    </linearGradient>
    <linearGradient id="jsonGrad" x1="0%" y1="0%" x2="0%" y2="100%">
      <stop offset="0%" style="stop-color:#6a11cb33"/>
      <stop offset="100%" style="stop-color:#2575fc33"/>
    </linearGradient>
    <!-- 箭头标记 -->
    <marker id="arrowWhite" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
      <polygon points="0 0, 10 3.5, 0 7" fill="#ffffff99"/>
    </marker>
    <marker id="arrowPink" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
      <polygon points="0 0, 10 3.5, 0 7" fill="#f5576c"/>
    </marker>
    <marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
      <polygon points="0 0, 10 3.5, 0 7" fill="#4facfe"/>
    </marker>
    <marker id="arrowGreen" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
      <polygon points="0 0, 10 3.5, 0 7" fill="#43e97b"/>
    </marker>
    <marker id="arrowOrange" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
      <polygon points="0 0, 10 3.5, 0 7" fill="#ffd200"/>
    </marker>
    <marker id="arrowCyan" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
      <polygon points="0 0, 10 3.5, 0 7" fill="#00f2fe"/>
    </marker>
    <!-- 发光效果 -->
    <filter id="glow">
      <feGaussianBlur stdDeviation="3" result="coloredBlur"/>
      <feMerge>
        <feMergeNode in="coloredBlur"/>
        <feMergeNode in="SourceGraphic"/>
      </feMerge>
    </filter>
    <filter id="softShadow">
      <feDropShadow dx="2" dy="3" stdDeviation="4" flood-color="#00000066"/>
    </filter>
  </defs>

  <!-- 背景 -->
  <rect width="1100" height="920" fill="url(#bgGrad)" rx="16"/>

  <!-- 标题栏 -->
  <rect x="30" y="20" width="1040" height="56" rx="12" fill="url(#headerGrad)" opacity="0.9" filter="url(#softShadow)"/>
  <text x="550" y="55" text-anchor="middle" fill="white" font-size="22" font-weight="bold">🗣️  Mode C — 多 Agent 辩论模式 · 执行流程与架构图</text>

  <!-- ═══════════ 阶段 0: 初始化 ═══════════ -->
  <rect x="30" y="96" width="1040" height="2" rx="1" fill="url(#phaseGrad)" opacity="0.4"/>
  <text x="50" y="118" fill="#a18cd1" font-size="13" font-weight="bold" letter-spacing="2">阶段 0 ─ 初始化</text>

  <!-- 用户 -->
  <rect x="60" y="132" width="160" height="54" rx="10" fill="#ffffff11" stroke="#ffffff44" stroke-width="1.2"/>
  <text x="140" y="155" text-anchor="middle" fill="white" font-size="14" font-weight="bold">👤 用户 (Master)</text>
  <text x="140" y="173" text-anchor="middle" fill="#aaa" font-size="11">提出辩论问题</text>

  <!-- 箭头: 用户 → AGI -->
  <line x1="220" y1="159" x2="310" y2="159" stroke="#ffffff99" stroke-width="1.5" marker-end="url(#arrowWhite)" stroke-dasharray="6,3"/>
  <text x="265" y="151" text-anchor="middle" fill="#ccc" font-size="10">init</text>

  <!-- AGI 调度中心 -->
  <rect x="315" y="126" width="200" height="66" rx="12" fill="url(#agiGrad)" opacity="0.15" stroke="#f5576c" stroke-width="1.5" filter="url(#softShadow)"/>
  <text x="415" y="153" text-anchor="middle" fill="#f5576c" font-size="15" font-weight="bold" filter="url(#glow)">🧠 AGI 调度中心</text>
  <text x="415" y="173" text-anchor="middle" fill="#f5576cbb" font-size="11">中心化编排 · 全流程控制</text>

  <!-- 箭头: AGI → JSON -->
  <line x1="515" y1="159" x2="605" y2="159" stroke="#f5576c" stroke-width="1.5" marker-end="url(#arrowPink)" stroke-dasharray="6,3"/>
  <text x="560" y="151" text-anchor="middle" fill="#f5576caa" font-size="10">创建项目</text>

  <!-- JSON 文件 -->
  <rect x="610" y="130" width="190" height="58" rx="10" fill="url(#jsonGrad)" stroke="#6a11cb66" stroke-width="1.2"/>
  <text x="705" y="153" text-anchor="middle" fill="#d4b0ff" font-size="13" font-weight="bold">📄 project.json</text>
  <text x="705" y="172" text-anchor="middle" fill="#9988cc" font-size="10">mode: "debate" · goal · debaters</text>

  <!-- CLI 命令提示 -->
  <rect x="835" y="132" width="220" height="54" rx="8" fill="#ffffff08" stroke="#ffffff22" stroke-width="1"/>
  <text x="945" y="152" text-anchor="middle" fill="#888" font-size="10" font-family="monospace">init proj --mode debate -g "问题"</text>
  <text x="945" y="167" text-anchor="middle" fill="#888" font-size="10" font-family="monospace">add-debater proj agent-a</text>
  <text x="945" y="182" text-anchor="middle" fill="#888" font-size="10" font-family="monospace">add-debater proj agent-b -r "角色"</text>

  <!-- ═══════════ 阶段 1: 初始立场 ═══════════ -->
  <rect x="30" y="210" width="1040" height="2" rx="1" fill="url(#phaseGrad)" opacity="0.4"/>
  <text x="50" y="232" fill="#a18cd1" font-size="13" font-weight="bold" letter-spacing="2">阶段 1 ─ 初始立场收集 (Round 1: initial)</text>

  <!-- AGI 派发框 -->
  <rect x="120" y="248" width="140" height="44" rx="10" fill="#f5576c22" stroke="#f5576c88" stroke-width="1.2"/>
  <text x="190" y="266" text-anchor="middle" fill="#f5576c" font-size="12" font-weight="bold">🧠 AGI 派发</text>
  <text x="190" y="281" text-anchor="middle" fill="#f5576caa" font-size="10">round start</text>

  <!-- 三条派发箭头 -->
  <path d="M260 260 C320 260, 340 270, 370 270" fill="none" stroke="#4facfe" stroke-width="1.5" marker-end="url(#arrowBlue)"/>
  <path d="M260 270 C320 270, 340 340, 370 340" fill="none" stroke="#43e97b" stroke-width="1.5" marker-end="url(#arrowGreen)"/>
  <path d="M260 280 C320 280, 340 410, 370 410" fill="none" stroke="#fa709a" stroke-width="1.5"/>

  <!-- Agent A -->
  <rect x="375" y="248" width="220" height="60" rx="10" fill="url(#agentAGrad)" opacity="0.12" stroke="#4facfe" stroke-width="1.3" filter="url(#softShadow)"/>
  <text x="485" y="270" text-anchor="middle" fill="#4facfe" font-size="13" font-weight="bold">🔧 Agent A (code-agent)</text>
  <text x="485" y="287" text-anchor="middle" fill="#4facfebb" font-size="11">角色: 安全专家</text>
  <text x="485" y="302" text-anchor="middle" fill="#4facfe88" font-size="10">→ 输出初始立场 A</text>

  <!-- Agent B -->
  <rect x="375" y="318" width="220" height="60" rx="10" fill="url(#agentBGrad)" opacity="0.12" stroke="#43e97b" stroke-width="1.3" filter="url(#softShadow)"/>
  <text x="485" y="340" text-anchor="middle" fill="#43e97b" font-size="13" font-weight="bold">🧪 Agent B (test-agent)</text>
  <text x="485" y="357" text-anchor="middle" fill="#43e97bbb" font-size="11">角色: QA 工程师</text>
  <text x="485" y="372" text-anchor="middle" fill="#43e97b88" font-size="10">→ 输出初始立场 B</text>

  <!-- Agent C -->
  <rect x="375" y="388" width="220" height="60" rx="10" fill="url(#agentCGrad)" opacity="0.12" stroke="#fa709a" stroke-width="1.3" filter="url(#softShadow)"/>
  <text x="485" y="410" text-anchor="middle" fill="#fa709a" font-size="13" font-weight="bold">🚨 Agent C (monitor-bot)</text>
  <text x="485" y="427" text-anchor="middle" fill="#fa709abb" font-size="11">角色: 运维工程师</text>
  <text x="485" y="442" text-anchor="middle" fill="#fa709a88" font-size="10">→ 输出初始立场 C</text>

  <!-- 回收箭头: Agent → AGI 收集 -->
  <rect x="700" y="310" width="150" height="50" rx="10" fill="#f5576c22" stroke="#f5576c88" stroke-width="1.2"/>
  <text x="775" y="332" text-anchor="middle" fill="#f5576c" font-size="12" font-weight="bold">🧠 AGI 收集</text>
  <text x="775" y="348" text-anchor="middle" fill="#f5576caa" font-size="10">round collect</text>

  <path d="M595 278 C640 278, 660 325, 700 325" fill="none" stroke="#4facfe88" stroke-width="1.2" stroke-dasharray="4,3" marker-end="url(#arrowBlue)"/>
  <path d="M595 348 C640 348, 660 335, 700 335" fill="none" stroke="#43e97b88" stroke-width="1.2" stroke-dasharray="4,3" marker-end="url(#arrowGreen)"/>
  <path d="M595 418 C640 418, 660 345, 700 345" fill="none" stroke="#fa709a88" stroke-width="1.2" stroke-dasharray="4,3"/>

  <!-- CLI 命令 -->
  <rect x="880" y="310" width="190" height="50" rx="8" fill="#ffffff08" stroke="#ffffff22" stroke-width="1"/>
  <text x="975" y="330" text-anchor="middle" fill="#888" font-size="10" font-family="monospace">round proj start</text>
  <text x="975" y="345" text-anchor="middle" fill="#888" font-size="10" font-family="monospace">round proj collect A "立场"</text>
  <text x="975" y="358" text-anchor="middle" fill="#888" font-size="10" font-family="monospace">round proj collect B "立场"</text>

  <!-- ═══════════ 阶段 2: 交叉评审 ═══════════ -->
  <rect x="30" y="470" width="1040" height="2" rx="1" fill="url(#phaseGrad)" opacity="0.4"/>
  <text x="50" y="492" fill="#a18cd1" font-size="13" font-weight="bold" letter-spacing="2">阶段 2 ─ 交叉评审 (Round 2: cross-review)</text>

  <!-- AGI 生成交叉评审提示 -->
  <rect x="60" y="508" width="180" height="50" rx="10" fill="#f5576c22" stroke="#f5576c88" stroke-width="1.2"/>
  <text x="150" y="528" text-anchor="middle" fill="#f5576c" font-size="12" font-weight="bold">🧠 AGI 生成提示</text>
  <text x="150" y="544" text-anchor="middle" fill="#f5576caa" font-size="10">round cross-review</text>

  <!-- 交叉评审：Agent A 看到 B+C -->
  <rect x="310" y="505" width="280" height="48" rx="10" fill="url(#agentAGrad)" opacity="0.10" stroke="#4facfe88" stroke-width="1"/>
  <text x="450" y="525" text-anchor="middle" fill="#4facfe" font-size="12" font-weight="bold">🔧 Agent A 评审</text>
  <text x="450" y="543" text-anchor="middle" fill="#4facfe99" font-size="10">看到 B + C 的立场 → 输出评审意见</text>

  <!-- 交叉评审：Agent B 看到 A+C -->
  <rect x="310" y="560" width="280" height="48" rx="10" fill="url(#agentBGrad)" opacity="0.10" stroke="#43e97b88" stroke-width="1"/>
  <text x="450" y="580" text-anchor="middle" fill="#43e97b" font-size="12" font-weight="bold">🧪 Agent B 评审</text>
  <text x="450" y="598" text-anchor="middle" fill="#43e97b99" font-size="10">看到 A + C 的立场 → 输出评审意见</text>

  <!-- 交叉评审：Agent C 看到 A+B -->
  <rect x="310" y="615" width="280" height="48" rx="10" fill="url(#agentCGrad)" opacity="0.10" stroke="#fa709a88" stroke-width="1"/>
  <text x="450" y="635" text-anchor="middle" fill="#fa709a" font-size="12" font-weight="bold">🚨 Agent C 评审</text>
  <text x="450" y="653" text-anchor="middle" fill="#fa709a99" font-size="10">看到 A + B 的立场 → 输出评审意见</text>

  <!-- 派发箭头 -->
  <path d="M240 528 C270 528, 290 525, 310 525" fill="none" stroke="#4facfe88" stroke-width="1.2" marker-end="url(#arrowBlue)"/>
  <path d="M240 533 C270 540, 290 575, 310 580" fill="none" stroke="#43e97b88" stroke-width="1.2" marker-end="url(#arrowGreen)"/>
  <path d="M240 538 C270 560, 290 630, 310 635" fill="none" stroke="#fa709a88" stroke-width="1.2"/>

  <!-- 交叉连线示意（谁看谁） -->
  <rect x="660" y="545" width="160" height="55" rx="10" fill="#ffffff08" stroke="#ffffff22" stroke-width="1"/>
  <text x="740" y="562" text-anchor="middle" fill="#ddd" font-size="11" font-weight="bold">🔀 交叉可见性</text>
  <text x="740" y="578" text-anchor="middle" fill="#4facfe" font-size="10">A → 看到 B, C 的立场</text>
  <text x="740" y="591" text-anchor="middle" fill="#43e97b" font-size="10">B → 看到 A, C 的立场</text>
  <!-- C line -->
  <text x="740" y="604" text-anchor="middle" fill="#fa709a" font-size="10" dy="-5">C → 看到 A, B 的立场</text>

  <!-- CLI -->
  <rect x="870" y="548" width="190" height="48" rx="8" fill="#ffffff08" stroke="#ffffff22" stroke-width="1"/>
  <text x="965" y="567" text-anchor="middle" fill="#888" font-size="10" font-family="monospace">round proj cross-review</text>
  <text x="965" y="582" text-anchor="middle" fill="#888" font-size="10" font-family="monospace">round proj collect A "评审"</text>
  <text x="965" y="596" text-anchor="middle" fill="#888" font-size="10" font-family="monospace">...</text>

  <!-- ═══════════ 阶段 3: 综合 ═══════════ -->
  <rect x="30" y="684" width="1040" height="2" rx="1" fill="url(#phaseGrad)" opacity="0.4"/>
  <text x="50" y="706" fill="#a18cd1" font-size="13" font-weight="bold" letter-spacing="2">阶段 3 ─ 综合决策 (Synthesize)</text>

  <!-- 综合输入 -->
  <rect x="100" y="722" width="340" height="80" rx="12" fill="#ffd20011" stroke="#ffd20066" stroke-width="1.2"/>
  <text x="270" y="744" text-anchor="middle" fill="#ffd200" font-size="13" font-weight="bold">📦 综合数据包</text>
  <text x="270" y="764" text-anchor="middle" fill="#ffd200bb" font-size="11">初始立场 (A, B, C) + 交叉评审 (A, B, C)</text>
  <text x="270" y="781" text-anchor="middle" fill="#ffd200aa" font-size="10">所有 Agent 的观点 + 反驳 + 修正意见</text>
  <text x="270" y="796" text-anchor="middle" fill="#88888888" font-size="10" font-family="monospace">round proj synthesize</text>

  <!-- 箭头 -->
  <line x1="440" y1="762" x2="530" y2="762" stroke="#ffd200" stroke-width="2" marker-end="url(#arrowOrange)"/>
  <text x="485" y="754" text-anchor="middle" fill="#ffd200aa" font-size="10">汇总</text>

  <!-- AGI 综合 -->
  <rect x="535" y="725" width="230" height="74" rx="12" fill="url(#synthGrad)" opacity="0.15" stroke="#ffd200" stroke-width="1.5" filter="url(#softShadow)"/>
  <text x="650" y="750" text-anchor="middle" fill="#ffd200" font-size="15" font-weight="bold" filter="url(#glow)">🧠 AGI 综合决策</text>
  <text x="650" y="770" text-anchor="middle" fill="#ffd200cc" font-size="11">提取最强论点 · 解决分歧</text>
  <text x="650" y="787" text-anchor="middle" fill="#ffd200aa" font-size="11">产出最终建议</text>

  <!-- 箭头: 综合 → 输出 -->
  <line x1="765" y1="762" x2="835" y2="762" stroke="#ffd200" stroke-width="2" marker-end="url(#arrowOrange)"/>

  <!-- 最终输出 -->
  <rect x="840" y="728" width="210" height="68" rx="12" fill="#ffffff11" stroke="#ffd20088" stroke-width="1.5" filter="url(#softShadow)"/>
  <text x="945" y="752" text-anchor="middle" fill="white" font-size="14" font-weight="bold">✅ 最终建议</text>
  <text x="945" y="771" text-anchor="middle" fill="#ccc" font-size="11">综合各方最优观点</text>
  <text x="945" y="788" text-anchor="middle" fill="#999" font-size="10">→ 反馈给用户</text>

  <!-- ═══════════ 底部: 数据流总览 ═══════════ -->
  <rect x="30" y="824" width="1040" height="2" rx="1" fill="url(#phaseGrad)" opacity="0.3"/>

  <!-- 时间线总览 -->
  <rect x="60" y="840" width="980" height="62" rx="10" fill="#ffffff06" stroke="#ffffff15" stroke-width="1"/>

  <!-- 流程节点 -->
  <circle cx="140" cy="865" r="14" fill="#6a11cb44" stroke="#a18cd1" stroke-width="1.5"/>
  <text x="140" y="869" text-anchor="middle" fill="#d4b0ff" font-size="10" font-weight="bold">初始</text>
  <text x="140" y="893" text-anchor="middle" fill="#888" font-size="9">init + add</text>

  <line x1="154" y1="865" x2="256" y2="865" stroke="#ffffff33" stroke-width="1.5"/>

  <circle cx="270" cy="865" r="14" fill="#4facfe33" stroke="#4facfe" stroke-width="1.5"/>
  <text x="270" y="869" text-anchor="middle" fill="#4facfe" font-size="10" font-weight="bold">R1</text>
  <text x="270" y="893" text-anchor="middle" fill="#888" font-size="9">立场收集</text>

  <line x1="284" y1="865" x2="386" y2="865" stroke="#ffffff33" stroke-width="1.5"/>

  <circle cx="400" cy="865" r="14" fill="#43e97b33" stroke="#43e97b" stroke-width="1.5"/>
  <text x="400" y="869" text-anchor="middle" fill="#43e97b" font-size="10" font-weight="bold">R2</text>
  <text x="400" y="893" text-anchor="middle" fill="#888" font-size="9">交叉评审</text>

  <line x1="414" y1="865" x2="516" y2="865" stroke="#ffffff33" stroke-width="1.5"/>

  <circle cx="530" cy="865" r="14" fill="#ffd20044" stroke="#ffd200" stroke-width="1.5"/>
  <text x="530" y="869" text-anchor="middle" fill="#ffd200" font-size="10" font-weight="bold">综合</text>
  <text x="530" y="893" text-anchor="middle" fill="#888" font-size="9">synthesize</text>

  <line x1="544" y1="865" x2="626" y2="865" stroke="#ffffff33" stroke-width="1.5"/>

  <circle cx="640" cy="865" r="14" fill="#ffffff22" stroke="#ffffff88" stroke-width="1.5"/>
  <text x="640" y="869" text-anchor="middle" fill="white" font-size="10" font-weight="bold">✓</text>
  <text x="640" y="893" text-anchor="middle" fill="#888" font-size="9">完成</text>

  <!-- 技术栈说明 -->
  <text x="820" y="856" text-anchor="middle" fill="#666" font-size="10">核心: task_manager.py (Python 3.12+)</text>
  <text x="820" y="872" text-anchor="middle" fill="#666" font-size="10">数据: JSON · 无外部依赖</text>
  <text x="820" y="888" text-anchor="middle" fill="#666" font-size="10">编排: AGI 中心化调度 (sessions_send)</text>

</svg>


path: docs/dev-team-agents-detail.svg
content: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 960" font-family="'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif">
  <defs>
    <linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" style="stop-color:#0a0a1a"/><stop offset="100%" style="stop-color:#1a1a2e"/>
    </linearGradient>
    <linearGradient id="codeG" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" style="stop-color:#3b82f6"/><stop offset="100%" style="stop-color:#06b6d4"/>
    </linearGradient>
    <linearGradient id="testG" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" style="stop-color:#8b5cf6"/><stop offset="100%" style="stop-color:#a78bfa"/>
    </linearGradient>
    <linearGradient id="docsG" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" style="stop-color:#10b981"/><stop offset="100%" style="stop-color:#34d399"/>
    </linearGradient>
    <linearGradient id="monG" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" style="stop-color:#f59e0b"/><stop offset="100%" style="stop-color:#fbbf24"/>
    </linearGradient>
    <linearGradient id="agiG" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" style="stop-color:#ef4444"/><stop offset="100%" style="stop-color:#f97316"/>
    </linearGradient>
    <filter id="gl"><feGaussianBlur stdDeviation="2" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
    <filter id="sh"><feDropShadow dx="1" dy="2" stdDeviation="3" flood-color="#00000055"/></filter>
    <marker id="aw" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#ffffff66"/></marker>
    <marker id="ac" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#3b82f6"/></marker>
    <marker id="at" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#8b5cf6"/></marker>
    <marker id="ad" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#10b981"/></marker>
    <marker id="am" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#f59e0b"/></marker>
  </defs>

  <!-- 背景 -->
  <rect width="1100" height="960" fill="url(#bg)" rx="14"/>

  <!-- 标题 -->
  <text x="550" y="40" text-anchor="middle" fill="white" font-size="22" font-weight="bold">🏢 开发团队 Agent 详情 — 四大专职 AI 成员</text>
  <text x="550" y="62" text-anchor="middle" fill="#64748b" font-size="12">AGI 中心调度 · 独立工作空间 · Agent 间通信 (agentToAgent)</text>
  <line x1="60" y1="76" x2="1040" y2="76" stroke="#ffffff12" stroke-width="1"/>

  <!-- ══════════════════════════════════════ -->
  <!-- 中央: AGI 调度中心                       -->
  <!-- ══════════════════════════════════════ -->
  <rect x="410" y="90" width="280" height="70" rx="14" fill="url(#agiG)" opacity="0.15" stroke="#ef4444" stroke-width="1.8" filter="url(#sh)"/>
  <text x="550" y="118" text-anchor="middle" fill="#ef4444" font-size="18" font-weight="bold" filter="url(#gl)">🧠 AGI 调度中心 (main)</text>
  <text x="550" y="140" text-anchor="middle" fill="#ef444499" font-size="11">Claude Opus 4.6 · 中心化编排 · 任务分发与结果收集</text>

  <!-- 连线: AGI → 4 agents -->
  <line x1="440" y1="160" x2="270" y2="195" stroke="#3b82f688" stroke-width="1.5" marker-end="url(#ac)" stroke-dasharray="5,3"/>
  <line x1="510" y1="160" x2="270" y2="530" stroke="#8b5cf688" stroke-width="1.5" marker-end="url(#at)" stroke-dasharray="5,3"/>
  <line x1="590" y1="160" x2="830" y2="195" stroke="#10b98188" stroke-width="1.5" marker-end="url(#ad)" stroke-dasharray="5,3"/>
  <line x1="660" y1="160" x2="830" y2="530" stroke="#f59e0b88" stroke-width="1.5" marker-end="url(#am)" stroke-dasharray="5,3"/>

  <!-- sessions_send 标签 -->
  <text x="340" y="178" text-anchor="middle" fill="#ffffff44" font-size="9">sessions_send</text>
  <text x="370" y="348" text-anchor="middle" fill="#ffffff44" font-size="9">sessions_send</text>
  <text x="730" y="178" text-anchor="middle" fill="#ffffff44" font-size="9">sessions_send</text>
  <text x="760" y="348" text-anchor="middle" fill="#ffffff44" font-size="9">sessions_send</text>

  <!-- ══════════════════════════════════════ -->
  <!-- Agent 1: Code Agent 🔧                  -->
  <!-- ══════════════════════════════════════ -->
  <rect x="40" y="200" width="460" height="290" rx="14" fill="#3b82f608" stroke="#3b82f644" stroke-width="1.2"/>

  <!-- 头部 -->
  <rect x="40" y="200" width="460" height="50" rx="14" fill="url(#codeG)" opacity="0.12"/>
  <text x="60" y="230" fill="#3b82f6" font-size="20" font-weight="bold" filter="url(#gl)">🔧 Code Agent</text>
  <rect x="210" y="214" width="80" height="22" rx="6" fill="#3b82f622"/>
  <text x="250" y="229" text-anchor="middle" fill="#3b82f6" font-size="11">code-agent</text>

  <!-- 模型标签 -->
  <rect x="310" y="214" width="178" height="22" rx="6" fill="#ef444422"/>
  <text x="399" y="229" text-anchor="middle" fill="#ef4444" font-size="10" font-weight="bold">Anthropic Claude Opus 4.6</text>

  <!-- 职责 -->
  <text x="60" y="272" fill="#94a3b8" font-size="12" font-weight="bold">核心职责</text>
  <text x="60" y="292" fill="#cbd5e1" font-size="11">• 代码审查 — 质量分析、命名规范、设计模式</text>
  <text x="60" y="310" fill="#cbd5e1" font-size="11">• Bug 分析 — 定位根因、给出修复方案和示例代码</text>
  <text x="60" y="328" fill="#cbd5e1" font-size="11">• 重构建议 — 识别代码异味、提供改进路径</text>
  <text x="60" y="346" fill="#cbd5e1" font-size="11">• 代码开发 — 直接使用 read/write/edit/exec 开发</text>
  <text x="60" y="364" fill="#cbd5e1" font-size="11">• PR 审查 / CI 状态 — 通过 gh CLI 查询</text>

  <!-- 工具 & Skill -->
  <text x="60" y="392" fill="#94a3b8" font-size="12" font-weight="bold">工具 &amp; Skill</text>
  <rect x="60" y="400" width="50" height="18" rx="4" fill="#3b82f622"/>
  <text x="85" y="413" text-anchor="middle" fill="#3b82f6" font-size="9">GitHub</text>
  <rect x="118" y="400" width="36" height="18" rx="4" fill="#3b82f622"/>
  <text x="136" y="413" text-anchor="middle" fill="#3b82f6" font-size="9">read</text>
  <rect x="162" y="400" width="36" height="18" rx="4" fill="#3b82f622"/>
  <text x="180" y="413" text-anchor="middle" fill="#3b82f6" font-size="9">write</text>
  <rect x="206" y="400" width="30" height="18" rx="4" fill="#3b82f622"/>
  <text x="221" y="413" text-anchor="middle" fill="#3b82f6" font-size="9">edit</text>
  <rect x="244" y="400" width="36" height="18" rx="4" fill="#3b82f622"/>
  <text x="262" y="413" text-anchor="middle" fill="#3b82f6" font-size="9">exec</text>
  <rect x="288" y="400" width="50" height="18" rx="4" fill="#3b82f622"/>
  <text x="313" y="413" text-anchor="middle" fill="#3b82f6" font-size="9">gh CLI</text>

  <!-- 工作空间 -->
  <text x="60" y="442" fill="#94a3b8" font-size="12" font-weight="bold">工作空间</text>
  <rect x="60" y="450" width="260" height="18" rx="4" fill="#ffffff08"/>
  <text x="68" y="463" fill="#64748b" font-size="10" font-family="monospace">/home/ubuntu/clawd-code-agent</text>

  <!-- 输出标准 -->
  <text x="350" y="272" fill="#94a3b8" font-size="12" font-weight="bold">输出标准</text>
  <text x="350" y="292" fill="#f87171" font-size="11">🔴 Critical</text>
  <text x="350" y="310" fill="#fbbf24" font-size="11">🟡 Warning</text>
  <text x="350" y="328" fill="#60a5fa" font-size="11">💡 Info</text>

  <!-- ══════════════════════════════════════ -->
  <!-- Agent 2: Test Agent 🧪                  -->
  <!-- ══════════════════════════════════════ -->
  <rect x="40" y="510" width="460" height="290" rx="14" fill="#8b5cf608" stroke="#8b5cf644" stroke-width="1.2"/>

  <rect x="40" y="510" width="460" height="50" rx="14" fill="url(#testG)" opacity="0.12"/>
  <text x="60" y="540" fill="#8b5cf6" font-size="20" font-weight="bold" filter="url(#gl)">🧪 Test Agent</text>
  <rect x="200" y="524" width="78" height="22" rx="6" fill="#8b5cf622"/>
  <text x="239" y="539" text-anchor="middle" fill="#8b5cf6" font-size="11">test-agent</text>

  <rect x="296" y="524" width="192" height="22" rx="6" fill="#10b98122"/>
  <text x="392" y="539" text-anchor="middle" fill="#10b981" font-size="10" font-weight="bold">Gemini 3 Pro High (免费)</text>

  <text x="60" y="582" fill="#94a3b8" font-size="12" font-weight="bold">核心职责</text>
  <text x="60" y="602" fill="#cbd5e1" font-size="11">• 运行测试 — 通过 Claude Code CLI 执行单元/集成测试</text>
  <text x="60" y="620" fill="#cbd5e1" font-size="11">• 覆盖率分析 — 检查测试覆盖率、找未覆盖代码</text>
  <text x="60" y="638" fill="#cbd5e1" font-size="11">• 测试用例生成 — 边界值、异常路径、回归测试</text>
  <text x="60" y="656" fill="#cbd5e1" font-size="11">• CI 检查 — 使用 gh run 查看 GitHub Actions 结果</text>
  <text x="60" y="674" fill="#cbd5e1" font-size="11">• 回归测试 — 检查新改动是否破坏现有功能</text>

  <text x="60" y="702" fill="#94a3b8" font-size="12" font-weight="bold">工具 &amp; Skill</text>
  <rect x="60" y="710" width="80" height="18" rx="4" fill="#8b5cf622"/>
  <text x="100" y="723" text-anchor="middle" fill="#8b5cf6" font-size="9">Claude Code</text>
  <rect x="148" y="710" width="50" height="18" rx="4" fill="#8b5cf622"/>
  <text x="173" y="723" text-anchor="middle" fill="#8b5cf6" font-size="9">GitHub</text>
  <rect x="206" y="710" width="46" height="18" rx="4" fill="#8b5cf622"/>
  <text x="229" y="723" text-anchor="middle" fill="#8b5cf6" font-size="9">pytest</text>
  <rect x="260" y="710" width="32" height="18" rx="4" fill="#8b5cf622"/>
  <text x="276" y="723" text-anchor="middle" fill="#8b5cf6" font-size="9">jest</text>
  <rect x="300" y="710" width="50" height="18" rx="4" fill="#8b5cf622"/>
  <text x="325" y="723" text-anchor="middle" fill="#8b5cf6" font-size="9">go test</text>

  <text x="60" y="752" fill="#94a3b8" font-size="12" font-weight="bold">工作空间</text>
  <rect x="60" y="760" width="260" height="18" rx="4" fill="#ffffff08"/>
  <text x="68" y="773" fill="#64748b" font-size="10" font-family="monospace">/home/ubuntu/clawd-test-agent</text>

  <!-- 测试报告格式 -->
  <text x="350" y="582" fill="#94a3b8" font-size="12" font-weight="bold">报告格式</text>
  <text x="350" y="602" fill="#22c55e" font-size="11">✅ 通过: N</text>
  <text x="350" y="620" fill="#ef4444" font-size="11">❌ 失败: N</text>
  <text x="350" y="638" fill="#94a3b8" font-size="11">⏭️ 跳过: N</text>
  <text x="350" y="656" fill="#60a5fa" font-size="11">📊 覆盖率: XX%</text>

  <text x="350" y="686" fill="#94a3b8" font-size="12" font-weight="bold">用例生成原则</text>
  <text x="350" y="706" fill="#a5b4fc" font-size="10">边界值 · 异常路径</text>
  <text x="350" y="722" fill="#a5b4fc" font-size="10">正常路径 · 回归防护</text>

  <!-- ══════════════════════════════════════ -->
  <!-- Agent 3: Docs Agent 📝                  -->
  <!-- ══════════════════════════════════════ -->
  <rect x="600" y="200" width="460" height="290" rx="14" fill="#10b98108" stroke="#10b98144" stroke-width="1.2"/>

  <rect x="600" y="200" width="460" height="50" rx="14" fill="url(#docsG)" opacity="0.12"/>
  <text x="620" y="230" fill="#10b981" font-size="20" font-weight="bold" filter="url(#gl)">📝 Docs Agent</text>
  <text x="773" y="229" fill="#10b981" font-size="11">docs-agent</text>

  <rect x="848" y="214" width="200" height="22" rx="6" fill="#8b5cf622"/>
  <text x="948" y="229" text-anchor="middle" fill="#8b5cf6" font-size="10" font-weight="bold">Claude Sonnet 4.5 Thinking</text>

  <text x="620" y="272" fill="#94a3b8" font-size="12" font-weight="bold">核心职责</text>
  <text x="620" y="292" fill="#cbd5e1" font-size="11">• API 文档 — 根据代码自动生成接口文档</text>
  <text x="620" y="310" fill="#cbd5e1" font-size="11">• README 生成 — 简介、特性、快速开始、配置说明</text>
  <text x="620" y="328" fill="#cbd5e1" font-size="11">• CHANGELOG 维护 — Keep a Changelog 规范</text>
  <text x="620" y="346" fill="#cbd5e1" font-size="11">• 代码注释 — 函数/类文档字符串</text>
  <text x="620" y="364" fill="#cbd5e1" font-size="11">• 使用指南 — 操作手册和教程</text>

  <text x="620" y="392" fill="#94a3b8" font-size="12" font-weight="bold">工具 &amp; Skill</text>
  <rect x="620" y="400" width="50" height="18" rx="4" fill="#10b98122"/>
  <text x="645" y="413" text-anchor="middle" fill="#10b981" font-size="9">GitHub</text>
  <rect x="678" y="400" width="50" height="18" rx="4" fill="#10b98122"/>
  <text x="703" y="413" text-anchor="middle" fill="#10b981" font-size="9">gh CLI</text>
  <rect x="736" y="400" width="50" height="18" rx="4" fill="#10b98122"/>
  <text x="761" y="413" text-anchor="middle" fill="#10b981" font-size="9">git log</text>
  <rect x="794" y="400" width="36" height="18" rx="4" fill="#10b98122"/>
  <text x="812" y="413" text-anchor="middle" fill="#10b981" font-size="9">read</text>
  <rect x="838" y="400" width="36" height="18" rx="4" fill="#10b98122"/>
  <text x="856" y="413" text-anchor="middle" fill="#10b981" font-size="9">write</text>

  <text x="620" y="442" fill="#94a3b8" font-size="12" font-weight="bold">工作空间</text>
  <rect x="620" y="450" width="260" height="18" rx="4" fill="#ffffff08"/>
  <text x="628" y="463" fill="#64748b" font-size="10" font-family="monospace">/home/ubuntu/clawd-docs-agent</text>

  <!-- 文档标准 -->
  <text x="900" y="272" fill="#94a3b8" font-size="12" font-weight="bold">文档标准</text>
  <text x="900" y="292" fill="#6ee7b7" font-size="10">Added · Changed</text>
  <text x="900" y="308" fill="#6ee7b7" font-size="10">Fixed · Security</text>
  <text x="900" y="324" fill="#6ee7b7" font-size="10">Removed · Deprecated</text>

  <text x="900" y="352" fill="#94a3b8" font-size="12" font-weight="bold">语言规则</text>
  <text x="900" y="372" fill="#6ee7b7" font-size="10">API 文档 → 英文</text>
  <text x="900" y="388" fill="#6ee7b7" font-size="10">用户指南 → 中文</text>

  <!-- ══════════════════════════════════════ -->
  <!-- Agent 4: Monitor Bot 🚨                 -->
  <!-- ══════════════════════════════════════ -->
  <rect x="600" y="510" width="460" height="290" rx="14" fill="#f59e0b08" stroke="#f59e0b44" stroke-width="1.2"/>

  <rect x="600" y="510" width="460" height="50" rx="14" fill="url(#monG)" opacity="0.12"/>
  <text x="620" y="540" fill="#f59e0b" font-size="20" font-weight="bold" filter="url(#gl)">🚨 Monitor Bot</text>
  <text x="786" y="539" fill="#f59e0b" font-size="11">monitor-bot</text>

  <rect x="875" y="524" width="173" height="22" rx="6" fill="#10b98122"/>
  <text x="961" y="539" text-anchor="middle" fill="#10b981" font-size="10" font-weight="bold">Gemini 3 Pro High (免费)</text>

  <text x="620" y="582" fill="#94a3b8" font-size="12" font-weight="bold">核心职责</text>
  <text x="620" y="602" fill="#cbd5e1" font-size="11">• 服务器监控 — CPU、内存、磁盘、网络状态</text>
  <text x="620" y="620" fill="#cbd5e1" font-size="11">• API 健康检查 — 端点可用性、响应时间</text>
  <text x="620" y="638" fill="#cbd5e1" font-size="11">• 日志分析 — 错误日志、异常模式识别</text>
  <text x="620" y="656" fill="#cbd5e1" font-size="11">• 安全审计 — 使用 healthcheck skill 检查</text>
  <text x="620" y="674" fill="#cbd5e1" font-size="11">• 告警通知 — 发现异常立即报告</text>

  <text x="620" y="702" fill="#94a3b8" font-size="12" font-weight="bold">工具 &amp; Skill</text>
  <rect x="620" y="710" width="80" height="18" rx="4" fill="#f59e0b22"/>
  <text x="660" y="723" text-anchor="middle" fill="#f59e0b" font-size="9">healthcheck</text>
  <rect x="708" y="710" width="30" height="18" rx="4" fill="#f59e0b22"/>
  <text x="723" y="723" text-anchor="middle" fill="#f59e0b" font-size="9">top</text>
  <rect x="746" y="710" width="26" height="18" rx="4" fill="#f59e0b22"/>
  <text x="759" y="723" text-anchor="middle" fill="#f59e0b" font-size="9">df</text>
  <rect x="780" y="710" width="34" height="18" rx="4" fill="#f59e0b22"/>
  <text x="797" y="723" text-anchor="middle" fill="#f59e0b" font-size="9">curl</text>
  <rect x="822" y="710" width="24" height="18" rx="4" fill="#f59e0b22"/>
  <text x="834" y="723" text-anchor="middle" fill="#f59e0b" font-size="9">ss</text>
  <rect x="854" y="710" width="60" height="18" rx="4" fill="#f59e0b22"/>
  <text x="884" y="723" text-anchor="middle" fill="#f59e0b" font-size="9">journalctl</text>

  <text x="620" y="752" fill="#94a3b8" font-size="12" font-weight="bold">工作空间</text>
  <rect x="620" y="760" width="270" height="18" rx="4" fill="#ffffff08"/>
  <text x="628" y="773" fill="#64748b" font-size="10" font-family="monospace">/home/ubuntu/clawd-monitor-bot</text>

  <!-- 告警级别 -->
  <text x="900" y="582" fill="#94a3b8" font-size="12" font-weight="bold">告警级别</text>
  <rect x="900" y="590" width="120" height="22" rx="4" fill="#ef444418"/>
  <text x="960" y="605" text-anchor="middle" fill="#ef4444" font-size="11">🔴 Critical</text>
  <text x="900" y="624" fill="#ef444499" font-size="9">服务宕机 · 磁盘满 · 内存溢出</text>

  <rect x="900" y="636" width="120" height="22" rx="4" fill="#fbbf2418"/>
  <text x="960" y="651" text-anchor="middle" fill="#fbbf24" font-size="11">🟡 Warning</text>
  <text x="900" y="670" fill="#fbbf2499" font-size="9">CPU&gt;80% · 磁盘&gt;85% · 响应&gt;5s</text>

  <rect x="900" y="682" width="120" height="22" rx="4" fill="#22c55e18"/>
  <text x="960" y="697" text-anchor="middle" fill="#22c55e" font-size="11">🟢 Normal</text>
  <text x="900" y="716" fill="#22c55e99" font-size="9">一切正常</text>

  <!-- ══════════════════════════════════════ -->
  <!-- 底部: 架构概览                            -->
  <!-- ══════════════════════════════════════ -->
  <line x1="60" y1="820" x2="1040" y2="820" stroke="#ffffff12" stroke-width="1"/>

  <!-- 通信方式 -->
  <rect x="60" y="838" width="310" height="105" rx="10" fill="#ffffff06" stroke="#ffffff15" stroke-width="1"/>
  <text x="80" y="862" fill="#94a3b8" font-size="13" font-weight="bold">📡 通信架构</text>
  <text x="80" y="884" fill="#cbd5e1" font-size="11">• AGI → Agent: sessions_send 派发任务</text>
  <text x="80" y="902" fill="#cbd5e1" font-size="11">• Agent → AGI: 回复自动返回调度中心</text>
  <text x="80" y="920" fill="#cbd5e1" font-size="11">• Agent ↔ Agent: agentToAgent 直接通信</text>
  <text x="80" y="938" fill="#64748b" font-size="10">所有 Agent 均在 allow 列表中，可互相调用</text>

  <!-- 模型策略 -->
  <rect x="400" y="838" width="310" height="105" rx="10" fill="#ffffff06" stroke="#ffffff15" stroke-width="1"/>
  <text x="420" y="862" fill="#94a3b8" font-size="13" font-weight="bold">🤖 模型策略</text>

  <rect x="420" y="872" width="10" height="10" rx="2" fill="#ef4444"/>
  <text x="438" y="882" fill="#cbd5e1" font-size="11">Code Agent → Claude Opus 4.6 (代码重)</text>

  <rect x="420" y="892" width="10" height="10" rx="2" fill="#8b5cf6"/>
  <text x="438" y="902" fill="#cbd5e1" font-size="11">Docs Agent → Sonnet 4.5 Thinking (写作)</text>

  <rect x="420" y="912" width="10" height="10" rx="2" fill="#10b981"/>
  <text x="438" y="922" fill="#cbd5e1" font-size="11">Test / Monitor → Gemini 3 Pro (快速+免费)</text>

  <!-- 隔离特性 -->
  <rect x="740" y="838" width="310" height="105" rx="10" fill="#ffffff06" stroke="#ffffff15" stroke-width="1"/>
  <text x="760" y="862" fill="#94a3b8" font-size="13" font-weight="bold">🔒 隔离特性</text>
  <text x="760" y="884" fill="#cbd5e1" font-size="11">• 独立工作空间 — 文件互不干扰</text>
  <text x="760" y="902" fill="#cbd5e1" font-size="11">• 独立 Session — 记忆和上下文隔离</text>
  <text x="760" y="920" fill="#cbd5e1" font-size="11">• 独立 Cron — 只响应自身 agentId 的任务</text>
  <text x="760" y="938" fill="#cbd5e1" font-size="11">• 独立 Skill — 各自加载专属 skill 文件</text>

</svg>


path: docs/GAP_ANALYSIS.md
content: # Gap Analysis: `team-tasks` vs Official Claude Code Agent Teams

Date: 2026-02-08

## Scope
Compared these files:
- `AGENT_TEAMS_OFFICIAL_DOCS.md`
- `SKILL.md`
- `scripts/task_manager.py`
- `SPEC.md`

This analysis compares our current implementation to official Claude Code Agent Teams behavior, not just to our internal spec.

## Summary Table
| Capability | Official Claude Code Agent Teams | Current `team-tasks` | Gap | Assessment |
|---|---|---|---|---|
| Team model (lead + teammates as managed instances) | Built-in team lead + spawned teammates with independent contexts | No native team object or spawn lifecycle in `task_manager.py`; project JSON tracks tasks/debaters only | High | We have a task tracker, not a full team runtime |
| Direct teammate communication (`message`, `broadcast`, mailbox) | Teammates can message each other directly | `SKILL.md` explicitly states centralized orchestration: "agents never talk to each other directly" | High | Core architectural mismatch |
| Shared task list with self-claim | Teammates self-claim or are assigned; shared list for all agents | Lead/dispatcher updates statuses; no claim command for teammates | High | Centralized control only |
| Race-safe task claiming (file locking) | Explicit locking for claim operations | No locking primitives or claim workflow in script | High | Concurrency safety missing for multi-writer use |
| Dependency handling / unblock | Dependencies and automatic unblock | Implemented via DAG `dependsOn` + `ready` + unblocked notifications (`compute_ready_tasks`, `cmd_update`) | Low | Strong match |
| Task state lifecycle | `pending`, `in progress`, `completed` | `pending`, `in-progress`, `done`, plus `failed`, `skipped` | Low | Equivalent + extensions |
| Delegate mode (lead restricted to orchestration tools) | Supported | Not implemented | High | No guardrails for lead behavior |
| Plan approval workflow | Teammates can be forced to submit plans for approval | Not implemented | High | Missing governance loop |
| Quality gates / hooks (`TeammateIdle`, `TaskCompleted`) | Supported with blocking feedback | Not implemented | High | Missing policy enforcement points |
| Display modes (in-process, split panes) | Supported | Not implemented in this tool | Medium | Mostly UX/runtime gap |
| Data model for teams | Team config + members persisted in `~/.claude/teams` and task list in `~/.claude/tasks` | Project JSON only (`/home/ubuntu/clawd/data/team-tasks/*.json`) | Medium | Simpler model; lacks team/member metadata |
| Project context propagation | Teammates load same project context automatically | Workspace path is manually stored and surfaced via `--workspace`, `next`, `ready` | Medium | Useful, but not full context/session semantics |
| Debate / competing hypotheses workflow | Documented as use case pattern | Dedicated `debate` mode with `add-debater` + `round` actions is implemented | Positive delta | Feature extension beyond official baseline tooling |
| Cross-review prompt generation | Not a dedicated first-class command in official docs | Implemented in `round <project> cross-review` | Positive delta | Good specialized workflow |

## Evidence Highlights
- Official direct teammate messaging and mailbox features: `AGENT_TEAMS_OFFICIAL_DOCS.md:35`, `AGENT_TEAMS_OFFICIAL_DOCS.md:36`, `AGENT_TEAMS_OFFICIAL_DOCS.md:37`.
- Official claim locking and dependency unblock semantics: `AGENT_TEAMS_OFFICIAL_DOCS.md:45`, `AGENT_TEAMS_OFFICIAL_DOCS.md:46`.
- Official delegate/plan approval/hooks/display modes: `AGENT_TEAMS_OFFICIAL_DOCS.md:48`, `AGENT_TEAMS_OFFICIAL_DOCS.md:53`, `AGENT_TEAMS_OFFICIAL_DOCS.md:60`, `AGENT_TEAMS_OFFICIAL_DOCS.md:63`.
- Current architecture is centralized by design: `SKILL.md:10`, `SKILL.md:11`.
- Current implementation does include debate + workspace enhancements from spec: `scripts/task_manager.py:992`, `scripts/task_manager.py:995`, `scripts/task_manager.py:1007`, `scripts/task_manager.py:1013`, `scripts/task_manager.py:746`, `scripts/task_manager.py:708`.
- Internal spec goals for debate/workspace: `SPEC.md:16`, `SPEC.md:57`.

## Honest Assessment
`team-tasks` is a solid JSON-based orchestration layer for linear and DAG pipelines, and now includes a useful debate workflow. It is effective for AGI-centric dispatch where one coordinator drives all state transitions.

It is **not yet close to full parity** with official Claude Code Agent Teams runtime semantics. The biggest gaps are architectural: no teammate-to-teammate mailbox, no self-claim/locking model, and no delegate/approval/hooks governance features. In practical terms, this behaves more like a workflow/state manager than a true multi-agent team runtime.

## Priority Gaps to Close (if parity is the goal)
1. Add a first-class team/member runtime model with explicit lead + teammate identity and lifecycle.
2. Implement mailbox primitives (`message`, `broadcast`, inbox/outbox) so teammates can coordinate directly.
3. Add claim/release semantics with lock-safe updates to prevent race conditions.
4. Add governance features: delegate mode restrictions, plan approval state machine, and completion/idle hooks.
5. Align docs (`SKILL.md`) with actual capabilities (`debate`, `workspace`) to remove drift.


path: docs/three-modes-overview.svg
content: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 780" font-family="'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif">
  <defs>
    <linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" style="stop-color:#0a0a1a"/>
      <stop offset="100%" style="stop-color:#1a1a2e"/>
    </linearGradient>
    <marker id="aw" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
      <polygon points="0 0,8 3,0 6" fill="#fff9"/>
    </marker>
    <marker id="ab" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
      <polygon points="0 0,8 3,0 6" fill="#60a5fa"/>
    </marker>
    <marker id="ag" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
      <polygon points="0 0,8 3,0 6" fill="#34d399"/>
    </marker>
    <marker id="ap" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
      <polygon points="0 0,8 3,0 6" fill="#c084fc"/>
    </marker>
    <marker id="ao" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
      <polygon points="0 0,8 3,0 6" fill="#fbbf24"/>
    </marker>
    <marker id="ar" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
      <polygon points="0 0,8 3,0 6" fill="#f87171"/>
    </marker>
    <filter id="gl">
      <feGaussianBlur stdDeviation="2.5" result="b"/>
      <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
    </filter>
    <filter id="sh">
      <feDropShadow dx="1" dy="2" stdDeviation="3" flood-color="#00000055"/>
    </filter>
  </defs>

  <!-- 背景 -->
  <rect width="1100" height="780" fill="url(#bg)" rx="14"/>

  <!-- 大标题 -->
  <text x="550" y="42" text-anchor="middle" fill="white" font-size="22" font-weight="bold">🏗️  Team Tasks — 三种多 Agent 协作模式</text>
  <line x1="60" y1="58" x2="1040" y2="58" stroke="#ffffff18" stroke-width="1"/>

  <!-- ════════════════════════════════════════════ -->
  <!-- Mode A: Linear 线性流水线                      -->
  <!-- ════════════════════════════════════════════ -->
  <rect x="40" y="72" width="1020" height="195" rx="12" fill="#60a5fa08" stroke="#60a5fa33" stroke-width="1"/>
  <rect x="40" y="72" width="260" height="30" rx="12" fill="#60a5fa22"/>
  <text x="58" y="92" fill="#60a5fa" font-size="15" font-weight="bold">Mode A · 线性流水线 (Linear)</text>

  <!-- 说明 -->
  <text x="58" y="122" fill="#94a3b8" font-size="12">Agent 按固定顺序逐个执行，前一个完成后自动推进到下一个</text>

  <!-- 流水线节点 -->
  <!-- AGI -->
  <rect x="70" y="142" width="100" height="46" rx="8" fill="#f4364c18" stroke="#f4364c88" stroke-width="1.2"/>
  <text x="120" y="163" text-anchor="middle" fill="#f87171" font-size="12" font-weight="bold">🧠 AGI</text>
  <text x="120" y="178" text-anchor="middle" fill="#f8717199" font-size="9">调度中心</text>

  <line x1="170" y1="165" x2="218" y2="165" stroke="#fff6" stroke-width="1.3" marker-end="url(#aw)"/>

  <!-- Stage 1 -->
  <rect x="224" y="140" width="150" height="50" rx="10" fill="#60a5fa12" stroke="#60a5fa" stroke-width="1.3" filter="url(#sh)"/>
  <text x="299" y="161" text-anchor="middle" fill="#60a5fa" font-size="13" font-weight="bold">🔧 code-agent</text>
  <text x="299" y="178" text-anchor="middle" fill="#60a5fa99" font-size="10">① 编写代码</text>

  <line x1="374" y1="165" x2="422" y2="165" stroke="#60a5fa" stroke-width="1.8" marker-end="url(#ab)"/>
  <text x="398" y="157" text-anchor="middle" fill="#60a5fa99" font-size="9">done</text>

  <!-- Stage 2 -->
  <rect x="428" y="140" width="150" height="50" rx="10" fill="#34d39912" stroke="#34d399" stroke-width="1.3" filter="url(#sh)"/>
  <text x="503" y="161" text-anchor="middle" fill="#34d399" font-size="13" font-weight="bold">🧪 test-agent</text>
  <text x="503" y="178" text-anchor="middle" fill="#34d39999" font-size="10">② 编写测试</text>

  <line x1="578" y1="165" x2="626" y2="165" stroke="#34d399" stroke-width="1.8" marker-end="url(#ag)"/>
  <text x="602" y="157" text-anchor="middle" fill="#34d39999" font-size="9">done</text>

  <!-- Stage 3 -->
  <rect x="632" y="140" width="150" height="50" rx="10" fill="#c084fc12" stroke="#c084fc" stroke-width="1.3" filter="url(#sh)"/>
  <text x="707" y="161" text-anchor="middle" fill="#c084fc" font-size="13" font-weight="bold">📝 docs-agent</text>
  <text x="707" y="178" text-anchor="middle" fill="#c084fc99" font-size="10">③ 编写文档</text>

  <line x1="782" y1="165" x2="830" y2="165" stroke="#c084fc" stroke-width="1.8" marker-end="url(#ap)"/>
  <text x="806" y="157" text-anchor="middle" fill="#c084fc99" font-size="9">done</text>

  <!-- Stage 4 -->
  <rect x="836" y="140" width="150" height="50" rx="10" fill="#fbbf2412" stroke="#fbbf24" stroke-width="1.3" filter="url(#sh)"/>
  <text x="911" y="161" text-anchor="middle" fill="#fbbf24" font-size="13" font-weight="bold">🚨 monitor-bot</text>
  <text x="911" y="178" text-anchor="middle" fill="#fbbf2499" font-size="10">④ 安全审查</text>

  <!-- 完成标志 -->
  <line x1="986" y1="165" x2="1020" y2="165" stroke="#fbbf24" stroke-width="1.8" marker-end="url(#ao)"/>
  <text x="1035" y="170" text-anchor="middle" fill="#22c55e" font-size="16">✅</text>

  <!-- 关键特征标签 -->
  <rect x="70" y="204" width="70" height="20" rx="4" fill="#60a5fa22"/>
  <text x="105" y="218" text-anchor="middle" fill="#60a5fa" font-size="10">顺序执行</text>
  <rect x="150" y="204" width="70" height="20" rx="4" fill="#60a5fa22"/>
  <text x="185" y="218" text-anchor="middle" fill="#60a5fa" font-size="10">自动推进</text>
  <rect x="230" y="204" width="90" height="20" rx="4" fill="#60a5fa22"/>
  <text x="275" y="218" text-anchor="middle" fill="#60a5fa" font-size="10">一次一个 Agent</text>

  <!-- 适用场景 -->
  <text x="580" y="218" fill="#64748b" font-size="10">适用: Bug 修复、简单功能、标准化流程</text>

  <!-- ════════════════════════════════════════════ -->
  <!-- Mode B: DAG 依赖图                            -->
  <!-- ════════════════════════════════════════════ -->
  <rect x="40" y="280" width="1020" height="220" rx="12" fill="#34d39908" stroke="#34d39933" stroke-width="1"/>
  <rect x="40" y="280" width="300" height="30" rx="12" fill="#34d39922"/>
  <text x="58" y="300" fill="#34d399" font-size="15" font-weight="bold">Mode B · 依赖图并行 (DAG)</text>

  <text x="58" y="328" fill="#94a3b8" font-size="12">任务声明依赖关系，依赖满足后可并行派发多个 Agent</text>

  <!-- AGI -->
  <rect x="70" y="348" width="90" height="42" rx="8" fill="#f4364c18" stroke="#f4364c88" stroke-width="1.2"/>
  <text x="115" y="366" text-anchor="middle" fill="#f87171" font-size="12" font-weight="bold">🧠 AGI</text>
  <text x="115" y="381" text-anchor="middle" fill="#f8717199" font-size="9">调度中心</text>

  <!-- Wave 1: 并行 -->
  <line x1="160" y1="362" x2="208" y2="352" stroke="#fff6" stroke-width="1.2" marker-end="url(#aw)"/>
  <line x1="160" y1="376" x2="208" y2="386" stroke="#fff6" stroke-width="1.2" marker-end="url(#aw)"/>

  <rect x="214" y="338" width="120" height="36" rx="8" fill="#c084fc12" stroke="#c084fc" stroke-width="1.2"/>
  <text x="274" y="357" text-anchor="middle" fill="#c084fc" font-size="11" font-weight="bold">📝 设计 API</text>
  <text x="274" y="370" text-anchor="middle" fill="#c084fc88" font-size="9">docs-agent</text>

  <rect x="214" y="382" width="120" height="36" rx="8" fill="#60a5fa12" stroke="#60a5fa" stroke-width="1.2"/>
  <text x="274" y="401" text-anchor="middle" fill="#60a5fa" font-size="11" font-weight="bold">🔧 搭建骨架</text>
  <text x="274" y="414" text-anchor="middle" fill="#60a5fa88" font-size="9">code-agent</text>

  <!-- 并行标记 -->
  <rect x="190" y="344" width="16" height="68" rx="3" fill="none" stroke="#22c55e55" stroke-width="1.2" stroke-dasharray="3,2"/>
  <text x="198" y="383" text-anchor="middle" fill="#22c55e" font-size="8" transform="rotate(-90 198 383)">并行</text>

  <!-- Wave 2: 依赖汇聚 -->
  <line x1="334" y1="356" x2="398" y2="370" stroke="#c084fc88" stroke-width="1.2" marker-end="url(#ap)"/>
  <line x1="334" y1="400" x2="398" y2="376" stroke="#60a5fa88" stroke-width="1.2" marker-end="url(#ab)"/>

  <!-- 同时 设计→测试 分支 -->
  <line x1="334" y1="356" x2="398" y2="430" stroke="#c084fc55" stroke-width="1" stroke-dasharray="4,3"/>

  <rect x="404" y="354" width="130" height="36" rx="8" fill="#60a5fa12" stroke="#60a5fa" stroke-width="1.2"/>
  <text x="469" y="373" text-anchor="middle" fill="#60a5fa" font-size="11" font-weight="bold">🔧 实现功能</text>
  <text x="469" y="386" text-anchor="middle" fill="#60a5fa88" font-size="9">code-agent</text>

  <rect x="404" y="416" width="130" height="36" rx="8" fill="#34d39912" stroke="#34d399" stroke-width="1.2"/>
  <text x="469" y="435" text-anchor="middle" fill="#34d399" font-size="11" font-weight="bold">🧪 编写测试</text>
  <text x="469" y="448" text-anchor="middle" fill="#34d39988" font-size="9">test-agent</text>

  <!-- 并行标记 -->
  <rect x="384" y="360" width="16" height="86" rx="3" fill="none" stroke="#22c55e55" stroke-width="1.2" stroke-dasharray="3,2"/>
  <text x="392" y="408" text-anchor="middle" fill="#22c55e" font-size="8" transform="rotate(-90 392 408)">并行</text>

  <!-- Wave 3: 汇聚 -->
  <line x1="534" y1="372" x2="598" y2="380" stroke="#60a5fa88" stroke-width="1.2" marker-end="url(#ab)"/>
  <line x1="534" y1="434" x2="598" y2="388" stroke="#34d39988" stroke-width="1.2" marker-end="url(#ag)"/>
  <line x1="534" y1="372" x2="598" y2="440" stroke="#60a5fa55" stroke-width="1" stroke-dasharray="4,3"/>

  <rect x="604" y="364" width="130" height="36" rx="8" fill="#34d39912" stroke="#34d399" stroke-width="1.2"/>
  <text x="669" y="383" text-anchor="middle" fill="#34d399" font-size="11" font-weight="bold">🧪 运行测试</text>
  <text x="669" y="396" text-anchor="middle" fill="#34d39988" font-size="9">test-agent</text>

  <rect x="604" y="424" width="130" height="36" rx="8" fill="#c084fc12" stroke="#c084fc" stroke-width="1.2"/>
  <text x="669" y="443" text-anchor="middle" fill="#c084fc" font-size="11" font-weight="bold">📝 编写文档</text>
  <text x="669" y="456" text-anchor="middle" fill="#c084fc88" font-size="9">docs-agent</text>

  <!-- 并行标记 -->
  <rect x="584" y="370" width="16" height="84" rx="3" fill="none" stroke="#22c55e55" stroke-width="1.2" stroke-dasharray="3,2"/>
  <text x="592" y="417" text-anchor="middle" fill="#22c55e" font-size="8" transform="rotate(-90 592 417)">并行</text>

  <!-- Wave 4: 最终 -->
  <line x1="734" y1="382" x2="808" y2="404" stroke="#34d39988" stroke-width="1.2" marker-end="url(#ag)"/>
  <line x1="734" y1="442" x2="808" y2="412" stroke="#c084fc88" stroke-width="1.2" marker-end="url(#ap)"/>

  <rect x="814" y="390" width="130" height="36" rx="8" fill="#fbbf2412" stroke="#fbbf24" stroke-width="1.2"/>
  <text x="879" y="409" text-anchor="middle" fill="#fbbf24" font-size="11" font-weight="bold">🚨 最终审查</text>
  <text x="879" y="422" text-anchor="middle" fill="#fbbf2488" font-size="9">monitor-bot</text>

  <line x1="944" y1="408" x2="978" y2="408" stroke="#fbbf24" stroke-width="1.5" marker-end="url(#ao)"/>
  <text x="993" y="413" text-anchor="middle" fill="#22c55e" font-size="16">✅</text>

  <!-- 关键特征标签 -->
  <rect x="70" y="468" width="70" height="20" rx="4" fill="#34d39922"/>
  <text x="105" y="482" text-anchor="middle" fill="#34d399" font-size="10">并行派发</text>
  <rect x="150" y="468" width="70" height="20" rx="4" fill="#34d39922"/>
  <text x="185" y="482" text-anchor="middle" fill="#34d399" font-size="10">依赖驱动</text>
  <rect x="230" y="468" width="70" height="20" rx="4" fill="#34d39922"/>
  <text x="265" y="482" text-anchor="middle" fill="#34d399" font-size="10">环检测</text>
  <rect x="310" y="468" width="90" height="20" rx="4" fill="#34d39922"/>
  <text x="355" y="482" text-anchor="middle" fill="#34d399" font-size="10">自动解除阻塞</text>

  <text x="580" y="482" fill="#64748b" font-size="10">适用: 大型功能开发、多分支并行、Spec 驱动开发</text>

  <!-- ════════════════════════════════════════════ -->
  <!-- Mode C: Debate 辩论                           -->
  <!-- ════════════════════════════════════════════ -->
  <rect x="40" y="510" width="1020" height="210" rx="12" fill="#c084fc08" stroke="#c084fc33" stroke-width="1"/>
  <rect x="40" y="510" width="260" height="30" rx="12" fill="#c084fc22"/>
  <text x="58" y="530" fill="#c084fc" font-size="15" font-weight="bold">Mode C · 多 Agent 辩论 (Debate)</text>

  <text x="58" y="558" fill="#94a3b8" font-size="12">同一问题多个 Agent 独立作答 → 交叉评审 → 综合最终决策</text>

  <!-- AGI -->
  <rect x="70" y="575" width="90" height="42" rx="8" fill="#f4364c18" stroke="#f4364c88" stroke-width="1.2"/>
  <text x="115" y="593" text-anchor="middle" fill="#f87171" font-size="12" font-weight="bold">🧠 AGI</text>
  <text x="115" y="608" text-anchor="middle" fill="#f8717199" font-size="9">主持人</text>

  <!-- 问题发出 -->
  <line x1="160" y1="588" x2="208" y2="575" stroke="#fff6" stroke-width="1.2" marker-end="url(#aw)"/>
  <line x1="160" y1="596" x2="208" y2="596" stroke="#fff6" stroke-width="1.2" marker-end="url(#aw)"/>
  <line x1="160" y1="604" x2="208" y2="618" stroke="#fff6" stroke-width="1.2" marker-end="url(#aw)"/>

  <!-- R1: 初始立场 -->
  <text x="280" y="556" text-anchor="middle" fill="#60a5fa99" font-size="10" font-weight="bold">Round 1 · 初始立场</text>

  <rect x="214" y="562" width="130" height="30" rx="7" fill="#60a5fa12" stroke="#60a5fa" stroke-width="1"/>
  <text x="279" y="581" text-anchor="middle" fill="#60a5fa" font-size="11">🔧 Agent A 立场</text>

  <rect x="214" y="596" width="130" height="30" rx="7" fill="#34d39912" stroke="#34d399" stroke-width="1"/>
  <text x="279" y="615" text-anchor="middle" fill="#34d399" font-size="11">🧪 Agent B 立场</text>

  <rect x="214" y="630" width="130" height="30" rx="7" fill="#fbbf2412" stroke="#fbbf24" stroke-width="1"/>
  <text x="279" y="649" text-anchor="middle" fill="#fbbf24" font-size="11">🚨 Agent C 立场</text>

  <!-- 箭头 R1 → R2 -->
  <line x1="344" y1="577" x2="415" y2="577" stroke="#60a5fa55" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#ab)"/>
  <line x1="344" y1="611" x2="415" y2="611" stroke="#34d39955" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#ag)"/>
  <line x1="344" y1="645" x2="415" y2="645" stroke="#fbbf2455" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#ao)"/>

  <!-- R2: 交叉评审 -->
  <text x="490" y="556" text-anchor="middle" fill="#34d39999" font-size="10" font-weight="bold">Round 2 · 交叉评审</text>

  <rect x="421" y="562" width="138" height="30" rx="7" fill="#60a5fa0c" stroke="#60a5fa88" stroke-width="1"/>
  <text x="490" y="576" text-anchor="middle" fill="#60a5fa" font-size="10">A 评审 B,C</text>
  <text x="490" y="588" text-anchor="middle" fill="#60a5fa66" font-size="8">看到其他人的立场</text>

  <rect x="421" y="596" width="138" height="30" rx="7" fill="#34d3990c" stroke="#34d39988" stroke-width="1"/>
  <text x="490" y="610" text-anchor="middle" fill="#34d399" font-size="10">B 评审 A,C</text>
  <text x="490" y="622" text-anchor="middle" fill="#34d39966" font-size="8">同意/反驳/补充</text>

  <rect x="421" y="630" width="138" height="30" rx="7" fill="#fbbf240c" stroke="#fbbf2488" stroke-width="1"/>
  <text x="490" y="644" text-anchor="middle" fill="#fbbf24" font-size="10">C 评审 A,B</text>
  <text x="490" y="656" text-anchor="middle" fill="#fbbf2466" font-size="8">修正自身观点</text>

  <!-- 交叉连线 -->
  <path d="M440 592 L440 596" stroke="#60a5fa33" stroke-width="0.8"/>
  <path d="M540 592 L540 596" stroke="#34d39933" stroke-width="0.8"/>
  <path d="M440 626 L440 630" stroke="#34d39933" stroke-width="0.8"/>

  <!-- 箭头 R2 → 综合 -->
  <line x1="559" y1="577" x2="640" y2="596" stroke="#60a5fa44" stroke-width="1" stroke-dasharray="4,3"/>
  <line x1="559" y1="611" x2="640" y2="600" stroke="#34d39944" stroke-width="1" stroke-dasharray="4,3"/>
  <line x1="559" y1="645" x2="640" y2="604" stroke="#fbbf2444" stroke-width="1" stroke-dasharray="4,3"/>

  <!-- 综合 -->
  <text x="710" y="566" text-anchor="middle" fill="#c084fc99" font-size="10" font-weight="bold">综合决策</text>

  <rect x="646" y="578" width="128" height="48" rx="10" fill="#c084fc15" stroke="#c084fc" stroke-width="1.5" filter="url(#sh)"/>
  <text x="710" y="599" text-anchor="middle" fill="#c084fc" font-size="13" font-weight="bold" filter="url(#gl)">🧠 综合分析</text>
  <text x="710" y="616" text-anchor="middle" fill="#c084fcaa" font-size="10">解决分歧 · 合并观点</text>

  <!-- 最终输出 -->
  <line x1="774" y1="602" x2="820" y2="602" stroke="#c084fc" stroke-width="1.8" marker-end="url(#ap)"/>

  <rect x="826" y="580" width="150" height="44" rx="10" fill="#22c55e12" stroke="#22c55e88" stroke-width="1.3"/>
  <text x="901" y="600" text-anchor="middle" fill="#22c55e" font-size="13" font-weight="bold">✅ 最终建议</text>
  <text x="901" y="616" text-anchor="middle" fill="#22c55e99" font-size="10">综合最强论点</text>

  <!-- 关键特征标签 -->
  <rect x="70" y="680" width="80" height="20" rx="4" fill="#c084fc22"/>
  <text x="110" y="694" text-anchor="middle" fill="#c084fc" font-size="10">独立作答</text>
  <rect x="160" y="680" width="80" height="20" rx="4" fill="#c084fc22"/>
  <text x="200" y="694" text-anchor="middle" fill="#c084fc" font-size="10">交叉评审</text>
  <rect x="250" y="680" width="80" height="20" rx="4" fill="#c084fc22"/>
  <text x="290" y="694" text-anchor="middle" fill="#c084fc" font-size="10">综合决策</text>
  <rect x="340" y="680" width="90" height="20" rx="4" fill="#c084fc22"/>
  <text x="385" y="694" text-anchor="middle" fill="#c084fc" font-size="10">竞争性假设</text>

  <text x="580" y="694" fill="#64748b" font-size="10">适用: 代码审查、架构决策、安全评估、竞争分析</text>

  <!-- ════════════════════════════════════════════ -->
  <!-- 底部: 共性说明                                 -->
  <!-- ════════════════════════════════════════════ -->
  <line x1="60" y1="730" x2="1040" y2="730" stroke="#ffffff12" stroke-width="1"/>

  <circle cx="120" cy="755" r="6" fill="#f87171"/>
  <text x="134" y="759" fill="#94a3b8" font-size="11">AGI 中心化调度 — 所有 Agent 之间不直接通信</text>

  <circle cx="530" cy="755" r="6" fill="#60a5fa"/>
  <text x="544" y="759" fill="#94a3b8" font-size="11">JSON 状态文件驱动 — 无外部依赖</text>

  <circle cx="830" cy="755" r="6" fill="#34d399"/>
  <text x="844" y="759" fill="#94a3b8" font-size="11">Python 3.12+ · stdlib only</text>

</svg>


path: README.md
content: # Team Tasks — Multi-Agent Pipeline Coordination

A Python CLI tool for coordinating multi-agent development workflows through shared JSON task files. Designed for use with [OpenClaw](https://github.com/openclaw/openclaw) and AI agent orchestration systems.

## Features

Three coordination modes for different workflows:

| Mode | Description | Use Case |
|------|-------------|----------|
| **Linear** | Sequential pipeline with auto-advance | Bug fixes, simple features, step-by-step workflows |
| **DAG** | Dependency graph with parallel dispatch | Large features, spec-driven dev, complex dependencies |
| **Debate** | Multi-agent position + cross-review | Code reviews, architecture decisions, competing hypotheses |

## Requirements

- Python 3.12+ (stdlib only, no external dependencies)
- Data stored as JSON in `/home/ubuntu/clawd/data/team-tasks/` (override with `TEAM_TASKS_DIR` env var)

## Installation

```bash
# Clone the repo
git clone https://github.com/win4r/team-tasks.git

# No pip install needed — it's a standalone script
python3 team-tasks/scripts/task_manager.py --help
```

For OpenClaw skill integration, copy to your skills directory:
```bash
cp -r team-tasks/ /path/to/clawd/skills/team-tasks/
```

## Quick Start

### Mode A: Linear Pipeline

A sequential pipeline where agents execute one after another in order.

```bash
TM="python3 scripts/task_manager.py"

# 1. Create project with pipeline order
$TM init my-api -g "Build REST API with tests and docs" \
  -p "code-agent,test-agent,docs-agent,monitor-bot"

# 2. Assign tasks to each stage
$TM assign my-api code-agent "Implement Flask REST API: GET/POST/DELETE /items"
$TM assign my-api test-agent "Write pytest tests, target 90%+ coverage"
$TM assign my-api docs-agent "Write README with API docs and examples"
$TM assign my-api monitor-bot "Security audit and deployment readiness check"

# 3. Check what's next
$TM next my-api
# ▶️  Next stage: code-agent

# 4. Dispatch → work → save result → mark done
$TM update my-api code-agent in-progress
# ... agent does work ...
$TM result my-api code-agent "Created app.py with 3 endpoints"
$TM update my-api code-agent done
# ▶️  Next: test-agent  (auto-advance!)

# 5. Check progress anytime
$TM status my-api
```

**Output example:**
```
📋 Project: my-api
🎯 Goal: Build REST API with tests and docs
📊 Status: active  |  Mode: linear
▶️  Current: test-agent

  ✅ code-agent: done
     Task: Implement Flask REST API
     Output: Created app.py with 3 endpoints
  🔄 test-agent: in-progress
     Task: Write pytest tests, target 90%+ coverage
  ⬜ docs-agent: pending
  ⬜ monitor-bot: pending

  Progress: [██░░] 2/4
```

### Mode B: DAG (Dependency Graph)

Tasks declare dependencies and run in parallel when deps are met.

```bash
TM="python3 scripts/task_manager.py"

# 1. Create DAG project
$TM init my-feature -m dag -g "Build search feature with parallel workstreams"

# 2. Add tasks with dependencies
$TM add my-feature design     -a docs-agent  --desc "Write API spec"
$TM add my-feature scaffold   -a code-agent  --desc "Create project skeleton"
$TM add my-feature implement  -a code-agent  -d "design,scaffold" --desc "Implement API"
$TM add my-feature write-tests -a test-agent -d "design"          --desc "Write test cases from spec"
$TM add my-feature run-tests  -a test-agent  -d "implement,write-tests" --desc "Run all tests"
$TM add my-feature write-docs -a docs-agent  -d "implement"             --desc "Write final docs"
$TM add my-feature review     -a monitor-bot -d "run-tests,write-docs"  --desc "Final review"

# 3. Visualize the DAG
$TM graph my-feature
```

**Graph output:**
```
📋 my-feature — DAG Graph

├─ ⬜ design [docs-agent]
│  ├─ ⬜ implement [code-agent]
│  │  ├─ ⬜ run-tests [test-agent]
│  │  │  └─ ⬜ review [monitor-bot]
│  │  └─ ⬜ write-docs [docs-agent]
│  └─ ⬜ write-tests [test-agent]
└─ ⬜ scaffold [code-agent]
   └─ ⬜ implement (↑ see above)

  Progress: [░░░░░░░] 0/7
```

```bash
# 4. Get ready tasks (parallel dispatch!)
$TM ready my-feature
# 🟢 Ready to dispatch (2 tasks):
#   📌 design → agent: docs-agent
#   📌 scaffold → agent: code-agent

# 5. Dispatch both in parallel, then mark done
$TM update my-feature design done
# 🟢 Unblocked: write-tests  ← auto-detected!

$TM update my-feature scaffold done
# 🟢 Unblocked: implement

# 6. Continue until all complete
$TM ready my-feature  # Shows newly unblocked tasks
```

**Key DAG features:**
- `ready` returns ALL tasks whose deps are satisfied — dispatch them simultaneously
- `ready --json` includes `depOutputs` — previous stage results to pass to agents
- Automatic unblock notifications when a task completes
- Cycle detection on `add` — rejects tasks that would create circular dependencies
- Partial failure: unrelated branches continue; only downstream tasks block

### Mode C: Debate (Multi-Agent Deliberation)

Send the same question to multiple agents, collect positions, cross-review, and synthesize.

```bash
TM="python3 scripts/task_manager.py"

# 1. Create debate project
$TM init security-review --mode debate \
  -g "Review auth module for security vulnerabilities"

# 2. Add debaters with roles/perspectives
$TM add-debater security-review code-agent  --role "security expert focused on injection attacks"
$TM add-debater security-review test-agent  --role "QA engineer focused on edge cases"
$TM add-debater security-review monitor-bot --role "ops engineer focused on deployment risks"

# 3. Start initial round
$TM round security-review start
# 🗣️  Debate Round 1 (initial) started
# Outputs dispatch prompts for each debater

# 4. Collect initial positions
$TM round security-review collect code-agent  "Found SQL injection in login()"
$TM round security-review collect test-agent  "Missing input validation on email field"
$TM round security-review collect monitor-bot "No rate limiting on auth endpoints"
# ✅ Round 1 (initial) is complete.
# ➡️  Next: round security-review cross-review

# 5. Generate cross-review prompts
$TM round security-review cross-review
# 🔁 Each debater gets others' positions + review instructions

# 6. Collect cross-reviews
$TM round security-review collect code-agent  "Agree on validation. Rate limiting is critical."
$TM round security-review collect test-agent  "SQL injection is most severe. Adding rate limit tests."
$TM round security-review collect monitor-bot "Both findings valid. Recommending WAF as additional layer."

# 7. Synthesize all positions
$TM round security-review synthesize
# 🧾 Outputs all initial positions + cross-reviews for final synthesis
```

**Debate workflow diagram:**
```
Question → [Agent A] → Position A ─┐
         → [Agent B] → Position B ─┤── Cross-Review ── Synthesis
         → [Agent C] → Position C ─┘
```

## CLI Reference

### All Commands

| Command | Mode | Usage | Description |
|---------|------|-------|-------------|
| `init` | all | `init <project> -g "goal" [-m linear\|dag\|debate]` | Create project |
| `add` | dag | `add <project> <task-id> -a <agent> -d <deps>` | Add task with deps |
| `add-debater` | debate | `add-debater <project> <agent-id> [-r "role"]` | Add debater |
| `round` | debate | `round <project> start\|collect\|cross-review\|synthesize` | Debate actions |
| `status` | all | `status <project> [--json]` | Show progress |
| `assign` | linear/dag | `assign <project> <stage> "desc"` | Set task description |
| `update` | linear/dag | `update <project> <stage> <status>` | Change status |
| `next` | linear | `next <project> [--json]` | Get next stage |
| `ready` | dag | `ready <project> [--json]` | Get dispatchable tasks |
| `graph` | dag | `graph <project>` | Show dependency tree |
| `log` | linear/dag | `log <project> <stage> "msg"` | Add log entry |
| `result` | linear/dag | `result <project> <stage> "output"` | Save stage output |
| `reset` | linear/dag | `reset <project> [stage] [--all]` | Reset to pending |
| `history` | linear/dag | `history <project> <stage>` | Show log history |
| `list` | all | `list` | List all projects |

### Status Values

| Status | Icon | Meaning |
|--------|------|---------|
| `pending` | ⬜ | Waiting for dispatch |
| `in-progress` | 🔄 | Agent is working |
| `done` | ✅ | Completed |
| `failed` | ❌ | Failed (pipeline blocks downstream) |
| `skipped` | ⏭️ | Intentionally skipped |

### Init Options

```bash
python3 scripts/task_manager.py init <project> \
  --goal "Project description" \
  --mode linear|dag|debate \
  --pipeline "agent1,agent2,agent3"  # linear only \
  --workspace "/path/to/shared/dir" \
  --force  # overwrite existing
```

## Integration with OpenClaw

This tool is designed as an [OpenClaw Skill](https://docs.openclaw.ai). The orchestrating agent (AGI) dispatches tasks to worker agents via `sessions_send` and tracks state through the CLI.

**Dispatch loop (linear):**
```
1. next <project> --json           → get next stage info
2. update <project> <agent> in-progress
3. sessions_send(agent, task)      → dispatch to agent
4. Wait for agent reply
5. result <project> <agent> "..."  → save output
6. update <project> <agent> done   → auto-advances to next stage
7. Repeat
```

**Dispatch loop (DAG):**
```
1. ready <project> --json          → get ALL dispatchable tasks
2. For each ready task (parallel):
   a. update <project> <task> in-progress
   b. sessions_send(agent, task + depOutputs)
3. On reply: result → update done → check newly unblocked
4. Repeat until all tasks complete
```

## Common Pitfalls

### ⚠️ Linear mode: Stage ID = agent name, NOT a number

```bash
# ❌ WRONG — "stage '1' not found"
python3 scripts/task_manager.py assign my-project 1 "Build API"

# ✅ CORRECT
python3 scripts/task_manager.py assign my-project code-agent "Build API"
```

### ⚠️ DAG: Dependencies must exist before referencing

```bash
# ❌ WRONG — "dependency 'design' not found"
$TM add my-project implement -a code-agent -d "design"

# ✅ CORRECT — add deps first
$TM add my-project design -a docs-agent --desc "Write spec"
$TM add my-project implement -a code-agent -d "design" --desc "Implement"
```

### ⚠️ Debate: Cannot add debaters after rounds start

```bash
# ❌ WRONG
$TM round my-debate start
$TM add-debater my-debate new-agent  # Error!

# ✅ CORRECT — add all debaters before starting
$TM add-debater my-debate agent-a
$TM add-debater my-debate agent-b
$TM round my-debate start
```

## Data Storage

Project files are stored as JSON at:
```
/home/ubuntu/clawd/data/team-tasks/<project>.json
```

Override with environment variable:
```bash
export TEAM_TASKS_DIR=/custom/path
```

## Project Structure

```
team-tasks/
├── README.md              # This file
├── SKILL.md               # OpenClaw skill definition
├── SPEC.md                # Enhancement spec (debate + workspace)
├── scripts/
│   └── task_manager.py    # Main CLI tool (Python 3.12+, stdlib only)
└── docs/
    ├── GAP_ANALYSIS.md    # Comparison with Claude Code Agent Teams
    └── AGENT_TEAMS_OFFICIAL_DOCS.md  # Reference documentation
```

## License

MIT


path: scripts/task_manager.py
content: #!/usr/bin/env python3
"""Team Tasks — shared JSON task manager for multi-agent pipelines.

Supports two modes:
  Mode A (linear):  Fixed pipeline order, auto-advance on done
  Mode B (dag):     Tasks declare dependsOn, parallel dispatch when deps met
  Mode C (debate):  Multi-agent position + cross-review workflow

Commands:
  init      Create a new project (--mode linear|dag|debate)
  add       Add a task to a DAG project
  add-debater Add a debater to a debate project
  round     Debate round actions (start/collect/cross-review/synthesize)
  status    Show current pipeline/DAG status
  assign    Set task description for a stage/task
  update    Update stage/task status (pending/in-progress/done/failed)
  next      Get next actionable stage (linear mode)
  ready     Get all tasks whose dependencies are met (dag mode)
  log       Append a log entry to a stage/task
  result    Set the output/result of a stage/task
  reset     Reset a stage/task (or all) back to pending
  history   Show full log history for a stage/task
  graph     Show DAG dependency graph (dag mode)
  list      List all projects
"""

import argparse
import json
import os
import sys
from datetime import datetime, timezone

DEFAULT_PIPELINE = ["code-agent", "test-agent", "docs-agent", "monitor-bot"]
TASKS_DIR = os.environ.get("TEAM_TASKS_DIR", "/home/ubuntu/clawd/data/team-tasks")


def now_iso():
    return datetime.now(timezone.utc).isoformat()


def task_file(project: str) -> str:
    return os.path.join(TASKS_DIR, f"{project}.json")


def load_project(project: str) -> dict:
    path = task_file(project)
    if not os.path.exists(path):
        print(f"Error: project '{project}' not found at {path}", file=sys.stderr)
        sys.exit(1)
    with open(path) as f:
        return json.load(f)


def save_project(project: str, data: dict):
    path = task_file(project)
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w") as f:
        json.dump(data, f, indent=2, ensure_ascii=False)


def make_stage(agent_id: str, task: str = "", depends_on: list = None) -> dict:
    stage = {
        "agent": agent_id,
        "status": "pending",
        "task": task,
        "startedAt": None,
        "completedAt": None,
        "output": "",
        "logs": [],
    }
    if depends_on is not None:
        stage["dependsOn"] = depends_on
    return stage


def get_mode(data: dict) -> str:
    return data.get("mode", "linear")


def is_dag(data: dict) -> bool:
    return get_mode(data) == "dag"


def is_debate(data: dict) -> bool:
    return get_mode(data) == "debate"


def ensure_stage_mode(data: dict, command: str):
    if is_debate(data):
        print(
            f"Error: '{command}' is not supported for debate mode projects.",
            file=sys.stderr,
        )
        sys.exit(1)


def ensure_debate_mode(data: dict, command: str):
    if not is_debate(data):
        print(
            f"Error: '{command}' is only for debate mode projects. Use 'init --mode debate'.",
            file=sys.stderr,
        )
        sys.exit(1)


def compute_ready_tasks(data: dict) -> list:
    """Return task IDs whose dependencies are all done and status is pending."""
    ready = []
    for task_id, task in data["stages"].items():
        if task["status"] != "pending":
            continue
        deps = task.get("dependsOn", [])
        all_deps_done = all(
            data["stages"].get(d, {}).get("status") in ("done", "skipped")
            for d in deps
        )
        if all_deps_done:
            ready.append(task_id)
    return ready


def check_dag_completion(data: dict):
    """Update project status based on DAG task states."""
    all_tasks = data["stages"]
    statuses = [t["status"] for t in all_tasks.values()]

    if all(s in ("done", "skipped") for s in statuses):
        data["status"] = "completed"
    elif any(s == "failed" for s in statuses):
        # Check if any ready tasks remain despite failure
        ready = compute_ready_tasks(data)
        if not ready and not any(s == "in-progress" for s in statuses):
            data["status"] = "blocked"
    elif any(s in ("in-progress", "pending") for s in statuses):
        data["status"] = "active"


def detect_cycles(data: dict) -> list:
    """Detect cycles in DAG using DFS. Returns list of nodes in cycle or empty list."""
    WHITE, GRAY, BLACK = 0, 1, 2
    color = {tid: WHITE for tid in data["stages"]}
    path = []

    def dfs(node):
        color[node] = GRAY
        path.append(node)
        for dep in data["stages"].get(node, {}).get("dependsOn", []):
            if dep not in color:
                continue
            if color[dep] == GRAY:
                cycle_start = path.index(dep)
                return path[cycle_start:]
            if color[dep] == WHITE:
                result = dfs(dep)
                if result:
                    return result
        path.pop()
        color[node] = BLACK
        return []

    for tid in data["stages"]:
        if color[tid] == WHITE:
            result = dfs(tid)
            if result:
                return result
    return []


# ── Commands ────────────────────────────────────────────────────────

def cmd_init(args):
    """Create a new project."""
    project = args.project
    path = task_file(project)
    if os.path.exists(path) and not args.force:
        print(f"Error: project '{project}' already exists. Use --force to overwrite.", file=sys.stderr)
        sys.exit(1)

    mode = args.mode or "linear"
    goal = args.goal or ""

    workspace = args.workspace or ""

    if mode == "linear":
        pipeline = args.pipeline.split(",") if args.pipeline else DEFAULT_PIPELINE
        stages = {}
        for agent in pipeline:
            stages[agent] = make_stage(agent)
        data = {
            "project": project,
            "goal": goal,
            "created": now_iso(),
            "updated": now_iso(),
            "status": "active",
            "mode": "linear",
            "workspace": workspace,
            "pipeline": pipeline,
            "currentStage": pipeline[0] if pipeline else None,
            "stages": stages,
        }
    elif mode == "dag":
        data = {
            "project": project,
            "goal": goal,
            "created": now_iso(),
            "updated": now_iso(),
            "status": "active",
            "mode": "dag",
            "workspace": workspace,
            "stages": {},
        }
    elif mode == "debate":
        data = {
            "project": project,
            "goal": goal,
            "created": now_iso(),
            "updated": now_iso(),
            "status": "active",
            "mode": "debate",
            "workspace": workspace,
            "debaters": {},
            "rounds": [],
            "currentRound": 0,
        }
    else:
        print(f"Error: mode must be 'linear', 'dag', or 'debate'", file=sys.stderr)
        sys.exit(1)

    save_project(project, data)
    print(json.dumps(data, indent=2, ensure_ascii=False))


def _debate_current_round(data: dict) -> tuple[int, dict] | tuple[None, None]:
    idx = data.get("currentRound", 0) - 1
    if idx < 0 or idx >= len(data.get("rounds", [])):
        return None, None
    return idx, data["rounds"][idx]


def _debate_record_response(data: dict, agent_id: str, round_idx: int, content: str):
    debater = data["debaters"][agent_id]
    responses = debater.setdefault("responses", [])
    round_type = data["rounds"][round_idx]["type"]
    round_num = round_idx + 1

    existing = None
    for item in responses:
        if item.get("round") == round_num and item.get("type") == round_type:
            existing = item
            break

    if existing is None:
        responses.append(
            {
                "round": round_num,
                "type": round_type,
                "response": content,
                "time": now_iso(),
            }
        )
    else:
        existing["response"] = content
        existing["time"] = now_iso()


def _debate_role(data: dict, agent_id: str) -> str:
    role = data["debaters"].get(agent_id, {}).get("role", "")
    return role or "no role specified"


def _all_debaters_responded(data: dict, round_data: dict) -> bool:
    return all(agent in round_data["responses"] for agent in data["debaters"])


def cmd_add_debater(args):
    """Add a debater to a debate project."""
    data = load_project(args.project)
    ensure_debate_mode(data, "add-debater")

    if data.get("rounds"):
        print("Error: cannot add debaters after rounds have started", file=sys.stderr)
        sys.exit(1)

    agent_id = args.agent_id
    if agent_id in data["debaters"]:
        print(f"Error: debater '{agent_id}' already exists", file=sys.stderr)
        sys.exit(1)

    data["debaters"][agent_id] = {
        "role": args.role or "",
        "responses": [],
    }
    data["updated"] = now_iso()
    save_project(args.project, data)
    role_str = f" ({args.role})" if args.role else ""
    print(f"✅ Added debater '{agent_id}'{role_str}")


def cmd_round(args):
    """Debate round actions: start/collect/cross-review/synthesize."""
    data = load_project(args.project)
    ensure_debate_mode(data, "round")
    action = args.action

    if action == "start":
        if not data.get("debaters"):
            print("Error: add at least one debater first", file=sys.stderr)
            sys.exit(1)
        if data.get("rounds"):
            print("Error: initial round already started", file=sys.stderr)
            sys.exit(1)

        round_data = {
            "type": "initial",
            "status": "in-progress",
            "responses": {},
            "startedAt": now_iso(),
            "completedAt": None,
        }
        data["rounds"].append(round_data)
        data["currentRound"] = 1
        data["updated"] = now_iso()
        save_project(args.project, data)

        question = data.get("goal", "")
        print("🗣️  Debate Round 1 (initial) started\n")
        for agent_id in data["debaters"]:
            role = _debate_role(data, agent_id)
            print(f"Agent: {agent_id} ({role})")
            print(f"Question: {question}")
            print("Task: Provide your position and supporting reasoning.\n")
        return

    if action == "collect":
        if not args.agent_id or args.content is None:
            print("Error: usage: round <project> collect <agent-id> \"text\"", file=sys.stderr)
            sys.exit(1)

        round_idx, round_data = _debate_current_round(data)
        if round_data is None:
            print("Error: no active round. Run 'round <project> start' first.", file=sys.stderr)
            sys.exit(1)
        if round_data["status"] != "in-progress":
            print("Error: current round is not accepting responses", file=sys.stderr)
            sys.exit(1)
        if args.agent_id not in data["debaters"]:
            print(f"Error: debater '{args.agent_id}' not found", file=sys.stderr)
            sys.exit(1)

        round_data["responses"][args.agent_id] = args.content
        _debate_record_response(data, args.agent_id, round_idx, args.content)

        if _all_debaters_responded(data, round_data):
            round_data["status"] = "done"
            round_data["completedAt"] = now_iso()
            print(
                f"✅ Collected response from {args.agent_id}. "
                f"Round {round_idx + 1} ({round_data['type']}) is complete."
            )
            if round_data["type"] == "initial":
                print("➡️  Next: round <project> cross-review")
            elif round_data["type"] == "cross-review":
                print("➡️  Next: round <project> synthesize")
        else:
            missing = [a for a in data["debaters"] if a not in round_data["responses"]]
            print(f"✅ Collected response from {args.agent_id}. Waiting for: {', '.join(missing)}")

        data["updated"] = now_iso()
        save_project(args.project, data)
        return

    if action == "cross-review":
        if not data.get("rounds"):
            print("Error: initial round not started", file=sys.stderr)
            sys.exit(1)
        initial = data["rounds"][0]
        if initial["type"] != "initial":
            print("Error: invalid debate state: first round is not initial", file=sys.stderr)
            sys.exit(1)
        if initial["status"] != "done":
            print("Error: complete initial round responses before cross-review", file=sys.stderr)
            sys.exit(1)

        if len(data["rounds"]) == 1:
            cross = {
                "type": "cross-review",
                "status": "in-progress",
                "responses": {},
                "startedAt": now_iso(),
                "completedAt": None,
            }
            data["rounds"].append(cross)
            data["currentRound"] = 2
        else:
            cross = data["rounds"][1]
            if cross["type"] != "cross-review":
                print("Error: invalid debate state: second round is not cross-review", file=sys.stderr)
                sys.exit(1)
            data["currentRound"] = 2

        data["updated"] = now_iso()
        save_project(args.project, data)

        print("🔁 Cross-review prompts\n")
        for agent_id in data["debaters"]:
            role = _debate_role(data, agent_id)
            own = initial["responses"].get(agent_id, "")
            print(f"Agent: {agent_id} ({role})")
            print(f"Your previous response: {own}\n")
            print("Other debaters' responses:")
            others = [a for a in data["debaters"] if a != agent_id]
            if not others:
                print("- (none)")
            else:
                for other_id in others:
                    other_role = _debate_role(data, other_id)
                    other_resp = initial["responses"].get(other_id, "")
                    print(f"- {other_id} ({other_role}): {other_resp}")
            print(
                "\nTask: Review the other responses. Do you agree or disagree? "
                "What did they miss? Update your position if needed.\n"
            )
        return

    if action == "synthesize":
        if not data.get("rounds"):
            print("Error: no rounds found. Start with 'round <project> start'.", file=sys.stderr)
            sys.exit(1)

        initial = data["rounds"][0]
        cross = data["rounds"][1] if len(data["rounds"]) > 1 else None

        if initial["status"] != "done":
            print("Error: initial round is incomplete", file=sys.stderr)
            sys.exit(1)
        if cross and cross["type"] == "cross-review" and cross["status"] == "done":
            data["status"] = "completed"

        data["updated"] = now_iso()
        save_project(args.project, data)

        print(f"🧾 Synthesis package for {data['project']}")
        if data.get("goal"):
            print(f"Question: {data['goal']}")
        print("\nInitial positions:")
        for agent_id in data["debaters"]:
            role = _debate_role(data, agent_id)
            response = initial["responses"].get(agent_id, "(missing)")
            print(f"- {agent_id} ({role}): {response}")

        print("\nCross-reviews:")
        if not cross or cross.get("type") != "cross-review":
            print("- (cross-review round not started)")
        else:
            for agent_id in data["debaters"]:
                role = _debate_role(data, agent_id)
                review = cross["responses"].get(agent_id, "(missing)")
                print(f"- {agent_id} ({role}): {review}")

        print(
            "\nTask: Synthesize the strongest points, resolve disagreements, "
            "and produce a final recommendation."
        )
        return

    print(f"Error: unknown round action '{action}'", file=sys.stderr)
    sys.exit(1)


def cmd_add(args):
    """Add a task to a DAG project."""
    data = load_project(args.project)
    ensure_stage_mode(data, "add")
    if not is_dag(data):
        print("Error: 'add' is only for DAG mode projects. Use 'init --mode dag'.", file=sys.stderr)
        sys.exit(1)

    task_id = args.task_id
    if task_id in data["stages"]:
        print(f"Error: task '{task_id}' already exists", file=sys.stderr)
        sys.exit(1)

    agent = args.agent or task_id
    depends_on = args.depends.split(",") if args.depends else []
    task_desc = args.desc or ""

    # Validate dependencies exist
    for dep in depends_on:
        if dep not in data["stages"]:
            print(f"Error: dependency '{dep}' not found. Add it first.", file=sys.stderr)
            sys.exit(1)

    data["stages"][task_id] = make_stage(agent, task_desc, depends_on)

    # Check for cycles
    cycles = detect_cycles(data)
    if cycles:
        del data["stages"][task_id]
        print(f"Error: adding '{task_id}' creates a cycle: {' → '.join(cycles + [cycles[0]])}", file=sys.stderr)
        sys.exit(1)

    data["updated"] = now_iso()
    save_project(args.project, data)
    dep_str = f" (depends on: {', '.join(depends_on)})" if depends_on else " (no dependencies — root task)"
    print(f"✅ Added task '{task_id}' → agent: {agent}{dep_str}")


def cmd_status(args):
    """Show current project status."""
    data = load_project(args.project)

    if args.json:
        print(json.dumps(data, indent=2, ensure_ascii=False))
        return

    mode = get_mode(data)
    print(f"📋 Project: {data['project']}")
    if data.get("goal"):
        print(f"🎯 Goal: {data['goal']}")
    print(f"📊 Status: {data['status']}  |  Mode: {mode}")
    if data.get("workspace"):
        print(f"🗂️  Workspace: {data['workspace']}")

    if mode == "linear":
        print(f"▶️  Current: {data.get('currentStage', 'N/A')}")
    print()

    status_icons = {
        "pending": "⬜",
        "in-progress": "🔄",
        "done": "✅",
        "failed": "❌",
        "skipped": "⏭️",
    }

    if mode == "debate":
        debaters = data.get("debaters", {})
        rounds = data.get("rounds", [])
        print(f"  👥 Debaters: {len(debaters)}")
        for agent_id, info in debaters.items():
            role = info.get("role") or "no role specified"
            print(f"  - {agent_id}: {role}")

        if not rounds:
            print("\n  🟡 No rounds started")
            return

        print()
        for idx, round_data in enumerate(rounds, start=1):
            rtype = round_data.get("type", "?")
            rstatus = round_data.get("status", "pending")
            responses = round_data.get("responses", {})
            print(f"  🔹 Round {idx}: {rtype} [{rstatus}] ({len(responses)}/{len(debaters)} responses)")
            for agent_id, response in responses.items():
                preview = response[:80]
                if len(response) > 80:
                    preview += "..."
                print(f"     {agent_id}: {preview}")
        return

    if mode == "dag":
        ready = compute_ready_tasks(data)
        # Topological-ish display: roots first, then by depth
        displayed = set()

        def display_task(tid, indent=0):
            if tid in displayed:
                return
            displayed.add(tid)
            task = data["stages"].get(tid, {})
            icon = status_icons.get(task.get("status", "pending"), "❓")
            ready_mark = " 🟢 READY" if tid in ready else ""
            deps = task.get("dependsOn", [])
            dep_str = f" ← [{', '.join(deps)}]" if deps else ""
            prefix = "  " * indent
            print(f"{prefix}  {icon} {tid} ({task.get('agent', '?')}): {task.get('status', 'pending')}{ready_mark}{dep_str}")
            task_preview = task.get("task", "")[:60]
            if task_preview:
                print(f"{prefix}     Task: {task_preview}{'...' if len(task.get('task', '')) > 60 else ''}")
            if task.get("output"):
                out_preview = task["output"][:80]
                print(f"{prefix}     Output: {out_preview}{'...' if len(task['output']) > 80 else ''}")

        # Display root tasks first, then tasks with deps
        roots = [tid for tid, t in data["stages"].items() if not t.get("dependsOn")]
        non_roots = [tid for tid, t in data["stages"].items() if t.get("dependsOn")]
        for tid in roots:
            display_task(tid)
        for tid in non_roots:
            display_task(tid)

        if ready:
            print(f"\n  🟢 Ready to dispatch: {', '.join(ready)}")

    else:  # linear
        for i, agent in enumerate(data.get("pipeline", [])):
            stage = data["stages"].get(agent, {})
            icon = status_icons.get(stage.get("status", "pending"), "❓")
            task_preview = stage.get("task", "")[:60]
            if len(stage.get("task", "")) > 60:
                task_preview += "..."
            print(f"  {icon} {agent}: {stage.get('status', 'pending')}")
            if task_preview:
                print(f"     Task: {task_preview}")
            if stage.get("output"):
                out_preview = stage["output"][:80]
                if len(stage["output"]) > 80:
                    out_preview += "..."
                print(f"     Output: {out_preview}")

    # Progress bar
    all_tasks = list(data["stages"].values())
    done_count = sum(1 for t in all_tasks if t.get("status") in ("done", "skipped"))
    total = len(all_tasks)
    if total:
        bar = "█" * done_count + "░" * (total - done_count)
        print(f"\n  Progress: [{bar}] {done_count}/{total}")


def cmd_assign(args):
    """Set task description for a stage/task."""
    data = load_project(args.project)
    ensure_stage_mode(data, "assign")
    stage_id = args.stage
    if stage_id not in data["stages"]:
        print(f"Error: stage '{stage_id}' not found", file=sys.stderr)
        sys.exit(1)

    data["stages"][stage_id]["task"] = args.task
    data["updated"] = now_iso()
    save_project(args.project, data)
    print(f"✅ Assigned task to {stage_id}")


def cmd_update(args):
    """Update stage/task status."""
    data = load_project(args.project)
    ensure_stage_mode(data, "update")
    stage_id = args.stage
    new_status = args.status

    if stage_id not in data["stages"]:
        print(f"Error: stage '{stage_id}' not found", file=sys.stderr)
        sys.exit(1)

    valid = ("pending", "in-progress", "done", "failed", "skipped")
    if new_status not in valid:
        print(f"Error: status must be one of {valid}", file=sys.stderr)
        sys.exit(1)

    stage = data["stages"][stage_id]
    old_status = stage["status"]
    stage["status"] = new_status

    if new_status == "in-progress" and not stage["startedAt"]:
        stage["startedAt"] = now_iso()
    elif new_status in ("done", "failed", "skipped"):
        stage["completedAt"] = now_iso()

    stage["logs"].append({
        "time": now_iso(),
        "event": f"status: {old_status} → {new_status}",
    })

    if is_dag(data):
        # DAG mode: check completion and show newly ready tasks
        check_dag_completion(data)
        data["updated"] = now_iso()
        save_project(args.project, data)
        print(f"✅ {stage_id}: {old_status} → {new_status}")

        if new_status == "done":
            ready = compute_ready_tasks(data)
            if ready:
                print(f"🟢 Unblocked: {', '.join(ready)}")
            elif data["status"] == "completed":
                print("🎉 All tasks completed!")
        elif new_status == "failed":
            # Show what's still runnable despite the failure
            ready = compute_ready_tasks(data)
            if ready:
                print(f"⚠️  Failed, but these tasks can still run: {', '.join(ready)}")
            else:
                print(f"❌ Pipeline blocked — no tasks can proceed")
    else:
        # Linear mode: auto-advance currentStage
        if new_status == "done":
            pipeline = data.get("pipeline", [])
            idx = pipeline.index(stage_id) if stage_id in pipeline else -1
            if idx >= 0 and idx < len(pipeline) - 1:
                data["currentStage"] = pipeline[idx + 1]
            elif idx == len(pipeline) - 1:
                data["status"] = "completed"
                data["currentStage"] = None
        elif new_status == "failed":
            data["status"] = "blocked"

        data["updated"] = now_iso()
        save_project(args.project, data)
        print(f"✅ {stage_id}: {old_status} → {new_status}")

        if new_status == "done" and data.get("currentStage"):
            print(f"▶️  Next: {data['currentStage']}")
        elif data["status"] == "completed":
            print("🎉 Pipeline completed!")


def cmd_next(args):
    """Get next actionable stage (linear mode)."""
    data = load_project(args.project)
    ensure_stage_mode(data, "next")

    if is_dag(data):
        # In DAG mode, redirect to ready
        return cmd_ready(args)

    current = data.get("currentStage")
    if not current:
        if data["status"] == "completed":
            print("🎉 Pipeline completed — no pending stages")
        else:
            print("❌ No current stage (pipeline may be blocked)")
        return

    stage = data["stages"].get(current, {})
    result = {
        "stage": current,
        "agent": stage.get("agent", current),
        "task": stage.get("task", ""),
        "status": stage.get("status", "pending"),
        "workspace": data.get("workspace", ""),
    }

    if args.json:
        print(json.dumps(result, indent=2, ensure_ascii=False))
    else:
        print(f"▶️  Next stage: {current}")
        print(f"   Agent: {result['agent']}")
        print(f"   Status: {result['status']}")
        if result["workspace"]:
            print(f"   Workspace: {result['workspace']}")
        if result["task"]:
            print(f"   Task: {result['task']}")


def cmd_ready(args):
    """Get all tasks whose dependencies are met (dag mode)."""
    data = load_project(args.project)
    ensure_stage_mode(data, "ready")

    if not is_dag(data):
        print("Hint: 'ready' is for DAG mode. Use 'next' for linear pipelines.")
        return cmd_next(args)

    if data["status"] == "completed":
        print("🎉 All tasks completed — nothing to dispatch")
        return

    ready = compute_ready_tasks(data)

    if not ready:
        in_progress = [tid for tid, t in data["stages"].items() if t["status"] == "in-progress"]
        if in_progress:
            print(f"⏳ No ready tasks — waiting for: {', '.join(in_progress)}")
        else:
            print("❌ No ready tasks (pipeline may be blocked)")
        return

    results = []
    for tid in ready:
        task = data["stages"][tid]
        deps = task.get("dependsOn", [])
        dep_outputs = {}
        for d in deps:
            dep_task = data["stages"].get(d, {})
            if dep_task.get("output"):
                dep_outputs[d] = dep_task["output"]

        entry = {
            "taskId": tid,
            "agent": task.get("agent", tid),
            "task": task.get("task", ""),
            "dependsOn": deps,
            "depOutputs": dep_outputs,
            "workspace": data.get("workspace", ""),
        }
        results.append(entry)

    if getattr(args, "json", False):
        print(json.dumps(results, indent=2, ensure_ascii=False))
    else:
        print(f"🟢 Ready to dispatch ({len(results)} task{'s' if len(results) > 1 else ''}):\n")
        for r in results:
            deps_str = f" ← [{', '.join(r['dependsOn'])}]" if r["dependsOn"] else ""
            print(f"  📌 {r['taskId']} → agent: {r['agent']}{deps_str}")
            if r["workspace"]:
                print(f"     Workspace: {r['workspace']}")
            if r["task"]:
                print(f"     Task: {r['task'][:80]}{'...' if len(r['task']) > 80 else ''}")
            if r["depOutputs"]:
                print(f"     Dep outputs:")
                for dep_id, out in r["depOutputs"].items():
                    print(f"       {dep_id}: {out[:60]}{'...' if len(out) > 60 else ''}")
            print()


def cmd_log(args):
    """Append a log entry to a stage/task."""
    data = load_project(args.project)
    ensure_stage_mode(data, "log")
    stage_id = args.stage

    if stage_id not in data["stages"]:
        print(f"Error: stage '{stage_id}' not found", file=sys.stderr)
        sys.exit(1)

    data["stages"][stage_id]["logs"].append({
        "time": now_iso(),
        "event": args.message,
    })
    data["updated"] = now_iso()
    save_project(args.project, data)
    print(f"📝 Log added to {stage_id}")


def cmd_result(args):
    """Set stage/task output/result."""
    data = load_project(args.project)
    ensure_stage_mode(data, "result")
    stage_id = args.stage

    if stage_id not in data["stages"]:
        print(f"Error: stage '{stage_id}' not found", file=sys.stderr)
        sys.exit(1)

    data["stages"][stage_id]["output"] = args.output
    data["updated"] = now_iso()
    save_project(args.project, data)
    print(f"✅ Result saved for {stage_id}")


def cmd_reset(args):
    """Reset stage/task(s) to pending."""
    data = load_project(args.project)
    ensure_stage_mode(data, "reset")

    if args.all:
        targets = list(data["stages"].keys())
    elif args.stage:
        targets = [args.stage]
    else:
        print("Error: specify a stage or use --all", file=sys.stderr)
        sys.exit(1)

    for stage_id in targets:
        if stage_id not in data["stages"]:
            continue
        data["stages"][stage_id]["status"] = "pending"
        data["stages"][stage_id]["startedAt"] = None
        data["stages"][stage_id]["completedAt"] = None
        data["stages"][stage_id]["output"] = ""
        data["stages"][stage_id]["logs"].append({
            "time": now_iso(),
            "event": "reset to pending",
        })

    if not is_dag(data):
        data["currentStage"] = data["pipeline"][0] if data.get("pipeline") else None
    data["status"] = "active"
    data["updated"] = now_iso()
    save_project(args.project, data)
    print(f"🔄 Reset: {', '.join(targets)}")


def cmd_history(args):
    """Show log history for a stage/task."""
    data = load_project(args.project)
    ensure_stage_mode(data, "history")
    stage_id = args.stage

    if stage_id not in data["stages"]:
        print(f"Error: stage '{stage_id}' not found", file=sys.stderr)
        sys.exit(1)

    logs = data["stages"][stage_id]["logs"]
    if not logs:
        print(f"No logs for {stage_id}")
        return

    print(f"📜 History for {stage_id}:")
    for entry in logs:
        print(f"  [{entry['time']}] {entry['event']}")


def cmd_graph(args):
    """Show DAG dependency graph."""
    data = load_project(args.project)
    ensure_stage_mode(data, "graph")

    if not is_dag(data):
        print("Graph view is only for DAG mode projects.")
        return

    status_icons = {
        "pending": "⬜",
        "in-progress": "🔄",
        "done": "✅",
        "failed": "❌",
        "skipped": "⏭️",
    }

    # Find roots (no deps)
    roots = [tid for tid, t in data["stages"].items() if not t.get("dependsOn")]
    # Find what each task unblocks
    children = {tid: [] for tid in data["stages"]}
    for tid, t in data["stages"].items():
        for dep in t.get("dependsOn", []):
            if dep in children:
                children[dep].append(tid)

    print(f"📋 {data['project']} — DAG Graph\n")

    visited = set()
    def print_tree(tid, prefix="", is_last=True):
        if tid in visited:
            icon = status_icons.get(data["stages"][tid]["status"], "❓")
            print(f"{prefix}{'└─' if is_last else '├─'} {icon} {tid} (↑ see above)")
            return
        visited.add(tid)

        task = data["stages"][tid]
        icon = status_icons.get(task["status"], "❓")
        connector = "└─" if is_last else "├─"
        agent = task.get("agent", "?")
        print(f"{prefix}{connector} {icon} {tid} [{agent}]")

        kids = children.get(tid, [])
        for i, child in enumerate(kids):
            child_prefix = prefix + ("   " if is_last else "│  ")
            print_tree(child, child_prefix, i == len(kids) - 1)

    for i, root in enumerate(roots):
        print_tree(root, "", i == len(roots) - 1)

    # Show orphans (tasks with deps that aren't reachable from roots)
    orphans = set(data["stages"].keys()) - visited
    if orphans:
        print(f"\n  ⚠️  Unreachable tasks: {', '.join(orphans)}")

    # Summary
    all_tasks = list(data["stages"].values())
    done = sum(1 for t in all_tasks if t["status"] in ("done", "skipped"))
    total = len(all_tasks)
    bar = "█" * done + "░" * (total - done)
    print(f"\n  Progress: [{bar}] {done}/{total}")


def cmd_list(args):
    """List all projects."""
    os.makedirs(TASKS_DIR, exist_ok=True)
    files = [f for f in os.listdir(TASKS_DIR) if f.endswith(".json")]
    if not files:
        print("No projects found.")
        return

    for f in sorted(files):
        name = f.replace(".json", "")
        try:
            with open(os.path.join(TASKS_DIR, f)) as fh:
                data = json.load(fh)
            goal = data.get("goal", "")[:50]
            status = data.get("status", "unknown")
            mode = data.get("mode", "linear")
            done = sum(1 for t in data.get("stages", {}).values()
                       if t.get("status") in ("done", "skipped"))
            total = len(data.get("stages", {}))
            print(f"  {name} [{status}] ({done}/{total}) mode={mode} {goal}")
        except Exception:
            print(f"  {name} [error reading]")


# ── Main ────────────────────────────────────────────────────────────

def main():
    parser = argparse.ArgumentParser(description="Team Tasks — multi-agent pipeline & DAG manager")
    sub = parser.add_subparsers(dest="command", help="Command")

    # init
    p = sub.add_parser("init", help="Create a new project")
    p.add_argument("project", help="Project name (slug)")
    p.add_argument("--goal", "-g", help="Project goal description")
    p.add_argument("--mode", "-m", choices=["linear", "dag", "debate"], default="linear",
                   help="Pipeline mode: linear (sequential), dag (dependency graph), or debate")
    p.add_argument("--pipeline", "-p", help="Comma-separated agent order (linear mode only)")
    p.add_argument("--workspace", "-w", help="Shared workspace path for all agents")
    p.add_argument("--force", "-f", action="store_true", help="Overwrite existing project")

    # add (dag only)
    p = sub.add_parser("add", help="Add a task to DAG project")
    p.add_argument("project", help="Project name")
    p.add_argument("task_id", help="Task ID (unique)")
    p.add_argument("--agent", "-a", help="Agent to assign (defaults to task_id)")
    p.add_argument("--depends", "-d", help="Comma-separated dependency task IDs")
    p.add_argument("--desc", help="Task description")

    # add-debater (debate only)
    p = sub.add_parser("add-debater", help="Add a debater to debate project")
    p.add_argument("project", help="Project name")
    p.add_argument("agent_id", help="Debater agent ID")
    p.add_argument("--role", "-r", help="Debater role/perspective")

    # round (debate only)
    p = sub.add_parser("round", help="Debate round actions")
    p.add_argument("project", help="Project name")
    p.add_argument("action", choices=["start", "collect", "cross-review", "synthesize"],
                   help="Round action")
    p.add_argument("agent_id", nargs="?", help="Debater agent ID (collect only)")
    p.add_argument("content", nargs="?", help="Response/review text (collect only)")

    # status
    p = sub.add_parser("status", help="Show project status")
    p.add_argument("project", help="Project name")
    p.add_argument("--json", "-j", action="store_true", help="Output raw JSON")

    # assign
    p = sub.add_parser("assign", help="Set task for a stage")
    p.add_argument("project", help="Project name")
    p.add_argument("stage", help="Stage/task ID")
    p.add_argument("task", help="Task description")

    # update
    p = sub.add_parser("update", help="Update stage/task status")
    p.add_argument("project", help="Project name")
    p.add_argument("stage", help="Stage/task ID")
    p.add_argument("status", help="New status: pending|in-progress|done|failed|skipped")

    # next (linear)
    p = sub.add_parser("next", help="Get next stage (linear) or ready tasks (dag)")
    p.add_argument("project", help="Project name")
    p.add_argument("--json", "-j", action="store_true", help="Output JSON")

    # ready (dag)
    p = sub.add_parser("ready", help="Get all dispatchable tasks (dag mode)")
    p.add_argument("project", help="Project name")
    p.add_argument("--json", "-j", action="store_true", help="Output JSON")

    # log
    p = sub.add_parser("log", help="Add log entry")
    p.add_argument("project", help="Project name")
    p.add_argument("stage", help="Stage/task ID")
    p.add_argument("message", help="Log message")

    # result
    p = sub.add_parser("result", help="Set stage/task output")
    p.add_argument("project", help="Project name")
    p.add_argument("stage", help="Stage/task ID")
    p.add_argument("output", help="Output/result text")

    # reset
    p = sub.add_parser("reset", help="Reset stage/task(s)")
    p.add_argument("project", help="Project name")
    p.add_argument("stage", nargs="?", help="Stage to reset (or --all)")
    p.add_argument("--all", "-a", action="store_true", help="Reset all")

    # history
    p = sub.add_parser("history", help="Show stage/task log history")
    p.add_argument("project", help="Project name")
    p.add_argument("stage", help="Stage/task ID")

    # graph (dag)
    p = sub.add_parser("graph", help="Show DAG dependency tree")
    p.add_argument("project", help="Project name")

    # list
    sub.add_parser("list", help="List all projects")

    args = parser.parse_args()
    if not args.command:
        parser.print_help()
        sys.exit(1)

    cmds = {
        "init": cmd_init,
        "add": cmd_add,
        "add-debater": cmd_add_debater,
        "round": cmd_round,
        "status": cmd_status,
        "assign": cmd_assign,
        "update": cmd_update,
        "next": cmd_next,
        "ready": cmd_ready,
        "log": cmd_log,
        "result": cmd_result,
        "reset": cmd_reset,
        "history": cmd_history,
        "graph": cmd_graph,
        "list": cmd_list,
    }
    cmds[args.command](args)


if __name__ == "__main__":
    main()


path: SPEC.md
content: # Agent Teams Enhancement Spec

## Goal
Enhance the existing `team-tasks` skill at `/home/ubuntu/clawd/skills/team-tasks/` to support two new modes that replicate Claude Code's Agent Teams capabilities:

1. **Shared workspace mode** — multiple agents work on the same codebase
2. **Debate mode** — send the same question to N agents, collect responses, cross-evaluate

## Current State
- `scripts/task_manager.py` already supports `linear` and `dag` modes
- Data files in `/home/ubuntu/clawd/data/team-tasks/<project>.json`
- Agents are dispatched via OpenClaw's `sessions_send` and results tracked in JSON

## Requirements

### 1. New mode: `debate`

Add a new mode `--mode debate` to `task_manager.py init`.

**Debate workflow:**
1. `init <project> --mode debate -g "question"` — creates project with a question/topic
2. `add-debater <project> <agent-id> [--role "perspective"]` — add agents as debaters, each with an optional role/perspective
3. `round <project> start` — generates dispatch info for round 1 (each debater gets the question + their role)
4. After collecting responses: `round <project> collect <agent-id> "response"` — save each debater's response
5. `round <project> cross-review` — generates cross-review prompts (each debater reviews others' responses)
6. After cross-review: `round <project> collect <agent-id> "review"` — save reviews
7. `round <project> synthesize` — outputs all positions + reviews for final synthesis
8. `status <project>` — shows debate state, rounds, and positions

**Data structure for debate mode:**
```json
{
  "project": "security-review",
  "mode": "debate",
  "goal": "Review security of auth module",
  "debaters": {
    "agent-1": { "role": "security expert", "responses": [] },
    "agent-2": { "role": "performance analyst", "responses": [] }
  },
  "rounds": [
    {
      "type": "initial",
      "status": "done",
      "responses": { "agent-1": "...", "agent-2": "..." }
    },
    {
      "type": "cross-review", 
      "status": "in-progress",
      "responses": {}
    }
  ],
  "currentRound": 1,
  "status": "active"
}
```

### 2. Shared workspace tracking

Add `--workspace <path>` to `init` for all modes. This records the shared workspace path in the project JSON so all agents work in the same directory.

For `dag` and `linear` modes, add the workspace path to `ready`/`next` output so the dispatcher knows where to point agents.

### 3. Cross-review prompt generation

The `round <project> cross-review` command should output structured prompts for each debater:

```
Agent: agent-1 (security expert)
Your previous response: <agent-1's response>

Other debaters' responses:
- agent-2 (performance analyst): <agent-2's response>
- agent-3 (testing specialist): <agent-3's response>

Task: Review the other responses. Do you agree or disagree? What did they miss? Update your position if needed.
```

## Constraints
- Keep backward compatibility with existing `linear` and `dag` modes
- Same CLI pattern as existing commands
- Same data directory: `/home/ubuntu/clawd/data/team-tasks/`
- Python 3.12+, no external dependencies beyond stdlib
- Keep code clean and readable — this will be used by AI agents

## Files to modify
- `/home/ubuntu/clawd/skills/team-tasks/scripts/task_manager.py` — add debate mode commands

## Testing
After implementing, verify with this test scenario:
```bash
TM="python3 /home/ubuntu/clawd/skills/team-tasks/scripts/task_manager.py"

# Create debate
$TM init security-debate --mode debate -g "Review the auth module at /tmp/auth.py for security vulnerabilities"

# Add 3 debaters
$TM add-debater security-debate code-agent --role "security expert focused on injection attacks"
$TM add-debater security-debate test-agent --role "QA engineer focused on edge cases"
$TM add-debater security-debate monitor-bot --role "ops engineer focused on deployment risks"

# Start round 1
$TM round security-debate start

# Collect responses
$TM round security-debate collect code-agent "Found SQL injection in login()"
$TM round security-debate collect test-agent "Missing input validation on email field"
$TM round security-debate collect monitor-bot "No rate limiting on auth endpoints"

# Generate cross-review prompts
$TM round security-debate cross-review

# Collect cross-reviews
$TM round security-debate collect code-agent "Agree with test-agent on validation. monitor-bot's rate limiting is critical."
$TM round security-debate collect test-agent "code-agent's SQL injection is most severe. Adding rate limit tests."
$TM round security-debate collect monitor-bot "Both findings are valid. Recommending WAF as additional layer."

# Synthesize
$TM round security-debate synthesize

# Check status throughout
$TM status security-debate
```


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)