---
audience: developer
summary: Spawn, list, cancel sidekicks; supervise plans.
title: Sidekicks and plans API
path: reference/api/sidekicks-and-plans
status: published
---

# Sidekicks and plans API

10 endpoints, split between sidekicks (5) and plans (5).

## Sidekicks

| Method | Path | Purpose |
|---|---|---|
| `POST` | `/v1/rooms/{room_id}/sidekicks` | Spawn. |
| `GET` | `/v1/rooms/{room_id}/sidekicks` | List sidekicks in a room. |
| `GET` | `/v1/rooms/{room_id}/sidekicks/{task_id}` | One sidekick's detail. |
| `POST` | `/v1/rooms/{room_id}/sidekicks/{task_id}/cancel` | Cancel. |
| `GET` | `/v1/sidekicks` | All your active sidekicks across rooms. |

### POST /v1/rooms/{room_id}/sidekicks

```json
{
  "task": "Read the last 30 commits on app/ai/ and write a summary",
  "context_mode": "task_only",
  "model_id": "<optional override>",
  "timeout_seconds": 300,
  "name": "<optional human-readable name>"
}
```

`context_mode`:

- `task_only` (default) — sidekick sees only its task description.
- `full` — gets the parent room's recent history.

Returns:

```json
{
  "data": {
    "task_id": "task-…",
    "codename": "brave-penguin",
    "sidekick_room_id": "room-…",
    "agent_participant_id": "ai-…",
    "status": "spawning"
  }
}
```

### Cancel

`POST .../cancel` returns 200 with the updated task. Interrupting
an in-flight ScaiGrid stream is best-effort; the task is marked
`terminated` regardless.

## Plans

| Method | Path | Purpose |
|---|---|---|
| `GET` | `/v1/rooms/{room_id}/plan` | Active plan for this room (or `null`). |
| `GET` | `/v1/rooms/{room_id}/plans?limit=20` | Recent plans (audit). |
| `POST` | `/v1/rooms/{room_id}/plan/pause` | Supervisor pause. |
| `POST` | `/v1/rooms/{room_id}/plan/resume` | Supervisor resume; resets auto-step throttle. |
| `POST` | `/v1/rooms/{room_id}/plan/cancel` | Supervisor cancel; propagates to sidekick venue. |

### GET /v1/rooms/{room_id}/plan

Returns:

```json
{
  "data": {
    "id": "plan-…",
    "room_id": "room-…",
    "participant_id": "ai-…",
    "state": "running",
    "mode": "sequential",
    "advance": "auto",
    "venue": "main",
    "sidekick_room_id": null,
    "goal": "Audit the auth module",
    "steps": [
      {
        "id": "abc12",
        "text": "List every route",
        "status": "done",
        "result": "Found 5 routes in app/api/v1/auth.py",
        "attempts": 1,
        "expected_tools": null
      },
      …
    ],
    "current_step_idx": 2,
    "consecutive_auto_steps": 1,
    "max_consecutive_auto_steps": 8,
    "finish_reason": null,
    "created_at": "...",
    "updated_at": "..."
  }
}
```

### Pause / Resume / Cancel

```jsonc
// POST .../pause
{ "reason": "lunch break" }

// POST .../cancel
{ "reason": "scope changed" }
```

`/resume` takes no body. It resets `consecutive_auto_steps` to 0
so the AI has fresh budget after a supervisor nudge.

`/cancel` is idempotent — cancelling a terminal plan returns it
unchanged.

### Permissions

Any room member can pause / resume / cancel. The user is always a
supervisor by project rule; no role gating.

### Errors

- `SW_PLAN_NOT_FOUND` — no active plan in this room.
- `SW_PLAN_NOT_RUNNING` — operation requires `state=running`.
- `SW_PLAN_NOT_PAUSABLE` — only `running` can be paused.
- `SW_PLAN_NOT_RUNNABLE` — only `draft` or `paused` can be run.
- `SW_PLAN_TOO_MANY_STEPS` — write tried to submit > 20 steps.
- `SW_PLAN_EMPTY_STEPS` / `SW_PLAN_EMPTY_GOAL`.
- `SW_PLAN_UNKNOWN_STEP` — step_id not in the plan.
- `SW_PLAN_STEP_TERMINAL` — step already done / failed / skipped.

### Sidekick venue cancel propagation

When `plan.venue=sidekick` and `plan.sidekick_room_id` is set, the
cancel handler:

1. Looks up the `AgentTask` for the sidekick room.
2. Calls `AIBridge.cancel_generation(...)` to interrupt any active
   ScaiGrid stream.
3. Marks the task `TERMINATED` with a "plan supervisor: <reason>"
   summary.
4. Then sets the plan state to `cancelled`.

Order matters — the sidekick's record gets the supervisor reason
in its `result_summary`, not a generic "plan cancelled".
