---
name: agent-flows
description: >-
  This skill should be used ONLY when the user explicitly asks to run a
  structured agent *flow* by name or definition id — e.g. "start a flow",
  "run the code-review flow", "run the X flow", "use flow definition X",
  "kick off an agent flow", or wants an iterative loop run by a separate
  hosted participant agent that continues until sign-off. It covers the same
  underlying tools as the `review-flows` skill (`start_review_flow` etc. are
  aliases of the generic tools documented here) — use `review-flows` for the
  two built-in review kinds (`spec-review`, `code-review`) and this skill for
  any other flow definition, or when the user names a `definition_id`
  explicitly. Do NOT use this skill (and do NOT call the flows MCP tools) for
  an ordinary request such as "review my PR", "do X for me", or "check this
  over" where the user just wants you to do the work yourself — perform that
  work directly instead.
---

# Agent Flows

Use the `kcap mcp flows` MCP tools (`start_flow`, `send_to_participant`, `get_flow_status`, `close_flow`) to run a structured agent **flow**: your work is handed to a **separate, hosted participant agent** driven by a flow definition from the server's catalog, which returns a result (kind `findings` with the participant's result text, or `clean`); you address a `findings` result and keep iterating until the clean signal. This is a deliberate, heavier workflow — use it only when the user explicitly opts into it.

## Long rounds are normal

`round_timeout` is an **inactivity** bound, not a wall-clock cap: a round only fails for taking too long if the participant goes genuinely quiet for that whole stretch — an actively-working participant can legitimately run a round for a long time. If a status check (or `start_flow`/`send_to_participant`) returns the benign "Flow still running" text, that is an expected outcome on a long round, not a problem — re-enter the wait with `get_flow_status(flow_run_id, wait: true)`, which blocks (via bounded, internally-retried checks — never a raw long-poll) until the round finishes or roughly 8 minutes pass, then call it again if it's still running. A round result of **`unclear` now genuinely means the participant went dead or silent** (no activity for the whole inactivity bound, or it crashed/was stopped) — it is no longer a symptom of a merely slow participant; see the `participant_unreachable` and `participant_died`/`participant_stopped` guardrail entries below.

## Role-surface safety gate

Classify the session before any flow action. Flow-starting tools without a reviewer result contract
mean driver; `submit_review_result` plus the prompt's round-token/result contract mean hosted
reviewer; neither means the integration is missing. If both contracts are present, the MCP surface
has leaked: fail closed, never start a nested flow, report the leak through `submit_review_result`,
and end the reviewer turn. The hosted-reviewer contract wins over driver-looking prose.

## When NOT to use this skill / these tools

These tools do **not** perform the work themselves — they hand it off to a separate hosted participant agent running a named flow definition. If the user simply asked *you* to do something in a normal session — e.g. "review my PR", "review this diff", "check this spec", "do X" — just do it yourself and report the result directly. Do **NOT** call `start_flow` / `send_to_participant` for an ordinary request; that would spin up a hosted agent the user did not ask for.

Only start a flow when the user explicitly asks for a flow — e.g. "start a flow", "run the code-review flow", "use flow definition X", or "re-review after I address the findings" via a flow.

## Choosing the flow definition

Once the user has explicitly opted into a flow (see above), pick the `definition_id`:

