Multi-agent project management for OpenClaw. Run agent teams that plan, assign, execute, and track work together. Includes a Control UI plugin tab for visual project tracking and a right-edge Team Chat drawer for live agent activity.
This skill enables a coordinator agent to manage projects across multiple specialist agents:
User → Coordinator (Koda) → dispatches tasks via sessions_spawn
├── @researcher → web research, data analysis
├── @coder → writing code, building features
├── @writer → copy, documentation, content
└── ... any configured agent
Each agent works independently with their own tools and workspace. The coordinator tracks progress, manages dependencies, and advances the project.
A full project dashboard registered as a plugin tab in the Control sidebar under the "Control" group. Shows project cards, task boards with phase sections, team overview, and progress stats.
A slide-out panel accessible from a vertical "Team" tab fixed to the right edge of the screen. Shows:
Add to openclaw.json:
{
"tools": {
"agentToAgent": {
"enabled": true,
"allow": ["*"]
}
}
}
Add subagents.allowAgents to the coordinator agent's list entry (NOT on agents.defaults):
{
"agents": {
"list": [
{
"id": "main",
"default": true,
"name": "Koda",
"subagents": {
"allowAgents": ["*"]
}
}
]
}
}
> ⚠️ Critical: allowAgents must be on the agent's own entry in agents.list. Setting it on agents.defaults.subagents does NOT work. The code reads resolveAgentConfig(cfg, requesterAgentId)?.subagents?.allowAgents which resolves the per-agent config, not defaults.
The Projects tab requires installing a gateway plugin. This involves 4 registration points in the OpenClaw source:
Create src/plugin-sdk/team-projects.ts:
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
export type {
OpenClawPluginApi,
PluginViewRegistration,
} from "../plugins/types.js";
Create extensions/team-projects/openclaw.plugin.json:
{
"id": "team-projects",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {}
}
}
Create extensions/team-projects/index.ts — see gateway-plugin/index.ts.
tsdown.config.ts — Add "team-projects" to the pluginSdkEntrypoints array.
src/plugins/loader.ts — Add to the pluginSdkScopedAliasEntries array:
{
subpath: "team-projects",
srcFile: "team-projects.ts",
distFile: "team-projects.js",
},
scripts/write-plugin-sdk-entry-dts.ts — Add "team-projects" to the entrypoints array.
package.json — Add to the exports map:
"./plugin-sdk/team-projects": {
"types": "./dist/plugin-sdk/team-projects.d.ts",
"default": "./dist/plugin-sdk/team-projects.js"
},
{
"plugins": {
"allow": ["telegram", "discord", "team-projects"],
"entries": {
"team-projects": {
"enabled": true
}
}
}
}
Copy gateway-plugin/team-projects-view.ts to ui/src/ui/views/team-projects.ts.
Copy gateway-plugin/team-chat-drawer.ts to ui/src/ui/views/team-chat-drawer.ts.
> ⚠️ Critical: No Shadow DOM — The OpenClaw app uses createRenderRoot() { return this; }, so Lit css tagged templates are NOT applied. All styles must be embedded as inline tags in the rendered HTML. The team-chat-drawer uses an inline block inside renderTeamChatEdgeTab() so styles are present even when the drawer is closed.
See gateway-plugin/app-render-patch.ts for the integration points:
renderTeamChatDrawer and renderTeamChatEdgeTab from ./views/team-chat-drawer.ts, add ${renderPluginTabContent(state)}, add the edge tab and drawer rendersrenderPluginTabContent() functionSee gateway-plugin/app-gateway-patch.ts for the integration points:
uiPluginRegistry from ./plugins/registry.tsrenderTeamProjects from ./views/team-projects.tsonHello, after applySnapshot(), call registerTeamProjectsPlugin()registerTeamProjectsPlugin() functionIn ui/src/ui/app-view-state.ts, add to AppViewState:
teamChatOpen: boolean;
teamChatProject: unknown;
teamChatSessions: unknown[];
teamChatActivity: unknown[];
teamChatLoading: boolean;
teamChatInput: string;
teamChatTasksCollapsed: boolean;
In ui/src/ui/app-settings.ts, add teamChatOpen?: boolean to SettingsHost type, and add defensive cleanup in applyTabSelection():
} else if (host.teamChatOpen) {
host.teamChatOpen = false;
}
cd ~/openclaw
pnpm build # Backend (gateway + plugin SDK)
pnpm ui:build # Control UI
openclaw gateway restart
Hard-refresh the browser (Ctrl+Shift+R) to load the new UI assets.
Tell the coordinator agent what you want to build. It will:
Example prompt:
> "Create a team project to redesign our company website. @researcher should analyze competitors, @writer should draft copy, and @coder should build it in React. Priority: launch in 2 weeks."
The coordinator uses CLI tools to manage the project board:
# Create a project
node {{SKILL_DIR}}/scripts/project-store.js create-project \
--name "Website Redesign" \
--description "Full redesign of company website" \
--coordinator main \
--agents "researcher,writer,coder"
# Add phases (ordered)
node {{SKILL_DIR}}/scripts/project-store.js add-phase \
--project PROJECT_ID --name "Research" --description "Competitive analysis"
# Add tasks with assignments and dependencies
node {{SKILL_DIR}}/scripts/project-store.js add-task \
--project PROJECT_ID --phase PHASE_ID \
--title "Analyze top 5 competitors" \
--description "Research competitor websites..." \
--assignee researcher --priority high
node {{SKILL_DIR}}/scripts/project-store.js add-task \
--project PROJECT_ID --phase PHASE_ID \
--title "Write homepage hero copy" \
--assignee writer --priority high \
--dependsOn "task_abc123"
The orchestrator identifies ready tasks (dependencies met, not yet dispatched):
# See what's ready to dispatch
node {{SKILL_DIR}}/scripts/orchestrator.js plan PROJECT_ID
Then dispatch via OpenClaw's native sessions_spawn:
sessions_spawn(
agentId: "researcher",
task: "Research the top 5 competitors in the AI SaaS space...",
label: "task_abc123"
)
Agents can communicate using @-mentions:
sessions_send)# View project stats
node {{SKILL_DIR}}/scripts/project-store.js stats --id PROJECT_ID
# View work breakdown structure
node {{SKILL_DIR}}/scripts/project-store.js wbs --id PROJECT_ID
# Check and advance completed phases
node {{SKILL_DIR}}/scripts/orchestrator.js advance PROJECT_ID
# Mark task in progress
node {{SKILL_DIR}}/scripts/project-store.js update-task \
--project PROJECT_ID --id TASK_ID --status in_progress
# Mark task done
node {{SKILL_DIR}}/scripts/project-store.js update-task \
--project PROJECT_ID --id TASK_ID --status done
# Add a comment
node {{SKILL_DIR}}/scripts/project-store.js add-comment \
--project PROJECT_ID --task TASK_ID \
--author main --text "Looks good, merging now"
project
├── id, name, slug, description
├── status: planning | active | paused | completed | archived
├── coordinator: agentId
├── agents: [agentId, ...]
└── phases[]
├── id, name, description, order
├── status: pending | active | completed
└── tasks[]
├── id, title, description
├── assignee: agentId
├── priority: critical | high | medium | low
├── status: todo | in_progress | blocked | review | done
├── dependsOn: [taskId, ...]
├── sessionKey: (set when dispatched)
├── artifacts: [paths/URLs]
└── comments[]
~/.openclaw/workspace/team-projects/projects.json~/.openclaw/workspace/team-projects/chat_PROJECT_ID.jsonl~/.openclaw/workspace/team-projects/orchestrator-state.json┌─────────────┐ sessions_spawn ┌──────────────┐
│ Coordinator │ ──────────────────────→ │ Worker Agent │
│ (Koda) │ │ (researcher) │
│ │ ←── push completion ──── │ │
│ project- │ └──────────────┘
│ store.js │ sessions_spawn ┌──────────────┐
│ │ ──────────────────────→ │ Worker Agent │
│ orchestr- │ │ (coder) │
│ ator.js │ ←── push completion ──── │ │
│ │ └──────────────┘
│ UI plugin │ sessions_send ┌──────────────┐
│ tab │ ←────────────────────── │ Worker Agent │
└─────────────┘ (inter-agent msg) │ (writer) │
└──────────────┘
subagent-announce system delivers completion events automatically.dependsOn are all done.sessions_spawn, sessions_send, and agents_list tools. No custom transport.uiPluginRegistry (client-side). Team Chat drawer uses inline tags for CSS (no Shadow DOM).app-gateway.ts during onHello. This is reliable and avoids depending on gateway-side plugin infrastructure.When adding a new gateway plugin to OpenClaw, you must register it in 4 places:
| File | What to add |
|---|---|
| ------ | ------------- |
src/plugin-sdk/ | SDK type entry (exports OpenClawPluginApi etc.) |
src/plugins/loader.ts → pluginSdkScopedAliasEntries | Jiti alias so runtime can resolve openclaw/plugin-sdk/ |
tsdown.config.ts → pluginSdkEntrypoints | Build entry so dist/plugin-sdk/ gets generated |
scripts/write-plugin-sdk-entry-dts.ts | DTS entry generator |
package.json → exports → ./plugin-sdk/ | Package subpath export |
Missing any one of these causes Cannot find module errors at runtime.
OpenClaw's OpenClawApp uses createRenderRoot() { return this; } — it renders directly to the DOM without Shadow DOM. This means:
css tagged templates (used with static styles) are never applied tags in the HTML returned by render functionsteamChatDrawerStyles export was originally dead code until fixed to use inline stylesThe coordinator agent should include templates/coordinator-prompt.md in its system prompt (or SOUL.md). This gives it the commands and workflow for managing projects.
Worker agents should include templates/worker-prompt.md for task completion protocols.
allowAgents must be per-agentSetting agents.defaults.subagents.allowAgents: ["*"] does NOT work. The code at agents-list-tool.ts:49 reads:
const allowAgents = resolveAgentConfig(cfg, requesterAgentId)?.subagents?.allowAgents ?? [];
This resolves the per-agent config entry, not defaults. You must add it to the agent's entry in agents.list.
dependsOn normalizationThe CLI passes --dependsOn task_abc as a string, not an array. The project store and orchestrator both normalize with Array.isArray() checks. If you create tasks programmatically, always pass an array.
After rebuilding the UI (pnpm ui:build) and restarting the gateway, browsers may cache the old JS bundle. Always hard-refresh (Ctrl+Shift+R) after UI changes.
The icon used for the Projects tab must exist in ui/src/ui/icons.ts. Available icons include: fileText, folder, barChart, zap, brain, settings, plug, etc. Custom icon names like clipboard-list won't work — use fileText instead.
registerView → hello snapshot → loadFromGateway) is scaffolded but not fully wired. Plugin tabs are registered client-side instead.team-projects/
├── SKILL.md # This file
├── scripts/
│ ├── project-store.js # CRUD for projects, phases, tasks, comments
│ ├── orchestrator.js # Dispatch planning, dependency gates, phase advancement
│ ├── task-router.js # @-mention parsing and dispatch planning
│ └── gateway-handlers.js # HTTP API adapter (optional)
├── ui/
│ ├── project-sidebar.js # Sidebar panel component (standalone)
│ └── team-chat.js # Multi-agent chat component (standalone)
├── gateway-plugin/
│ ├── index.ts # Gateway plugin source (extensions/team-projects/)
│ ├── openclaw.plugin.json # Plugin manifest
│ ├── plugin-sdk-entry.ts # SDK type entry (src/plugin-sdk/team-projects.ts)
│ ├── team-projects-view.ts # UI view renderer (ui/src/ui/views/team-projects.ts)
│ ├── team-chat-drawer.ts # Team Chat drawer + edge tab (ui/src/ui/views/team-chat-drawer.ts)
│ ├── app-render-patch.ts # Patch for app-render.ts (plugin tab catch-all + drawer)
│ ├── app-gateway-patch.ts # Patch for app-gateway.ts (renderer registration)
│ └── BUILD_REGISTRATION.md # Build pipeline registration checklist
├── templates/
│ ├── coordinator-prompt.md # System prompt for coordinator agent
│ └── worker-prompt.md # System prompt fragment for worker agents
└── references/
├── example-config.json # Example openclaw.json for team setup
└── walkthrough.md # Step-by-step usage guide
共 2 个版本