
Team Tasks
- 70 installs
- 447 repo stars
- Updated February 9, 2026
- win4r/team-tasks
Helps with ai & agent building tasks.
About
team-tasks is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- team-tasks
- AI & Agent Building
- AI-coding skill
Team Tasks by the numbers
- 70 all-time installs (skills.sh)
- +1 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #5,726 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/win4r/team-tasks --skill team-tasksAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 70 |
|---|---|
| repo stars | ★ 447 |
| Last updated | February 9, 2026 |
| Repository | win4r/team-tasks ↗ |
What it does
Helps with ai & agent building tasks.
Files
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]` |
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 dispatchin-progress— agent is workingdone— stage completedfailed— stage failed (pipeline blocks)skipped— intentionally skipped
Pipeline Workflow (Mode A: Linear)
Step 1: Initialize Project
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
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:
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:
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
python3 scripts/task_manager.py status my-projectOutput:
📋 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/4Agent 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 endpointsDelivery 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
python3 scripts/task_manager.py init my-project -m dag -g "Build REST API with parallel workstreams"Step 2: Add Tasks with Dependencies
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
$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
$TM ready my-project # Shows all tasks whose deps are metFor 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 doneKey DAG Features
- Parallel dispatch:
readyreturns ALL tasks whose deps are satisfied — dispatch them simultaneously - Dep outputs forwarding:
ready --jsonincludesdepOutputs— previous stage results to pass to agents - Auto-unblock notification: When a task completes, shows which tasks are newly unblocked
- Cycle detection:
addrejects tasks that would create circular dependencies - Partial failure: If one task fails, unrelated branches continue; only downstream tasks block
- Graph visualization:
graphshows tree view with status icons and dedup markers
Custom Pipelines
Linear (Mode A)
# 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)
# 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.
# ❌ 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(notproject1) - Save results — always
resultbeforeupdate done; this is the inter-agent context - Log liberally —
logis cheap; helps debug failed pipelines - Reset and retry —
reset --allfor 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)
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
Gap Analysis: team-tasks vs Official Claude Code Agent Teams
Date: 2026-02-08
Scope
Compared these files:
AGENT_TEAMS_OFFICIAL_DOCS.mdSKILL.mdscripts/task_manager.pySPEC.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.
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 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 withTEAM_TASKS_DIRenv var)
Installation
# 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 --helpFor OpenClaw skill integration, copy to your skills directory:
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.
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-apiOutput 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/4Mode B: DAG (Dependency Graph)
Tasks declare dependencies and run in parallel when deps are met.
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-featureGraph 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# 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 tasksKey DAG features:
readyreturns ALL tasks whose deps are satisfied — dispatch them simultaneouslyready --jsonincludesdepOutputs— 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.
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 synthesisDebate 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\ |
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\ |
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
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 existingIntegration with OpenClaw
This tool is designed as an OpenClaw Skill. 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. RepeatDispatch 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 completeCommon Pitfalls
⚠️ Linear mode: Stage ID = agent name, NOT a number
# ❌ 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
# ❌ 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
# ❌ 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 startData Storage
Project files are stored as JSON at:
/home/ubuntu/clawd/data/team-tasks/<project>.jsonOverride with environment variable:
export TEAM_TASKS_DIR=/custom/pathProject 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 documentationLicense
MIT
#!/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()
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.pyalready supportslinearanddagmodes- Data files in
/home/ubuntu/clawd/data/team-tasks/<project>.json - Agents are dispatched via OpenClaw's
sessions_sendand 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:
{
"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
linearanddagmodes - 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:
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