{
  "schemaVersion": "1.0",
  "item": {
    "slug": "squad-control",
    "name": "Squad Control",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/wgan/squad-control",
    "canonicalUrl": "https://clawhub.ai/wgan/squad-control",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/squad-control",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=squad-control",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "SKILL.md",
      "references/advanced-flows.md",
      "references/api-reference.md",
      "references/api.md",
      "references/migration-notes.md",
      "references/poll-result.schema.json"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-05-07T17:22:31.273Z",
      "expiresAt": "2026-05-14T17:22:31.273Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-annual-report",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-annual-report",
        "contentDisposition": "attachment; filename=\"afrexai-annual-report-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/squad-control"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/squad-control",
    "agentPageUrl": "https://openagent3.xyz/skills/squad-control/agent",
    "manifestUrl": "https://openagent3.xyz/skills/squad-control/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/squad-control/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Squad Control Integration",
        "body": "Orchestrate AI agent tasks from Squad Control's kanban board."
      },
      {
        "title": "Critical Rules",
        "body": "Blocked-task dedup: Before posting a blocked status update, check the thread for your most recent message. If your last message is already a blocked update AND no new messages have been posted since, do NOT post another blocked comment — exit and wait for next dispatch instead.\nIncremental thread fetching: Use ?limit=N when reading thread history to avoid loading the full thread. If you know the last message ID from a previous session, save it in agentState so the next dispatch only fetches new messages.\nUse agentState for continuity: On pickup, check task.agentState for prior session context (files modified, steps completed, decisions made). Save state mid-task via POST /api/tasks/save-state."
      },
      {
        "title": "Quick Reference",
        "body": "Setup: references/setup.md\nFull API: references/api.md\nPR template: references/pr-template.md\nReview checklist: references/review-checklist.md\nPoll result schema: references/poll-result.schema.json\nMigration notes: references/migration-notes.md\nWake listener: scripts/wake-listener.sh\n\nRequired env vars: SC_API_URL, SC_API_KEY"
      },
      {
        "title": "Task Polling Flow",
        "body": "When a cron fires to check for tasks:\n\nRun ~/.openclaw/skills/squad-control/scripts/poll-tasks.sh (requires SC_API_URL and SC_API_KEY env vars)\n\nScript is multi-workspace safe: it does not export top-level REPO_URL / GITHUB_TOKEN; resolve repo/token per task from task.workspace (or fallback top-level workspace for legacy payloads).\nScript includes overlap protection via a lock/lease guard (stale lock auto-expires).\nScript uses retry/backoff/timeouts for API calls.\n\n\nIf output is HEARTBEAT_OK → no work, stop\nIf output contains POLL_RESULT: → parse the JSON envelope after it:\n\npending → follow Pickup & Dispatch below for each task\nreview.tasks → follow Review Dispatch below for each task\nstuck.tasks → follow Stuck Task Recovery below for each task\n\n⚠️ Isolated session fallback: If sessions_spawn is not available (e.g. this is an isolated cron session), do NOT hallucinate completion. Instead, hand off to the main session:\nopenclaw agent --agent main --channel telegram -m \"SQUAD_CONTROL_DISPATCH: pending tasks need worker sessions spawned. Use the squad-control skill. POLL_RESULT:\n<paste the full POLL_RESULT JSON here>\"\n\nThen reply HEARTBEAT_OK and exit. The main session has sessions_spawn and will handle dispatch.\n\nAlternatively, call the APIs directly:\n\nPending: curl -sL \"${SC_API_URL}/api/tasks/pending\" -H \"x-api-key: ${SC_API_KEY}\"\nReview: curl -sL \"${SC_API_URL}/api/tasks/list?status=review\" -H \"x-api-key: ${SC_API_KEY}\"\n\nParse workspace config from the response (see Multi-Workspace Response Handling below)."
      },
      {
        "title": "Wake Listener Flow",
        "body": "When a cron fires to run the wake listener:\n\nRun ~/.openclaw/skills/squad-control/scripts/wake-listener.sh\nThe script first calls POST /api/wake/session and opens an outbound relay connection when the wake relay is configured\nWhen a wake signal arrives, the script immediately runs poll-tasks.sh and captures the resulting POLL_RESULT envelope\nIf pending.tasks contains assigned work, the listener launches an ACP worker session directly via the local authenticated POST /api/sessions/spawn endpoint instead of routing through a second dispatcher LLM turn\nIf there is no direct pending work but review.tasks or stuck.tasks remain, the listener spawns a local openclaw agent turn for the residual dispatcher work\nThe wake-listener cron session can then exit cleanly; future wakes will be handled by the next listener run\nIf the relay is unavailable, it falls back to GET /api/wake/poll and uses the same handoff rule after the first wake\nIf the local /api/sessions/spawn endpoint returns 404/405/410/501 on an older OpenClaw build, the listener caches that capability miss for a while and falls back to the chat dispatcher path without retrying the same 404 on every wake\n\nUse this when you want low-latency async dispatch without exposing a public OpenClaw gateway URL.\nThe listener stays outbound-only; the existing 15-minute poll cron remains the final fallback recovery path."
      },
      {
        "title": "Multi-Workspace Response Handling",
        "body": "/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.\n\nResponse shape — workspace-scoped key (legacy / single-workspace):\n\n{\n  \"workspace\": { \"_id\": \"wsId\", \"name\": \"MyApp\", \"repoUrl\": \"...\", \"githubToken\": \"...\" },\n  \"tasks\": [{ \"_id\": \"taskId\", \"title\": \"...\", \"agent\": { ... } }]\n}\n\n→ workspace is at the top level; tasks do not have their own workspace object.\n\nResponse shape — account-scoped key (multi-workspace):\n\n{\n  \"tasks\": [\n    {\n      \"_id\": \"taskId\",\n      \"title\": \"...\",\n      \"workspace\": {\n        \"_id\": \"wsId\",\n        \"name\": \"MyApp\",\n        \"repoUrl\": \"https://github.com/org/repo\",\n        \"githubToken\": \"ghp_...\",\n        \"agentConcurrency\": 3\n      },\n      \"agent\": { ... }\n    }\n  ]\n}\n\n→ Each task carries its own workspace object. Tasks from different workspaces may appear in the same response.\n\nAgent fields — always nested under task.agent (never flat on task root):\n\n{\n  \"agent\": {\n    \"_id\": \"agentId\",\n    \"name\": \"Cody\",\n    \"role\": \"Developer\",\n    \"model\": \"anthropic/claude-sonnet-4-6\",\n    \"soulMd\": \"...\"\n  }\n}\n\n⚠️ Do NOT use task.agentName — that field does not exist. Always use task.agent.name, task.agent.model, task.agent.soulMd, task.agent._id.\n\nHandling both shapes (backward compatible):\n\n// For each task:\n// - If task.workspace is present → use it directly\n// - If not → use the top-level workspace from the response\nconst wsConfig = task.workspace ?? response.workspace;\nconst repoUrl = wsConfig.repoUrl;\nconst githubToken = wsConfig.githubToken;\nconst concurrencyLimit = wsConfig.agentConcurrency ?? 2;\n\nConcurrency 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.\n\n// Group by workspace, then dispatch up to concurrency limit for each\nconst byWorkspace = groupBy(tasks, t => (t.workspace ?? topLevelWs)._id);\nfor (const [wsId, wsTasks] of Object.entries(byWorkspace)) {\n  const ws = wsTasks[0].workspace ?? topLevelWs;\n  const limit = ws.agentConcurrency ?? 2;\n  const running = countRunningAgentsFor(wsId);\n  const slots = Math.max(0, limit - running);\n  for (const task of wsTasks.slice(0, slots)) {\n    dispatch(task, ws);\n  }\n}"
      },
      {
        "title": "Stuck Task Recovery",
        "body": "Run two checks every cron cycle:\n\nCheck 1 — Tasks stuck in \"working\" with a PR deliverable:\n\ncurl -sL \"${SC_API_URL}/api/tasks/list?status=working\" -H \"x-api-key: ${SC_API_KEY}\"\n\nFor 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.\n\nBefore rescue, apply idempotency guards:\n\nSkip if task already has an autoRescuedAt marker in metadata (if available)\nSkip if thread already contains an auto-rescue message for this task\nAfter successful rescue, write marker autoRescuedAt=<now> (or equivalent) to prevent duplicate rescues in later cron cycles\n\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/set-review\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${ASSIGNED_AGENT_ID}\\\", \\\"result\\\": \\\"Auto-rescued: sub-agent completed work but did not transition status.\\\", \\\"deliverables\\\": ${EXISTING_DELIVERABLES}}\"\n\nOnly 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.\"\n\nCheck 2 — Tasks marked \"done\" with an unmerged/open PR:\n\ncurl -sL \"${SC_API_URL}/api/tasks/list?status=done\" -H \"x-api-key: ${SC_API_KEY}\"\n# Filter to tasks completed in the last 2 hours that have a PR deliverable\n\nFor each recently-done task with a PR deliverable, verify the PR is actually merged:\n\n# Extract owner/repo from workspace.repoUrl\n# Extract PR number from deliverable URL (e.g. https://github.com/org/repo/pull/123 → 123)\ncurl -sL -H \"Authorization: token ${GITHUB_TOKEN}\" \\\n  \"https://api.github.com/repos/${owner}/${repo}/pulls/${PR_NUMBER}\" | grep -o '\"merged\":[^,]*'\n\nIf \"merged\":false (PR still open) → the agent skipped review. Re-open for Hawk.\n\nIdempotency guard before creating a review task:\n\nSearch existing tasks for one already referencing this PR URL/number in review|assigned|working\nOnly create a new review task if none exists\n\n# Create a review task for Hawk\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/create\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -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\\\"}\"\n\nPost 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.\""
      },
      {
        "title": "Review Dispatch",
        "body": "When review tasks are found, resolve the reviewer deterministically:\n\nIf SC_REVIEWER_AGENT_ID is set, use it directly\nElse query agents and pick exact role match Code Reviewer\nElse fallback to name Hawk or role containing Reviewer\n\ncurl -sL \"${SC_API_URL}/api/agents\" -H \"x-api-key: ${SC_API_KEY}\"\n\nFor each review task (has PR deliverable, pickedUpAt not set):\n\nDo 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.\n\nSpawn the reviewer agent using the Review Flow template below, passing the task ID and all context directly."
      },
      {
        "title": "Squad Lead Tasks — Merge & Complete",
        "body": "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.\n\n# 1. Pick up the task\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/pickup\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${SQUAD_LEAD_ID}\\\"}\"\n# Response includes workspace.repoUrl, workspace.githubToken, task.deliverables\n\n# 2. Find the PR deliverable — check type OR url, not just name\n# Priority: type === \"pr\" first, then url containing \"/pull/\"\n# Examples that all qualify: {type:\"pr\"}, {url:\"…/pull/7\"}, {name:\"PR #7\", type:\"pr\"}\n# NEVER complete as done if any deliverable has type=\"pr\" or url containing \"/pull/\"\n\n# 3. Clone and merge (use credential helper — do NOT embed token in URL)\nif [ -n \"$GITHUB_TOKEN\" ]; then\n  git -c \"credential.helper=!f() { echo username=x-access-token; echo password=${GITHUB_TOKEN}; }; f\" clone \"$REPO_URL\" /tmp/merge-repo\nelse\n  git clone \"$REPO_URL\" /tmp/merge-repo\nfi\ncd /tmp/merge-repo\ngit fetch origin\nDEFAULT_BRANCH=\"${SC_DEFAULT_BRANCH:-${WORKSPACE_DEFAULT_BRANCH:-main}}\"\ngit checkout \"$DEFAULT_BRANCH\" && git pull origin \"$DEFAULT_BRANCH\"\ngit merge --no-ff origin/task/${TASK_ID} -m \"Merge PR #${PR_NUMBER}: ${TASK_TITLE}\"\ngit push origin \"$DEFAULT_BRANCH\"\n\n# 4. Post to thread\ncurl -sL -X POST \"${SC_API_URL}/api/threads/send\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${SQUAD_LEAD_ID}\\\", \\\"content\\\": \\\"Merged PR #${PR_NUMBER} to main.\\\"}\"\n\n# 5. Complete the task\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/complete\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${SQUAD_LEAD_ID}\\\", \\\"result\\\": \\\"Merged PR #${PR_NUMBER} to main.\\\", \\\"status\\\": \\\"done\\\"}\"\n\nIf merge fails (conflicts): call /api/tasks/fail with the error — don't force-merge. Post the conflict details to the thread.\nIf no PR deliverable: just complete the task directly."
      },
      {
        "title": "Concurrency Limit",
        "body": "Before spawning anything, check how many sub-agents are already running:\n\nsubagents(action=\"list\")\n\nCount agents with status = \"running\". The concurrency limit comes from workspace.agentConcurrency (default: 2 if not set).\n\nWith a single workspace: If running agents ≥ limit, skip all spawning this cycle and reply HEARTBEAT_OK.\n\nWith 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.\n\nAlso, never dispatch more than workspace.agentConcurrency tasks per workspace per cron run. The rest will be picked up on the next run.\n\nThe workspace owner can change this limit in Squad Control → Settings → Agent Concurrency."
      },
      {
        "title": "Pickup & Dispatch",
        "body": "# Pick up task (marks it in-progress in Squad Control)\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/pickup\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${AGENT_ID}\\\", \\\"branch\\\": \\\"task/${TASK_ID}\\\"}\"\n# Response includes workspace.repoUrl and workspace.githubToken\n\nPost to the task thread that work is being dispatched:\n\ncurl -sL -X POST \"${SC_API_URL}/api/threads/send\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${AGENT_ID}\\\", \\\"content\\\": \\\"Picking up task. Starting work now.\\\"}\"\n\nThen 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:\n\nsessions_spawn({\n  task: <see Spawn Prompt Template below>,\n  model: agent.model,\n  label: \"${agent.name}-${workspace.name}-${taskTitle}\",\n  runTimeoutSeconds: 1800\n})"
      },
      {
        "title": "Spawn Prompt Template",
        "body": "When building this prompt, resolve workspace config as follows:\n\nIf task.workspace is present (account-scoped key): use task.workspace.repoUrl, task.workspace.githubToken, task.workspace.agentConcurrency\nIf task.workspace is absent (workspace-scoped key): use the top-level workspace from the response\n\n# Identity\n${agent.soulMd}\n\n# Squad Control Credentials (use these for ALL API calls)\nSC_API_URL=${SC_API_URL}\nSC_API_KEY=${SC_API_KEY}\nTASK_ID=${task._id}\nAGENT_ID=${agent._id}\n\n# Repository\nREPO_URL=${workspace.repoUrl}\nGITHUB_TOKEN=${workspace.githubToken}  # may be empty for public repos\n\n# Clone the repo (use credential helper — do NOT embed token in URL)\nif [ -n \"$GITHUB_TOKEN\" ]; then\n  git -c \"credential.helper=!f() { echo username=x-access-token; echo password=${GITHUB_TOKEN}; }; f\" clone \"$REPO_URL\" /tmp/task-repo\nelse\n  git clone \"$REPO_URL\" /tmp/task-repo\nfi\ncd /tmp/task-repo\ngit checkout -b task/${task._id}\n\n# Task\n**${task.title}**\n${task.description}\n\n# Git Workflow\n- Small, focused commits (feat:, fix:, chore: prefixes)\n- Scope changes to this task only\n- Run `npx tsc --noEmit` or existing tests before finishing\n\n# When Done — follow these steps EXACTLY, do not skip any\n\n## 1. Commit and push\ngit add -A && git commit -m \"feat: ${task.title}\"\ngit push origin task/${task._id}\n\n## 2. Create GitHub PR\ncurl -sL -X POST \\\n  -H \"Authorization: token ${GITHUB_TOKEN}\" \\\n  -H \"Content-Type: application/json\" \\\n  \"https://api.github.com/repos/${owner}/${repo}/pulls\" \\\n  -d '{\"title\": \"${task.title}\", \"head\": \"task/${task._id}\", \"base\": \"${SC_DEFAULT_BRANCH:-${WORKSPACE_DEFAULT_BRANCH:-main}}\", \"body\": \"${summary}\"}'\n# Save the PR number and URL from the response\n\n## 3. Post summary to thread\ncurl -sL -X POST \"${SC_API_URL}/api/threads/send\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d '{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${AGENT_ID}\", \"content\": \"Work complete. PR #N: ${PR_URL}\\n\\n${summary}\"}'\n\n## 4. If any files in convex/ were changed\n# Deployment is handled by CI after merge to main.\n# Do NOT run local deploy commands from this skill prompt.\n# If a manual deploy is required, ask the squad lead to run it in a controlled environment.\n\n## 5. Hand off for review (REQUIRED — NEVER call /complete if you opened a PR)\n\n> ⚠️ CRITICAL: If you created a PR in step 2, you MUST call set-review — not complete.\n> Calling /complete with an open PR bypasses code review entirely. This is a workflow violation.\n> The ONLY time to call /complete directly is when there is NO PR (e.g. a research or docs-only task).\n\n```bash\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/set-review\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d '{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${AGENT_ID}\", \"result\": \"${summary}\", \"deliverables\": [{\"type\": \"pr\", \"name\": \"PR #N\", \"url\": \"${PR_URL}\"}]}'\n\nVerify the API response confirms status changed to \"review\". If it returns an error, retry once then call /fail with the error details."
      },
      {
        "title": "If anything fails",
        "body": "curl -sL -X POST \"${SC_API_URL}/api/tasks/fail\" \n-H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \n-d '{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${AGENT_ID}\", \"error\": \"description of what went wrong\"}'\n\n### On Completion\n\n1. Post findings/summary to task thread:\n   ```bash\n   curl -sL -X POST \"${SC_API_URL}/api/threads/send\" \\\n     -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n     -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${AGENT_ID}\\\", \\\"content\\\": \\\"${SUMMARY}\\\"}\"\n\nCreate PR via GitHub API (see references/pr-template.md)\nIf a reviewer agent exists → set task to review:\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/set-review\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${AGENT_ID}\\\", \\\"result\\\": \\\"${SUMMARY}\\\", \\\"deliverables\\\": [{\\\"type\\\": \\\"pr\\\", \\\"name\\\": \\\"PR #N\\\", \\\"url\\\": \\\"${PR_URL}\\\"}]}\"\n\n\nIf no reviewer → complete directly:\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/complete\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${AGENT_ID}\\\", \\\"result\\\": \\\"${SUMMARY}\\\", \\\"status\\\": \\\"done\\\"}\""
      },
      {
        "title": "On Failure",
        "body": "Always report failures — don't silently mark done:\n\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/fail\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${AGENT_ID}\\\", \\\"error\\\": \\\"description of what went wrong\\\"}\""
      },
      {
        "title": "Review Flow",
        "body": "When routing to a reviewer agent, spawn them with this prompt (fill in all values):\n\n# Identity\n${reviewer.soulMd}\n\n# Task: Review PR #${prNumber}\n**Original task:** ${task.title}\n**PR:** ${prUrl}\n**Repo:** ${workspace.repoUrl}\n**GitHub token:** ${workspace.githubToken}   # may be empty for public repos\n\n# Extract owner/repo from repoUrl\n# e.g. https://github.com/org/repo -> owner=org, repo=repo\n\n# Step 1 — Get the diff\ncurl -sL -H \"Authorization: token ${GITHUB_TOKEN}\" \\\n  \"https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/files\"\n\n# Step 2 — Review the code\nCheck: correctness, code quality, security, edge cases.\n\n# Step 3 — Post review to GitHub PR (REQUIRED — not just to thread)\ncurl -sL -X POST \\\n  -H \"Authorization: token ${GITHUB_TOKEN}\" \\\n  -H \"Content-Type: application/json\" \\\n  \"https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews\" \\\n  -d '{\"event\": \"APPROVE\", \"body\": \"<review summary>\"}'\n# Use \"REQUEST_CHANGES\" instead of \"APPROVE\" if changes are needed\n\n# Step 4 — Post summary to Squad Control thread\ncurl -sL -X POST \"${SC_API_URL}/api/threads/send\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d '{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${REVIEWER_ID}\", \"content\": \"## Review — PR #${prNumber}\\n\\n${summary}\"}'\n\n# Step 5 — Submit verdict to Squad Control\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/review\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d '{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${REVIEWER_ID}\", \"verdict\": \"approve\", \"comments\": \"${summary}\"}'\n# Use \"request_changes\" if not approving\n\nNote: 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."
      },
      {
        "title": "Discovering Agents",
        "body": "curl -sL \"${SC_API_URL}/api/agents\" -H \"x-api-key: ${SC_API_KEY}\"\n\nReviewer selection order:\n\nSC_REVIEWER_AGENT_ID (explicit override)\nExact role: \"Code Reviewer\"\nFallback role contains Reviewer or name Hawk"
      },
      {
        "title": "Poll Script Tests",
        "body": "Run parser tests locally:\n\n~/.openclaw/skills/squad-control/scripts/run-tests.sh\n\nPOLL_RESULT envelope contract is documented in:\n\nreferences/poll-result.schema.json"
      },
      {
        "title": "Creating Tasks Programmatically",
        "body": "curl -sL -X POST \"${SC_API_URL}/api/tasks/create\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d '{\"title\": \"...\", \"description\": \"...\", \"assignedAgentId\": \"...\"}'"
      },
      {
        "title": "Common Mistakes",
        "body": "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.\nSub-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. Calling complete directly 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.\nSquad 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.\nNot 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.\nNot using workspace.repoUrl — The pending and pickup responses include workspace.repoUrl and workspace.githubToken. Use them — don't assume a default repo path.\nForgetting to report failure — If something goes wrong, call /api/tasks/fail. Tasks stuck in \"working\" forever block the queue.\nCloning without token on private repos — Check workspace.githubToken and 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.\nNot pulling latest before branching — Creates PRs against stale main, causing merge conflicts.\nApplying global concurrency instead of per-workspace — When handling tasks from multiple workspaces (account-level key), each workspace has its own agentConcurrency limit. Don't count agents running for workspace A against workspace B's limit.\nIgnoring task.workspace and always using top-level config — Account-level keys embed workspace config directly in each task. If task.workspace is present, use it; fall back to the top-level workspace only when it's absent."
      }
    ],
    "body": "Squad Control Integration\n\nOrchestrate AI agent tasks from Squad Control's kanban board.\n\nCritical Rules\nBlocked-task dedup: Before posting a blocked status update, check the thread for your most recent message. If your last message is already a blocked update AND no new messages have been posted since, do NOT post another blocked comment — exit and wait for next dispatch instead.\nIncremental thread fetching: Use ?limit=N when reading thread history to avoid loading the full thread. If you know the last message ID from a previous session, save it in agentState so the next dispatch only fetches new messages.\nUse agentState for continuity: On pickup, check task.agentState for prior session context (files modified, steps completed, decisions made). Save state mid-task via POST /api/tasks/save-state.\nQuick Reference\nSetup: references/setup.md\nFull API: references/api.md\nPR template: references/pr-template.md\nReview checklist: references/review-checklist.md\nPoll result schema: references/poll-result.schema.json\nMigration notes: references/migration-notes.md\nWake listener: scripts/wake-listener.sh\n\nRequired env vars: SC_API_URL, SC_API_KEY\n\nTask Polling Flow\n\nWhen a cron fires to check for tasks:\n\nRun ~/.openclaw/skills/squad-control/scripts/poll-tasks.sh (requires SC_API_URL and SC_API_KEY env vars)\nScript is multi-workspace safe: it does not export top-level REPO_URL / GITHUB_TOKEN; resolve repo/token per task from task.workspace (or fallback top-level workspace for legacy payloads).\nScript includes overlap protection via a lock/lease guard (stale lock auto-expires).\nScript uses retry/backoff/timeouts for API calls.\nIf output is HEARTBEAT_OK → no work, stop\nIf output contains POLL_RESULT: → parse the JSON envelope after it:\npending → follow Pickup & Dispatch below for each task\nreview.tasks → follow Review Dispatch below for each task\nstuck.tasks → follow Stuck Task Recovery below for each task\n\n⚠️ Isolated session fallback: If sessions_spawn is not available (e.g. this is an isolated cron session), do NOT hallucinate completion. Instead, hand off to the main session:\n\nopenclaw agent --agent main --channel telegram -m \"SQUAD_CONTROL_DISPATCH: pending tasks need worker sessions spawned. Use the squad-control skill. POLL_RESULT:\n<paste the full POLL_RESULT JSON here>\"\n\n\nThen reply HEARTBEAT_OK and exit. The main session has sessions_spawn and will handle dispatch.\n\nAlternatively, call the APIs directly:\n\nPending: curl -sL \"${SC_API_URL}/api/tasks/pending\" -H \"x-api-key: ${SC_API_KEY}\"\nReview: curl -sL \"${SC_API_URL}/api/tasks/list?status=review\" -H \"x-api-key: ${SC_API_KEY}\"\n\nParse workspace config from the response (see Multi-Workspace Response Handling below).\n\nWake Listener Flow\n\nWhen a cron fires to run the wake listener:\n\nRun ~/.openclaw/skills/squad-control/scripts/wake-listener.sh\nThe script first calls POST /api/wake/session and opens an outbound relay connection when the wake relay is configured\nWhen a wake signal arrives, the script immediately runs poll-tasks.sh and captures the resulting POLL_RESULT envelope\nIf pending.tasks contains assigned work, the listener launches an ACP worker session directly via the local authenticated POST /api/sessions/spawn endpoint instead of routing through a second dispatcher LLM turn\nIf there is no direct pending work but review.tasks or stuck.tasks remain, the listener spawns a local openclaw agent turn for the residual dispatcher work\nThe wake-listener cron session can then exit cleanly; future wakes will be handled by the next listener run\nIf the relay is unavailable, it falls back to GET /api/wake/poll and uses the same handoff rule after the first wake\nIf the local /api/sessions/spawn endpoint returns 404/405/410/501 on an older OpenClaw build, the listener caches that capability miss for a while and falls back to the chat dispatcher path without retrying the same 404 on every wake\n\nUse this when you want low-latency async dispatch without exposing a public OpenClaw gateway URL. The listener stays outbound-only; the existing 15-minute poll cron remains the final fallback recovery path.\n\nMulti-Workspace Response Handling\n\n/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.\n\nResponse shape — workspace-scoped key (legacy / single-workspace):\n\n{\n  \"workspace\": { \"_id\": \"wsId\", \"name\": \"MyApp\", \"repoUrl\": \"...\", \"githubToken\": \"...\" },\n  \"tasks\": [{ \"_id\": \"taskId\", \"title\": \"...\", \"agent\": { ... } }]\n}\n\n\n→ workspace is at the top level; tasks do not have their own workspace object.\n\nResponse shape — account-scoped key (multi-workspace):\n\n{\n  \"tasks\": [\n    {\n      \"_id\": \"taskId\",\n      \"title\": \"...\",\n      \"workspace\": {\n        \"_id\": \"wsId\",\n        \"name\": \"MyApp\",\n        \"repoUrl\": \"https://github.com/org/repo\",\n        \"githubToken\": \"ghp_...\",\n        \"agentConcurrency\": 3\n      },\n      \"agent\": { ... }\n    }\n  ]\n}\n\n\n→ Each task carries its own workspace object. Tasks from different workspaces may appear in the same response.\n\nAgent fields — always nested under task.agent (never flat on task root):\n\n{\n  \"agent\": {\n    \"_id\": \"agentId\",\n    \"name\": \"Cody\",\n    \"role\": \"Developer\",\n    \"model\": \"anthropic/claude-sonnet-4-6\",\n    \"soulMd\": \"...\"\n  }\n}\n\n\n⚠️ Do NOT use task.agentName — that field does not exist. Always use task.agent.name, task.agent.model, task.agent.soulMd, task.agent._id.\n\nHandling both shapes (backward compatible):\n\n// For each task:\n// - If task.workspace is present → use it directly\n// - If not → use the top-level workspace from the response\nconst wsConfig = task.workspace ?? response.workspace;\nconst repoUrl = wsConfig.repoUrl;\nconst githubToken = wsConfig.githubToken;\nconst concurrencyLimit = wsConfig.agentConcurrency ?? 2;\n\n\nConcurrency 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.\n\n// Group by workspace, then dispatch up to concurrency limit for each\nconst byWorkspace = groupBy(tasks, t => (t.workspace ?? topLevelWs)._id);\nfor (const [wsId, wsTasks] of Object.entries(byWorkspace)) {\n  const ws = wsTasks[0].workspace ?? topLevelWs;\n  const limit = ws.agentConcurrency ?? 2;\n  const running = countRunningAgentsFor(wsId);\n  const slots = Math.max(0, limit - running);\n  for (const task of wsTasks.slice(0, slots)) {\n    dispatch(task, ws);\n  }\n}\n\nStuck Task Recovery\n\nRun two checks every cron cycle:\n\nCheck 1 — Tasks stuck in \"working\" with a PR deliverable:\n\ncurl -sL \"${SC_API_URL}/api/tasks/list?status=working\" -H \"x-api-key: ${SC_API_KEY}\"\n\n\nFor 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.\n\nBefore rescue, apply idempotency guards:\n\nSkip if task already has an autoRescuedAt marker in metadata (if available)\nSkip if thread already contains an auto-rescue message for this task\nAfter successful rescue, write marker autoRescuedAt=<now> (or equivalent) to prevent duplicate rescues in later cron cycles\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/set-review\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${ASSIGNED_AGENT_ID}\\\", \\\"result\\\": \\\"Auto-rescued: sub-agent completed work but did not transition status.\\\", \\\"deliverables\\\": ${EXISTING_DELIVERABLES}}\"\n\n\nOnly 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.\"\n\nCheck 2 — Tasks marked \"done\" with an unmerged/open PR:\n\ncurl -sL \"${SC_API_URL}/api/tasks/list?status=done\" -H \"x-api-key: ${SC_API_KEY}\"\n# Filter to tasks completed in the last 2 hours that have a PR deliverable\n\n\nFor each recently-done task with a PR deliverable, verify the PR is actually merged:\n\n# Extract owner/repo from workspace.repoUrl\n# Extract PR number from deliverable URL (e.g. https://github.com/org/repo/pull/123 → 123)\ncurl -sL -H \"Authorization: token ${GITHUB_TOKEN}\" \\\n  \"https://api.github.com/repos/${owner}/${repo}/pulls/${PR_NUMBER}\" | grep -o '\"merged\":[^,]*'\n\n\nIf \"merged\":false (PR still open) → the agent skipped review. Re-open for Hawk.\n\nIdempotency guard before creating a review task:\n\nSearch existing tasks for one already referencing this PR URL/number in review|assigned|working\nOnly create a new review task if none exists\n# Create a review task for Hawk\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/create\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -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\\\"}\"\n\n\nPost 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.\"\n\nReview Dispatch\n\nWhen review tasks are found, resolve the reviewer deterministically:\n\nIf SC_REVIEWER_AGENT_ID is set, use it directly\nElse query agents and pick exact role match Code Reviewer\nElse fallback to name Hawk or role containing Reviewer\ncurl -sL \"${SC_API_URL}/api/agents\" -H \"x-api-key: ${SC_API_KEY}\"\n\n\nFor each review task (has PR deliverable, pickedUpAt not set):\n\nDo 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.\n\nSpawn the reviewer agent using the Review Flow template below, passing the task ID and all context directly.\n\nSquad Lead Tasks — Merge & Complete\n\nWhen 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.\n\n# 1. Pick up the task\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/pickup\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${SQUAD_LEAD_ID}\\\"}\"\n# Response includes workspace.repoUrl, workspace.githubToken, task.deliverables\n\n# 2. Find the PR deliverable — check type OR url, not just name\n# Priority: type === \"pr\" first, then url containing \"/pull/\"\n# Examples that all qualify: {type:\"pr\"}, {url:\"…/pull/7\"}, {name:\"PR #7\", type:\"pr\"}\n# NEVER complete as done if any deliverable has type=\"pr\" or url containing \"/pull/\"\n\n# 3. Clone and merge (use credential helper — do NOT embed token in URL)\nif [ -n \"$GITHUB_TOKEN\" ]; then\n  git -c \"credential.helper=!f() { echo username=x-access-token; echo password=${GITHUB_TOKEN}; }; f\" clone \"$REPO_URL\" /tmp/merge-repo\nelse\n  git clone \"$REPO_URL\" /tmp/merge-repo\nfi\ncd /tmp/merge-repo\ngit fetch origin\nDEFAULT_BRANCH=\"${SC_DEFAULT_BRANCH:-${WORKSPACE_DEFAULT_BRANCH:-main}}\"\ngit checkout \"$DEFAULT_BRANCH\" && git pull origin \"$DEFAULT_BRANCH\"\ngit merge --no-ff origin/task/${TASK_ID} -m \"Merge PR #${PR_NUMBER}: ${TASK_TITLE}\"\ngit push origin \"$DEFAULT_BRANCH\"\n\n# 4. Post to thread\ncurl -sL -X POST \"${SC_API_URL}/api/threads/send\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${SQUAD_LEAD_ID}\\\", \\\"content\\\": \\\"Merged PR #${PR_NUMBER} to main.\\\"}\"\n\n# 5. Complete the task\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/complete\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${SQUAD_LEAD_ID}\\\", \\\"result\\\": \\\"Merged PR #${PR_NUMBER} to main.\\\", \\\"status\\\": \\\"done\\\"}\"\n\n\nIf 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.\n\nConcurrency Limit\n\nBefore spawning anything, check how many sub-agents are already running:\n\nsubagents(action=\"list\")\n\n\nCount agents with status = \"running\". The concurrency limit comes from workspace.agentConcurrency (default: 2 if not set).\n\nWith a single workspace: If running agents ≥ limit, skip all spawning this cycle and reply HEARTBEAT_OK.\n\nWith 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.\n\nAlso, never dispatch more than workspace.agentConcurrency tasks per workspace per cron run. The rest will be picked up on the next run.\n\nThe workspace owner can change this limit in Squad Control → Settings → Agent Concurrency.\n\nPickup & Dispatch\n# Pick up task (marks it in-progress in Squad Control)\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/pickup\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${AGENT_ID}\\\", \\\"branch\\\": \\\"task/${TASK_ID}\\\"}\"\n# Response includes workspace.repoUrl and workspace.githubToken\n\n\nPost to the task thread that work is being dispatched:\n\ncurl -sL -X POST \"${SC_API_URL}/api/threads/send\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${AGENT_ID}\\\", \\\"content\\\": \\\"Picking up task. Starting work now.\\\"}\"\n\n\nThen 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:\n\nsessions_spawn({\n  task: <see Spawn Prompt Template below>,\n  model: agent.model,\n  label: \"${agent.name}-${workspace.name}-${taskTitle}\",\n  runTimeoutSeconds: 1800\n})\n\nSpawn Prompt Template\n\nWhen building this prompt, resolve workspace config as follows:\n\nIf task.workspace is present (account-scoped key): use task.workspace.repoUrl, task.workspace.githubToken, task.workspace.agentConcurrency\nIf task.workspace is absent (workspace-scoped key): use the top-level workspace from the response\n# Identity\n${agent.soulMd}\n\n# Squad Control Credentials (use these for ALL API calls)\nSC_API_URL=${SC_API_URL}\nSC_API_KEY=${SC_API_KEY}\nTASK_ID=${task._id}\nAGENT_ID=${agent._id}\n\n# Repository\nREPO_URL=${workspace.repoUrl}\nGITHUB_TOKEN=${workspace.githubToken}  # may be empty for public repos\n\n# Clone the repo (use credential helper — do NOT embed token in URL)\nif [ -n \"$GITHUB_TOKEN\" ]; then\n  git -c \"credential.helper=!f() { echo username=x-access-token; echo password=${GITHUB_TOKEN}; }; f\" clone \"$REPO_URL\" /tmp/task-repo\nelse\n  git clone \"$REPO_URL\" /tmp/task-repo\nfi\ncd /tmp/task-repo\ngit checkout -b task/${task._id}\n\n# Task\n**${task.title}**\n${task.description}\n\n# Git Workflow\n- Small, focused commits (feat:, fix:, chore: prefixes)\n- Scope changes to this task only\n- Run `npx tsc --noEmit` or existing tests before finishing\n\n# When Done — follow these steps EXACTLY, do not skip any\n\n## 1. Commit and push\ngit add -A && git commit -m \"feat: ${task.title}\"\ngit push origin task/${task._id}\n\n## 2. Create GitHub PR\ncurl -sL -X POST \\\n  -H \"Authorization: token ${GITHUB_TOKEN}\" \\\n  -H \"Content-Type: application/json\" \\\n  \"https://api.github.com/repos/${owner}/${repo}/pulls\" \\\n  -d '{\"title\": \"${task.title}\", \"head\": \"task/${task._id}\", \"base\": \"${SC_DEFAULT_BRANCH:-${WORKSPACE_DEFAULT_BRANCH:-main}}\", \"body\": \"${summary}\"}'\n# Save the PR number and URL from the response\n\n## 3. Post summary to thread\ncurl -sL -X POST \"${SC_API_URL}/api/threads/send\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d '{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${AGENT_ID}\", \"content\": \"Work complete. PR #N: ${PR_URL}\\n\\n${summary}\"}'\n\n## 4. If any files in convex/ were changed\n# Deployment is handled by CI after merge to main.\n# Do NOT run local deploy commands from this skill prompt.\n# If a manual deploy is required, ask the squad lead to run it in a controlled environment.\n\n## 5. Hand off for review (REQUIRED — NEVER call /complete if you opened a PR)\n\n> ⚠️ CRITICAL: If you created a PR in step 2, you MUST call set-review — not complete.\n> Calling /complete with an open PR bypasses code review entirely. This is a workflow violation.\n> The ONLY time to call /complete directly is when there is NO PR (e.g. a research or docs-only task).\n\n```bash\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/set-review\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d '{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${AGENT_ID}\", \"result\": \"${summary}\", \"deliverables\": [{\"type\": \"pr\", \"name\": \"PR #N\", \"url\": \"${PR_URL}\"}]}'\n\n\nVerify the API response confirms status changed to \"review\". If it returns an error, retry once then call /fail with the error details.\n\nIf anything fails\n\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/fail\"\n-H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\"\n-d '{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${AGENT_ID}\", \"error\": \"description of what went wrong\"}'\n\n\n### On Completion\n\n1. Post findings/summary to task thread:\n   ```bash\n   curl -sL -X POST \"${SC_API_URL}/api/threads/send\" \\\n     -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n     -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${AGENT_ID}\\\", \\\"content\\\": \\\"${SUMMARY}\\\"}\"\n\nCreate PR via GitHub API (see references/pr-template.md)\nIf a reviewer agent exists → set task to review:\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/set-review\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${AGENT_ID}\\\", \\\"result\\\": \\\"${SUMMARY}\\\", \\\"deliverables\\\": [{\\\"type\\\": \\\"pr\\\", \\\"name\\\": \\\"PR #N\\\", \\\"url\\\": \\\"${PR_URL}\\\"}]}\"\n\nIf no reviewer → complete directly:\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/complete\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${AGENT_ID}\\\", \\\"result\\\": \\\"${SUMMARY}\\\", \\\"status\\\": \\\"done\\\"}\"\n\nOn Failure\n\nAlways report failures — don't silently mark done:\n\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/fail\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"taskId\\\": \\\"${TASK_ID}\\\", \\\"agentId\\\": \\\"${AGENT_ID}\\\", \\\"error\\\": \\\"description of what went wrong\\\"}\"\n\nReview Flow\n\nWhen routing to a reviewer agent, spawn them with this prompt (fill in all values):\n\n# Identity\n${reviewer.soulMd}\n\n# Task: Review PR #${prNumber}\n**Original task:** ${task.title}\n**PR:** ${prUrl}\n**Repo:** ${workspace.repoUrl}\n**GitHub token:** ${workspace.githubToken}   # may be empty for public repos\n\n# Extract owner/repo from repoUrl\n# e.g. https://github.com/org/repo -> owner=org, repo=repo\n\n# Step 1 — Get the diff\ncurl -sL -H \"Authorization: token ${GITHUB_TOKEN}\" \\\n  \"https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/files\"\n\n# Step 2 — Review the code\nCheck: correctness, code quality, security, edge cases.\n\n# Step 3 — Post review to GitHub PR (REQUIRED — not just to thread)\ncurl -sL -X POST \\\n  -H \"Authorization: token ${GITHUB_TOKEN}\" \\\n  -H \"Content-Type: application/json\" \\\n  \"https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews\" \\\n  -d '{\"event\": \"APPROVE\", \"body\": \"<review summary>\"}'\n# Use \"REQUEST_CHANGES\" instead of \"APPROVE\" if changes are needed\n\n# Step 4 — Post summary to Squad Control thread\ncurl -sL -X POST \"${SC_API_URL}/api/threads/send\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d '{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${REVIEWER_ID}\", \"content\": \"## Review — PR #${prNumber}\\n\\n${summary}\"}'\n\n# Step 5 — Submit verdict to Squad Control\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/review\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d '{\"taskId\": \"${TASK_ID}\", \"agentId\": \"${REVIEWER_ID}\", \"verdict\": \"approve\", \"comments\": \"${summary}\"}'\n# Use \"request_changes\" if not approving\n\n\nNote: 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.\n\nDiscovering Agents\ncurl -sL \"${SC_API_URL}/api/agents\" -H \"x-api-key: ${SC_API_KEY}\"\n\n\nReviewer selection order:\n\nSC_REVIEWER_AGENT_ID (explicit override)\nExact role: \"Code Reviewer\"\nFallback role contains Reviewer or name Hawk\nPoll Script Tests\n\nRun parser tests locally:\n\n~/.openclaw/skills/squad-control/scripts/run-tests.sh\n\n\nPOLL_RESULT envelope contract is documented in:\n\nreferences/poll-result.schema.json\nCreating Tasks Programmatically\ncurl -sL -X POST \"${SC_API_URL}/api/tasks/create\" \\\n  -H \"x-api-key: ${SC_API_KEY}\" -H \"Content-Type: application/json\" \\\n  -d '{\"title\": \"...\", \"description\": \"...\", \"assignedAgentId\": \"...\"}'\n\nCommon Mistakes\nMarking 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.\nSub-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. Calling complete directly 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.\nSquad 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.\nNot 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.\nNot using workspace.repoUrl — The pending and pickup responses include workspace.repoUrl and workspace.githubToken. Use them — don't assume a default repo path.\nForgetting to report failure — If something goes wrong, call /api/tasks/fail. Tasks stuck in \"working\" forever block the queue.\nCloning without token on private repos — Check workspace.githubToken and 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.\nNot pulling latest before branching — Creates PRs against stale main, causing merge conflicts.\nApplying global concurrency instead of per-workspace — When handling tasks from multiple workspaces (account-level key), each workspace has its own agentConcurrency limit. Don't count agents running for workspace A against workspace B's limit.\nIgnoring task.workspace and always using top-level config — Account-level keys embed workspace config directly in each task. If task.workspace is present, use it; fall back to the top-level workspace only when it's absent."
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/wgan/squad-control",
    "publisherUrl": "https://clawhub.ai/wgan/squad-control",
    "owner": "wgan",
    "version": "1.4.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/squad-control",
    "downloadUrl": "https://openagent3.xyz/downloads/squad-control",
    "agentUrl": "https://openagent3.xyz/skills/squad-control/agent",
    "manifestUrl": "https://openagent3.xyz/skills/squad-control/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/squad-control/agent.md"
  }
}