Squad Control
Integrate with Squad Control kanban for AI agent task orchestration. ⚠️ Security note: This skill handles GitHub tokens and API keys by design — it clones pr...
技能说明
name: squad-control version: 1.2.1 homepage: https://squadcontrol.ai env: SC_API_URL: description: Base URL of your Squad Control instance (e.g. https://www.squadcontrol.ai) required: true SC_API_KEY: description: > API key from Squad Control Settings → API Keys. Can be workspace-scoped (single workspace) or account-scoped (all workspaces). Account-scoped keys are recommended for multi-workspace setups — tasks will include an embedded workspace object with all config needed. required: true GITHUB_TOKEN: description: > GitHub Personal Access Token for PR creation and repo cloning. When not set manually, the skill uses the token returned by the Squad Control API (workspace.githubToken) — configured in Squad Control Settings → Project Repository. required: false SC_REVIEWER_AGENT_ID: description: Optional explicit reviewer agent id. If set, review routing uses this id first. required: false SC_DEFAULT_BRANCH: description: Optional default git base branch override (fallback: workspace default branch, then main). required: false description: > Integrate with Squad Control kanban for AI agent task orchestration.
⚠️ Security note: This skill handles GitHub tokens and API keys by design — it clones private repos, pushes code, and creates PRs on your behalf. Review setup.md for security guidance before installing. Only connect to Squad Control instances you trust.
Use when:
- A cron fires to check for pending tasks
- You need to pick up, dispatch, or complete a kanban task
- You need to create a PR and route it for code review
- You need to spawn a sub-agent to work on an assigned task
- You need to report task success or failure back to Squad Control
- You need to create tasks programmatically (break down work)
Don't use when:
- The user is asking a general question (just answer directly)
- You're doing work that isn't tracked in Squad Control
- You're working inside a sub-agent session (you're already dispatched — just do the work)
- The user wants to manage agents/settings in the UI (direct them to squadcontrol.ai)
Inputs: Squad Control API credentials, task data from /api/tasks/pending Outputs: PRs on GitHub, task status updates in Squad Control, review verdicts
Squad Control Integration
Orchestrate AI agent tasks from Squad Control's kanban board.
Quick Reference
- Setup:
references/setup.md - Full API:
references/api.md - PR template:
references/pr-template.md - Review checklist:
references/review-checklist.md - Poll result schema:
references/poll-result.schema.json - Migration notes:
references/migration-notes.md
Required env vars: SC_API_URL, SC_API_KEY
Task Polling Flow
When a cron fires to check for tasks:
- Run
~/.openclaw/skills/squad-control/scripts/poll-tasks.sh(requiresSC_API_URLandSC_API_KEYenv vars)- Script is multi-workspace safe: it does not export top-level
REPO_URL/GITHUB_TOKEN; resolve repo/token per task fromtask.workspace(or fallback top-level workspace for legacy payloads). - Script includes overlap protection via a lock/lease guard (stale lock auto-expires).
- Script uses retry/backoff/timeouts for API calls.
- Script is multi-workspace safe: it does not export top-level
- If output is
HEARTBEAT_OK→ no work, stop - If output contains
POLL_RESULT:→ parse the JSON envelope after it:pending→ follow Pickup & Dispatch below for each taskreview.tasks→ follow Review Dispatch below for each taskstuck.tasks→ follow Stuck Task Recovery below for each task
Alternatively, call the APIs directly:
- Pending:
curl -sL "${SC_API_URL}/api/tasks/pending" -H "x-api-key: ${SC_API_KEY}" - Review:
curl -sL "${SC_API_URL}/api/tasks/list?status=review" -H "x-api-key: ${SC_API_KEY}"
Parse workspace config from the response (see Multi-Workspace Response Handling below).
Multi-Workspace Response Handling
/api/tasks/pending can return tasks from multiple workspaces when using an account-level API key. Each task includes an embedded workspace object with all config needed to work on it.
Response shape — workspace-scoped key (legacy / single-workspace):
{
"workspace": { "_id": "wsId", "name": "MyApp", "repoUrl": "...", "githubToken": "..." },
"tasks": [{ "_id": "taskId", "title": "...", "agent": { ... } }]
}
→ workspace is at the top level; tasks do not have their own workspace object.
Response shape — account-scoped key (multi-workspace):
{
"tasks": [
{
"_id": "taskId",
"title": "...",
"workspace": {
"_id": "wsId",
"name": "MyApp",
"repoUrl": "https://github.com/org/repo",
"githubToken": "ghp_...",
"agentConcurrency": 3
},
"agent": { ... }
}
]
}
→ Each task carries its own workspace object. Tasks from different workspaces may appear in the same response.
Agent fields — always nested under task.agent (never flat on task root):
{
"agent": {
"_id": "agentId",
"name": "Cody",
"role": "Developer",
"model": "anthropic/claude-sonnet-4-6",
"soulMd": "..."
}
}
⚠️ Do NOT use
task.agentName— that field does not exist. Always usetask.agent.name,task.agent.model,task.agent.soulMd,task.agent._id.
Handling both shapes (backward compatible):
// For each task:
// - If task.workspace is present → use it directly
// - If not → use the top-level workspace from the response
const wsConfig = task.workspace ?? response.workspace;
const repoUrl = wsConfig.repoUrl;
const githubToken = wsConfig.githubToken;
const concurrencyLimit = wsConfig.agentConcurrency ?? 2;
Concurrency per workspace: When tasks from multiple workspaces are returned, apply agentConcurrency per workspace independently. Do not count agents running for workspace A against workspace B's limit.
// Group by workspace, then dispatch up to concurrency limit for each
const byWorkspace = groupBy(tasks, t => (t.workspace ?? topLevelWs)._id);
for (const [wsId, wsTasks] of Object.entries(byWorkspace)) {
const ws = wsTasks[0].workspace ?? topLevelWs;
const limit = ws.agentConcurrency ?? 2;
const running = countRunningAgentsFor(wsId);
const slots = Math.max(0, limit - running);
for (const task of wsTasks.slice(0, slots)) {
dispatch(task, ws);
}
}
Stuck Task Recovery
Run two checks every cron cycle:
Check 1 — Tasks stuck in "working" with a PR deliverable:
curl -sL "${SC_API_URL}/api/tasks/list?status=working" -H "x-api-key: ${SC_API_KEY}"
For each working task where deliverables contains a PR entry, startedAt exists, and startedAt is more than 30 minutes ago (and no recent activity signal) → auto-rescue by moving to review.
Before rescue, apply idempotency guards:
- Skip if task already has an
autoRescuedAtmarker in metadata (if available) - Skip if thread already contains an auto-rescue message for this task
- After successful rescue, write marker
autoRescuedAt=<now>(or equivalent) to prevent duplicate rescues in later cron cycles
curl -sL -X POST "${SC_API_URL}/api/tasks/set-review" \
-H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \
-d "{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${ASSIGNED_AGENT_ID}\", \"result\": \"Auto-rescued: sub-agent completed work but did not transition status.\", \"deliverables\": ${EXISTING_DELIVERABLES}}"
Only post thread message if set-review actually changed the task state to review: "Auto-moved to review — sub-agent completed PR but didn't call set-review."
Check 2 — Tasks marked "done" with an unmerged/open PR:
curl -sL "${SC_API_URL}/api/tasks/list?status=done" -H "x-api-key: ${SC_API_KEY}"
# Filter to tasks completed in the last 2 hours that have a PR deliverable
For each recently-done task with a PR deliverable, verify the PR is actually merged:
# Extract owner/repo from workspace.repoUrl
# Extract PR number from deliverable URL (e.g. https://github.com/org/repo/pull/123 → 123)
curl -sL -H "Authorization: token ${GITHUB_TOKEN}" \
"https://api.github.com/repos/${owner}/${repo}/pulls/${PR_NUMBER}" | grep -o '"merged":[^,]*'
If "merged":false (PR still open) → the agent skipped review. Re-open for Hawk.
Idempotency guard before creating a review task:
- Search existing tasks for one already referencing this PR URL/number in
review|assigned|working - Only create a new review task if none exists
# Create a review task for Hawk
curl -sL -X POST "${SC_API_URL}/api/tasks/create" \
-H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \
-d "{\"title\": \"Review PR #${PR_NUMBER}: ${TASK_TITLE}\", \"description\": \"Agent marked task done but PR is still open and unmerged. Please review and merge if approved.\\n\\nPR: ${PR_URL}\", \"assignedAgentId\": \"${REVIEWER_AGENT_ID}\", \"workspaceId\": \"${WORKSPACE_ID}\", \"priority\": \"high\"}"
Post a warning to the original task thread only when a new review task is created: "⚠️ Task was marked done but PR #N is unmerged. Created review task for Hawk."
Review Dispatch
When review tasks are found, resolve the reviewer deterministically:
- If
SC_REVIEWER_AGENT_IDis set, use it directly - Else query agents and pick exact role match
Code Reviewer - Else fallback to name
Hawkor role containingReviewer
curl -sL "${SC_API_URL}/api/agents" -H "x-api-key: ${SC_API_KEY}"
For each review task (has PR deliverable, pickedUpAt not set):
Do NOT call /api/tasks/pickup — the state machine blocks review → working transitions. Instead, spawn the reviewer directly and let them call /api/tasks/review (verdict) which transitions review → done or review → assigned.
Spawn the reviewer agent using the Review Flow template below, passing the task ID and all context directly.
Squad Lead Tasks — Merge & Complete
When the task's assigned agent is the Squad Lead (role contains "Lead" or "Orchestrator"), it means Hawk approved a PR and it's ready to merge. Do NOT just mark it done — merge the PR first.
# 1. Pick up the task
curl -sL -X POST "${SC_API_URL}/api/tasks/pickup" \
-H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \
-d "{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${SQUAD_LEAD_ID}\"}"
# Response includes workspace.repoUrl, workspace.githubToken, task.deliverables
# 2. Find the PR deliverable — check type OR url, not just name
# Priority: type === "pr" first, then url containing "/pull/"
# Examples that all qualify: {type:"pr"}, {url:"…/pull/7"}, {name:"PR #7", type:"pr"}
# NEVER complete as done if any deliverable has type="pr" or url containing "/pull/"
# 3. Clone and merge (use credential helper — do NOT embed token in URL)
if [ -n "$GITHUB_TOKEN" ]; then
git -c "credential.helper=!f() { echo username=x-access-token; echo password=${GITHUB_TOKEN}; }; f" clone "$REPO_URL" /tmp/merge-repo
else
git clone "$REPO_URL" /tmp/merge-repo
fi
cd /tmp/merge-repo
git fetch origin
DEFAULT_BRANCH="${SC_DEFAULT_BRANCH:-${WORKSPACE_DEFAULT_BRANCH:-main}}"
git checkout "$DEFAULT_BRANCH" && git pull origin "$DEFAULT_BRANCH"
git merge --no-ff origin/task/${TASK_ID} -m "Merge PR #${PR_NUMBER}: ${TASK_TITLE}"
git push origin "$DEFAULT_BRANCH"
# 4. Post to thread
curl -sL -X POST "${SC_API_URL}/api/threads/send" \
-H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \
-d "{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${SQUAD_LEAD_ID}\", \"content\": \"Merged PR #${PR_NUMBER} to main.\"}"
# 5. Complete the task
curl -sL -X POST "${SC_API_URL}/api/tasks/complete" \
-H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \
-d "{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${SQUAD_LEAD_ID}\", \"result\": \"Merged PR #${PR_NUMBER} to main.\", \"status\": \"done\"}"
If merge fails (conflicts): call /api/tasks/fail with the error — don't force-merge. Post the conflict details to the thread.
If no PR deliverable: just complete the task directly.
Concurrency Limit
Before spawning anything, check how many sub-agents are already running:
subagents(action="list")
Count agents with status = "running". The concurrency limit comes from workspace.agentConcurrency (default: 2 if not set).
With a single workspace: If running agents ≥ limit, skip all spawning this cycle and reply HEARTBEAT_OK.
With multiple workspaces (account-level key): Apply the limit per workspace independently. A workspace with agentConcurrency: 3 can have up to 3 agents running regardless of what other workspaces are doing. Group tasks by workspace and check each workspace's running count separately.
Also, never dispatch more than workspace.agentConcurrency tasks per workspace per cron run. The rest will be picked up on the next run.
The workspace owner can change this limit in Squad Control → Settings → Agent Concurrency.
Pickup & Dispatch
# Pick up task (marks it in-progress in Squad Control)
curl -sL -X POST "${SC_API_URL}/api/tasks/pickup" \
-H "x-api-key: ${SC_API_KEY}" \
-H "Content-Type: application/json" \
-d "{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${AGENT_ID}\", \"branch\": \"task/${TASK_ID}\"}"
# Response includes workspace.repoUrl and workspace.githubToken
Post to the task thread that work is being dispatched:
curl -sL -X POST "${SC_API_URL}/api/threads/send" \
-H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \
-d "{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${AGENT_ID}\", \"content\": \"Picking up task. Starting work now.\"}"
Then spawn a sub-agent with the task's agent persona. Include the workspace name in the label so multi-workspace runs are easy to identify:
sessions_spawn({
task: <see Spawn Prompt Template below>,
model: agent.model,
label: "${agent.name}-${workspace.name}-${taskTitle}",
runTimeoutSeconds: 1800
})
Spawn Prompt Template
When building this prompt, resolve workspace config as follows:
- If
task.workspaceis present (account-scoped key): usetask.workspace.repoUrl,task.workspace.githubToken,task.workspace.agentConcurrency - If
task.workspaceis absent (workspace-scoped key): use the top-levelworkspacefrom the response
# Identity
${agent.soulMd}
# Squad Control Credentials (use these for ALL API calls)
SC_API_URL=${SC_API_URL}
SC_API_KEY=${SC_API_KEY}
TASK_ID=${task._id}
AGENT_ID=${agent._id}
# Repository
REPO_URL=${workspace.repoUrl}
GITHUB_TOKEN=${workspace.githubToken} # may be empty for public repos
# Clone the repo (use credential helper — do NOT embed token in URL)
if [ -n "$GITHUB_TOKEN" ]; then
git -c "credential.helper=!f() { echo username=x-access-token; echo password=${GITHUB_TOKEN}; }; f" clone "$REPO_URL" /tmp/task-repo
else
git clone "$REPO_URL" /tmp/task-repo
fi
cd /tmp/task-repo
git checkout -b task/${task._id}
# Task
**${task.title}**
${task.description}
# Git Workflow
- Small, focused commits (feat:, fix:, chore: prefixes)
- Scope changes to this task only
- Run `npx tsc --noEmit` or existing tests before finishing
# When Done — follow these steps EXACTLY, do not skip any
## 1. Commit and push
git add -A && git commit -m "feat: ${task.title}"
git push origin task/${task._id}
## 2. Create GitHub PR
curl -sL -X POST \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/${owner}/${repo}/pulls" \
-d '{"title": "${task.title}", "head": "task/${task._id}", "base": "${SC_DEFAULT_BRANCH:-${WORKSPACE_DEFAULT_BRANCH:-main}}", "body": "${summary}"}'
# Save the PR number and URL from the response
## 3. Post summary to thread
curl -sL -X POST "${SC_API_URL}/api/threads/send" \
-H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \
-d '{"taskId": "${TASK_ID}", "agentId": "${AGENT_ID}", "content": "Work complete. PR #N: ${PR_URL}\n\n${summary}"}'
## 4. If any files in convex/ were changed
# Deployment is handled by CI after merge to main.
# Do NOT run local deploy commands from this skill prompt.
# If a manual deploy is required, ask the squad lead to run it in a controlled environment.
## 5. Hand off for review (REQUIRED — NEVER call /complete if you opened a PR)
> ⚠️ CRITICAL: If you created a PR in step 2, you MUST call set-review — not complete.
> Calling /complete with an open PR bypasses code review entirely. This is a workflow violation.
> The ONLY time to call /complete directly is when there is NO PR (e.g. a research or docs-only task).
```bash
curl -sL -X POST "${SC_API_URL}/api/tasks/set-review" \
-H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \
-d '{"taskId": "${TASK_ID}", "agentId": "${AGENT_ID}", "result": "${summary}", "deliverables": [{"type": "pr", "name": "PR #N", "url": "${PR_URL}"}]}'
Verify the API response confirms status changed to "review". If it returns an error, retry once then call /fail with the error details.
If anything fails
curl -sL -X POST "${SC_API_URL}/api/tasks/fail"
-H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json"
-d '{"taskId": "${TASK_ID}", "agentId": "${AGENT_ID}", "error": "description of what went wrong"}'
### On Completion
1. Post findings/summary to task thread:
```bash
curl -sL -X POST "${SC_API_URL}/api/threads/send" \
-H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \
-d "{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${AGENT_ID}\", \"content\": \"${SUMMARY}\"}"
- Create PR via GitHub API (see
references/pr-template.md) - If a reviewer agent exists → set task to review:
curl -sL -X POST "${SC_API_URL}/api/tasks/set-review" \ -H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \ -d "{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${AGENT_ID}\", \"result\": \"${SUMMARY}\", \"deliverables\": [{\"type\": \"pr\", \"name\": \"PR #N\", \"url\": \"${PR_URL}\"}]}" - If no reviewer → complete directly:
curl -sL -X POST "${SC_API_URL}/api/tasks/complete" \ -H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \ -d "{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${AGENT_ID}\", \"result\": \"${SUMMARY}\", \"status\": \"done\"}"
On Failure
Always report failures — don't silently mark done:
curl -sL -X POST "${SC_API_URL}/api/tasks/fail" \
-H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \
-d "{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${AGENT_ID}\", \"error\": \"description of what went wrong\"}"
Review Flow
When routing to a reviewer agent, spawn them with this prompt (fill in all values):
# Identity
${reviewer.soulMd}
# Task: Review PR #${prNumber}
**Original task:** ${task.title}
**PR:** ${prUrl}
**Repo:** ${workspace.repoUrl}
**GitHub token:** ${workspace.githubToken} # may be empty for public repos
# Extract owner/repo from repoUrl
# e.g. https://github.com/org/repo -> owner=org, repo=repo
# Step 1 — Get the diff
curl -sL -H "Authorization: token ${GITHUB_TOKEN}" \
"https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/files"
# Step 2 — Review the code
Check: correctness, code quality, security, edge cases.
# Step 3 — Post review to GitHub PR (REQUIRED — not just to thread)
curl -sL -X POST \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews" \
-d '{"event": "APPROVE", "body": "<review summary>"}'
# Use "REQUEST_CHANGES" instead of "APPROVE" if changes are needed
# Step 4 — Post summary to Squad Control thread
curl -sL -X POST "${SC_API_URL}/api/threads/send" \
-H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \
-d '{"taskId": "${TASK_ID}", "agentId": "${REVIEWER_ID}", "content": "## Review — PR #${prNumber}\n\n${summary}"}'
# Step 5 — Submit verdict to Squad Control
curl -sL -X POST "${SC_API_URL}/api/tasks/review" \
-H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \
-d '{"taskId": "${TASK_ID}", "agentId": "${REVIEWER_ID}", "verdict": "approve", "comments": "${summary}"}'
# Use "request_changes" if not approving
Note: workspace.githubToken comes from the /api/tasks/pending or /api/tasks/pickup response. Never read it from a credentials file — it may not exist on this machine.
Discovering Agents
curl -sL "${SC_API_URL}/api/agents" -H "x-api-key: ${SC_API_KEY}"
Reviewer selection order:
SC_REVIEWER_AGENT_ID(explicit override)- Exact
role: "Code Reviewer" - Fallback role contains
Revieweror nameHawk
Poll Script Tests
Run parser tests locally:
~/.openclaw/workspace/skills/squad-control/scripts/run-tests.sh
POLL_RESULT envelope contract is documented in:
references/poll-result.schema.json
Creating Tasks Programmatically
curl -sL -X POST "${SC_API_URL}/api/tasks/create" \
-H "x-api-key: ${SC_API_KEY}" -H "Content-Type: application/json" \
-d '{"title": "...", "description": "...", "assignedAgentId": "..."}'
Common Mistakes
- Marking done without doing work — Always post results to the thread and create a PR (if code task) before marking complete. Empty result + no thread messages = task wasn't really done.
- Sub-agent calling /complete instead of /set-review after opening a PR — This is the most common workflow violation. If a PR was opened, the ONLY valid next call is
set-review. Callingcompletedirectly skips code review entirely and leaves an unmerged PR dangling. The stuck task recovery check now catches "done" tasks with open PRs and auto-creates a Hawk review task. - Squad Lead skipping the merge — When a task is assigned to the Squad Lead and has a PR deliverable, merge the PR to main BEFORE marking complete.
- Not passing SC_API_URL/SC_API_KEY into spawn prompt — Sub-agents can't call back to Squad Control without these. Always include them in the spawn template.
- Not using workspace.repoUrl — The pending and pickup responses include
workspace.repoUrlandworkspace.githubToken. Use them — don't assume a default repo path. - Forgetting to report failure — If something goes wrong, call
/api/tasks/fail. Tasks stuck in "working" forever block the queue. - Cloning without token on private repos — Check
workspace.githubTokenand use the git credential helper:git -c "credential.helper=!f() { echo username=x-access-token; echo password=<token>; }; f" clone "$REPO_URL"— never embed the token directly in the URL as it can leak via process lists, git remotes, or logs. - Not pulling latest before branching — Creates PRs against stale main, causing merge conflicts.
- Applying global concurrency instead of per-workspace — When handling tasks from multiple workspaces (account-level key), each workspace has its own
agentConcurrencylimit. Don't count agents running for workspace A against workspace B's limit. - Ignoring
task.workspaceand always using top-level config — Account-level keys embed workspace config directly in each task. Iftask.workspaceis present, use it; fall back to the top-level workspace only when it's absent.
如何使用「Squad Control」?
- 打开小龙虾AI(Web 或 iOS App)
- 点击上方「立即使用」按钮,或在对话框中输入任务描述
- 小龙虾AI 会自动匹配并调用「Squad Control」技能完成任务
- 结果即时呈现,支持继续对话优化