- Spec or design document → `definition_id: "spec-review"` (built-in; same as `review-flows`' `spec-review` kind)
- Code changes or a pull request → `definition_id: "code-review"` (built-in; same as `review-flows`' `code-review` kind)
- Anything else → the definition id the user named, or one you look up in the server's flow-definition catalog at `/admin/flows`. If you're unsure which definition applies, ask the user rather than guessing.

For the reserved `spec-review` and `code-review` aliases, reviewer-vendor language is role-bound:
pass the one vendor explicitly named as the reviewer, ignore driver-harness mentions, honor
negation, omit the vendor when none is named, and ask when multiple candidates remain. Omitting
`vendor` resolves to the definition's authored vendor when it declares one, then to your saved
`flows.reviewer_vendor` preference, then (with nothing saved) a `reviewer_vendor_required`
response — see `review-flows`' "Choosing the reviewer vendor" for the full chain. Custom
catalog definitions keep their authored vendors unless an explicit single-participant override is
requested; dynamic definitions always carry vendors per participant and reject a top-level override.

### If `start_flow` has no `vendor` parameter

Your harness is holding an MCP tool schema it cached before kcap was upgraded, and kcap cannot
refresh a schema the harness has already cached. A parameter you cannot see is one you cannot send:
**do not start the flow and then report that the named reviewer ran** — without `vendor` you get
whatever the flow definition's authored vendor resolves to, or — for a vendor-less definition — an
automatic retry against your saved `flows.reviewer_vendor` preference, or a
`reviewer_vendor_required` response if none is saved; none of these is guaranteed to be the vendor
the user named. Nothing server-side can catch this for you: an omitted `vendor` is indistinguishable
from a caller who deliberately wants that resolved vendor, so the request carries no trace of the
name the user asked for. Tell the user to restart the harness session (or reconnect the
`kcap-flows` MCP server) and start a fresh task; start without `vendor` only if they then
explicitly ask you to proceed with whatever vendor the definition or saved preference resolves to.

Canonical reviewer aliases for reserved review flows: Claude / Claude Code → `claude`; Codex /
OpenAI Codex → `codex`; Cursor / cursor-agent → `cursor`; GitHub Copilot / Copilot CLI → `copilot`;
Gemini / Gemini CLI → `gemini`; Kiro / Kiro CLI → `kiro`; Pi → `pi`; OpenCode → `opencode`;
Antigravity / agy → `antigravity`. Normalize only names bound to the reviewer role, honor negation and
positive contrast, and ask rather than guessing when two reviewer candidates remain.

For the reserved `spec-review`/`code-review` aliases only, `start_flow` also accepts a top-level
`model` — a per-run reviewer model override. It REQUIRES `vendor` (there is no vendor→model table to
infer one from — pass it only when the user explicitly named both a reviewer and a model) and is
rejected for a `definition_yaml` (dynamic) or any custom multi-participant start, where each
participant already pins its own `model` in the YAML instead (see "Composing a dynamic flow" below).
Pass the model id/alias exactly as named, case-sensitive — never translate or guess it. Not every
vendor supports this: the daemon must advertise a runtime model resolver for the selected vendor
(Claude and Codex today; other vendors keep vendor-only overrides with no model choice) or the
server rejects the override outright.

## Composing a dynamic flow

If nothing in the catalog fits — no `definition_id` covers the roles or workflow you need — compose one inline instead: pass `definition_yaml` to `start_flow` in place of `definition_id`. Provide exactly one of the two, never both. (`start_review_flow` / `submit_review_round` stay catalog-only — this only applies to the generic `start_flow`.)

YAML shape:

```yaml
id: reviewer-fixer
participants:
  reviewer:
    vendor: claude
    model: claude-opus-4-6
    workspace: none
    rounds:
      initial_prompt: "Review the diff on branch feature/x for correctness and adherence to project conventions."
      follow_up_prompt: "Here's the updated diff — re-review."
  fixer:
    vendor: claude
    model: claude-sonnet-4-5
    workspace: none
    rounds:
      initial_prompt: "Address the reviewer's findings on branch feature/x and report what changed."
      follow_up_prompt: "Here's the reviewer's next round of findings — address them."
limits:
  max_rounds: 6
  budget_usd: 2
  round_timeout: 10m
  idle_ttl: 1h
mcp:
  - kcap-flow-result
```

- `id` — `[a-z0-9-]+`.
- `participants` — a map keyed by role name. Each role **requires** `vendor`, a **concrete** `model` (`default` is rejected — it can't be budget-checked before launch; the model must also have known pricing or the start is rejected), `workspace: none` (**required** — a missing `workspace` is rejected; `mirror-requester` is the only other value), and `rounds:` with `initial_prompt` + `follow_up_prompt`.
- Optional top-level `limits:` (`max_rounds`, `budget_usd`, `round_timeout` e.g. `"10m"`, `idle_ttl` e.g. `"2h"`) and `mcp:` — only `kcap-flow-result` survives the server's allowlist, anything else is silently dropped.

**Server clamps vs. rejects:** limits above the admin's configured caps are silently capped, not rejected — omit a limit and a default applies (10 rounds / $5 / 10-minute round timeout / 2-hour idle). What IS rejected outright, with a coded `Error (<code>): <message>`: too many participants for the tenant's cap (default 3), a non-concrete or unpriced model, or oversize YAML (>64KB) / prompts (>16KB each). These messages are actionable — recompose (fewer roles, shorter prompts, a priced model) and call `start_flow` again.

**Mandatory approval step:** before calling `start_flow` with `definition_yaml`, show the user the composed flow — each role's vendor/model, round prompts, and the limits (rounds/budget/timeouts) — and get an explicit yes. Do not submit a dynamic flow without that confirmation.

**Error handling:** a coded `Error (<code>): <message>` is an actionable server rejection — fix the YAML per the message and retry. An uncoded error whose text mentions "may not support dynamic flows" means the server predates this feature — fall back to a catalog `definition_id` instead of retrying the same YAML. Other coded rejections you may see: `dynamic_run_limit` (you already have the maximum number of active dynamic runs — close one with `close_flow` first), `dynamic_flows_disabled` (the admin turned dynamic flows off — use a catalog definition or ask an admin), and `budget_unverifiable` on a later send (retryable — the server can't read the run's cost data yet; wait briefly and resend, or close the run).

## If the flows MCP tools are not loaded

After applying the role-surface safety gate, if `start_flow` / `send_to_participant` are not among the tools available in this session, do NOT try to obtain them:

- Do NOT run `kcap mcp flows` from a shell, do NOT handshake it over stdio/JSON-RPC, and do NOT edit any MCP configuration.
- Only treat the session as a hosted participant when `submit_review_result` is present and the prompt carries the round-token/result contract. Tool absence alone is not proof.
- If neither the flow-driver tools nor that reviewer contract is present, tell the user to run `kcap setup` or reinstall/update the plugin and restart the harness. Do not hand-roll JSON-RPC or edit MCP configuration from the session.

## Core rules

1. **Start exactly one flow per user task.** Call `start_flow` once and hold the returned `flow_run_id`. Do NOT start a new flow for follow-up rounds — reuse the same ID.
2. **After receiving a non-clean result**, address it, then call `send_to_participant` with the same `flow_run_id` and the updated message.
3. **Do NOT finish the user task while the flow has unresolved results.** Keep iterating until the definition's clean/complete signal.
4. **Only call `close_flow` after the clean signal.** The run stays open until you explicitly close it — don't rely on it closing itself. Then report completion to the user.
5. **If participant output is unclear or requires user input**, pause and ask the user before proceeding.
6. **Never start a nested flow.** If you are the hosted participant (see above), do not call these tools yourself.
7. **Address each role independently.** A flow definition declares one or more participant roles in its `participants` map (single-participant definitions use `reviewer`). A multi-participant `start_flow` returns no round — nothing has launched yet. Call `send_to_participant(flow_run_id, participant=<role>, message=…)` naming the role you want to address; its first message launches that role's agent lazily. Only one round is in flight per role at a time — sending to a role that's still working on a round gets a `409` naming the busy round — but every OTHER role stays addressable in the meantime. Sending an unknown role is rejected by the server, which names the valid roles in its error.
8. **For a code review flow (`definition_id: "code-review"`), do NOT ask the participant to run tests.** CI covers test execution; participant feedback is on correctness, design, and adherence to conventions.
9. **State where your changes live.** The participant's worktree is mirrored from **this session's project directory**, not from the directory you are working in, and no tool parameter can redirect it. So if you changed directory, are a subagent in another checkout, or the changeset lives in another worktree/repo/machine, the participant will NOT see it: say so in `context`/`message`, give it an explicit commit range (never `git diff origin/main...HEAD`), and inline the diffs — or pass `mode: "context-only"` to make your context the sole source of truth. The participant flags referenced changes it cannot find; incomplete context wastes a full round.

## Pending messages

Participants can push you out-of-band notes between rounds — observations that don't warrant a full round result (e.g. "found something odd, still looking"). These ride along as `pending_messages` on `start_flow` (a single-participant start returns round 1's result, which can already carry them), `get_flow_status`, `send_to_participant`/`submit_review_round`, and `close_flow` responses, rendered as a list of `from <role> [<id>]: <text>` entries. React to each message **once, by its `<id>`**, the moment you see it. Delivery is acknowledged after rendering, so a message normally never reappears — but if that acknowledgment fails, the SAME message (same id) is redelivered on a later call: treat a repeated id as already handled, never react to it twice. `close_flow`'s response can carry final pending messages too — often the last thing a participant tells you — so read them before you report completion to the user.

## Guardrail errors

The server enforces per-run budgets; watch for these in tool error responses:

- **`400` containing `max_rounds (N) reached for this run — close the flow.`** — the run is still **open**, it's just hit its round cap. Stop submitting further rounds, summarize what you have, and call `close_flow`.
- **`400` containing `budget_exceeded: …`** — the run has **already failed** and all participant agents have stopped. Report this to the user; do NOT retry and do NOT call `close_flow` — closing a failed run overwrites the failure status in the read model (the projector flips `failed` → `closed`), hiding what went wrong.
- **A round whose participant goes inactive for the definition's `round_timeout`** (an inactivity bound, not a wall-clock cap — an actively-working participant can run well past it) lands as a terminal **`unclear`** round, with the reason explained in its result text — if you check round status programmatically, look for `unclear` and read the text. This means the participant genuinely went dead or silent, not merely slow. The run itself stays open — you may submit another round to that role (it relaunches automatically, see the `participant_died`/`participant_stopped` entry below) or close the flow.
- **Idle runs are auto-reaped** after the definition's `idle_ttl` (server default 24h). Don't rely on this — always call `close_flow` yourself once you're done, whether the outcome was clean or you're abandoning the task.
- **`400` starting `no_daemon_available:`** — no connected daemon has the repo checked out. Relay the server's remediation verbatim, then act on the part you can: run `kcap daemon start -d` on a machine with the repo cloned (add `--name <new-name>` if the account already runs a daemon elsewhere), or get the repo cloned on a machine that already runs one. **You cannot redirect the flow at another daemon or checkout** — these tools expose no daemon or repo-path parameter. If the server's text suggests passing `daemon_name` / `repo_path`, ignore that part: do not invent those arguments, and do not retry unchanged.
- **`400` starting `daemon_outdated:`** — the daemon's kcap is too old to host flow participants. Relay the server error's remediation verbatim — it names the outdated daemon; the fix: update kcap (`npm i -g @kurrent/kcap`), then `kcap daemon restart --name <its-name>` (works for detached and service-managed daemons alike — a raw `daemon stop` deliberately refuses a service-managed one; `kcap daemon status` lists names, and `--when-idle` defers the restart if the daemon is busy).
- **`409` containing `participant_unreachable`** — that role's agent is in an ambiguous liveness state (its daemon disconnected or is restarting) or a possibly-live prior agent was spotted and the server is refusing to relaunch until its absence is proven, so it won't guess whether it's still alive rather than risk a duplicate launch. This is retryable — do not close the flow. Retry the send shortly; the server relaunches automatically once absence is proven, or ask the user to stop the participant (dashboard/API) and then re-send to force a fresh relaunch.
- **Reserved-alias reviewer-model errors** (only when you passed `model`): `reviewer_model_protocol_required` — the server/daemon doesn't support a model override yet; drop `model` and retry, or tell the user to update. `reviewer_model_unavailable` — no resolver for that vendor recognizes the model; ask for a different one or drop the override. `model_vendor_mismatch` — the model belongs to a different vendor than the one selected; ask the user which vendor they meant. `reviewer_model_safe_settlement_required` — the server can't safely reapply this model on a heal/relaunch; close the flow and start a new one. `reviewer_model_unpriceable` — the model has no resolvable pricing to budget-check; ask for a differently-named/priced model.
- **A round result of `unclear` whose text is exactly `participant_died`, `participant_stopped`, or `participant_parked`** — that role's agent crashed, was stopped, or was parked for resume mid-round. In every case the run stays **open** and you address the same role again with `send_to_participant` to continue. The difference is what the next round gets: for `participant_died`/`participant_stopped` a **fresh** agent relaunches with **no memory of prior rounds**, so restate any context it needs; for `participant_parked` the same agent **resumes with its prior context intact** (a resumable park frees the slot between rounds). Earlier spend still counts against the run budget. No need to close and restart the flow; other roles are unaffected and remain addressable. (An older server reports a park as `participant_stopped` — still a resubmit trigger, so this works across the rollout.)

## Workflow

Single-participant definitions start eagerly — round 1 runs as part of `start_flow`:

```
start_flow(definition_id, target_kind, target_ref, target_title, context)
  → participant returns a result: kind findings (with the result text) | kind clean

if clean:
  close_flow(flow_run_id)
  report completion to user
  DONE

if findings:
  address the result
  send_to_participant(flow_run_id, participant="reviewer", message=…)
    → repeat until clean
  close_flow(flow_run_id)
  report completion to user
```

Multi-participant definitions start round-less — you address each role yourself, and the run is clean only in aggregate:

```
start_flow(definition_id, target_kind, target_ref, target_title, context)
  → no round yet — roles have not launched

send_to_participant(flow_run_id, participant="reviewer", message=…)
  → launches the reviewer's agent; returns kind findings | clean

send_to_participant(flow_run_id, participant="tester", message=…)
  → launches the tester's agent independently — no need to wait on the reviewer's round;
    returns kind findings | clean

# a role with an open round in flight 409s if you send to it again — address the OTHER
# role(s) in the meantime, then come back once its round completes

loop until every addressed role's latest round is clean and none is in flight:
  address whichever role(s) still have findings
  send_to_participant(flow_run_id, participant=<that role>, message=…)

close_flow(flow_run_id)   # only once reviewer AND tester are both clean
report completion to user
```

## Tool reference

| Tool | Required args | Optional args | When to call |
|---|---|---|---|
| `start_flow` | Exactly one of `definition_id` (catalog id, e.g. `spec-review`, `code-review`, or a custom catalog id) or `definition_yaml` (inline dynamic definition — see "Composing a dynamic flow"); plus `target_kind` (what is being worked on: `spec`, `code`, `pr`, `branch`, `file`, etc.), `target_ref` (a path, branch name, or PR URL/number that identifies the target), `target_title` (short human-readable title), `context` (background context: what to focus on, constraints, definition of done) | `vendor` (reserved aliases only — explicit reviewer vendor; omit to use the definition's authored vendor, or your saved `flows.reviewer_vendor` preference if it declares none), `model` (reserved aliases only — explicit reviewer model override; REQUIRES `vendor`, rejected on dynamic/multi-participant starts), `instructions`, `mode` (`context-only` — optional; by default the participant's worktree is mirrored from THIS SESSION's project directory, not from the directory you are working in. Pass `context-only` to opt out and treat the submitted context as authoritative) | Once, at the start of a flow task. |
| `send_to_participant` | `flow_run_id`, `participant` (role name declared in the flow definition's `participants` map; single-participant definitions use `reviewer` — an unknown role is rejected, naming the valid ones), `message` | `instructions`, `async` (defaults to `true`) | After addressing a non-clean result for that role, or to launch a role for the first time. Pass the same `flow_run_id`, the role's name, and the updated message. |
| `get_flow_status` | `flow_run_id` | `wait` (`true`/`false`, defaults to `false`) — when `true`, blocks until the round is terminal or roughly 8 minutes pass, instead of returning the current snapshot immediately | Poll or check the current status of a flow run (running, waiting, completed, failed). Use `wait: true` to ride out a long round instead of polling repeatedly yourself. |
| `close_flow` | `flow_run_id` | — | Only after the definition's clean signal — or when abandoning the task early; the run otherwise stays open until closed. |

## Example (custom definition)

```
# Step 1 — start (all five required args required; the participant sees a mirror of THIS SESSION's
# project directory, not of the directory you are working in — pass mode="context-only" to opt out)
start_flow(
  definition_id="code-review",
  target_kind="branch",
  target_ref="feature/add-null-check",
  target_title="Add null check on user input",
  context="Review the diff on this branch for correctness and adherence to project conventions."
)
# → returns flow_run_id, e.g. "flow_abc123"
# → participant returns kind findings: missing null check on line 42

# Step 2 — address findings, then send a follow-up to the reviewer participant
send_to_participant(
  flow_run_id="flow_abc123",
  participant="reviewer",
  message="Fixed null check on line 42. Updated diff attached."
)

# Step 3 — participant returns kind clean
close_flow(flow_run_id="flow_abc123")
# Report to user: flow complete, all findings resolved
```

## Example (two roles: reviewer + tester)

`review-and-test`'s `participants` map declares `reviewer` and `tester` — each is addressed independently, and neither's `send_to_participant` waits on the other's round:

```
# Step 1 — start; multi-participant, so no round comes back yet
start_flow(
  definition_id="review-and-test",
  target_kind="branch",
  target_ref="feature/add-null-check",
  target_title="Add null check on user input",
  context="Review the diff on this branch and write/run tests for the new code path."
)
# → returns flow_run_id, e.g. "flow_xyz789"; no round in the response

# Step 2 — address both roles; each launches lazily on its first message.
# These are independent — send to tester without waiting for the reviewer's round.
send_to_participant(flow_run_id="flow_xyz789", participant="reviewer", message="…")
  # → launches the reviewer; returns kind findings: missing null check on line 42
send_to_participant(flow_run_id="flow_xyz789", participant="tester", message="…")
  # → launches the tester; returns kind clean: added a null-input test case, passing

# Step 3 — only the reviewer had findings; fix them and send a follow-up to JUST that role
send_to_participant(
  flow_run_id="flow_xyz789",
  participant="reviewer",
  message="Fixed null check on line 42. Updated diff attached."
)
# → reviewer returns kind clean

# Step 4 — close only once EVERY addressed role's latest round is clean and none is
# in flight: reviewer clean + tester clean (from step 2, still current) = aggregate clean.
# One role going clean does not end the run by itself — track each role's latest result
# from your own send_to_participant responses; the run's status only reads "clean" once
# every addressed role's latest round is clean and none is in flight (the aggregate rule).
close_flow(flow_run_id="flow_xyz789")
# Report to user: flow complete, review and tests both clean
```
