Claude Code channels

Bidirectional human-in-the-loop — the engine pushes parked forms, webhooks, and agent prompts straight into your Claude Code session.

Normally you drive flowwork one way: flowctl run start, then poll run status. The channel bridge adds the reverse direction — when a run parks for input (a form, a webhook, or an agent() prompt), the engine pushes an event straight into your Claude Code session so you can gather the values and submit them, no polling.

Install the flowwork skill first

Before setting this up by hand, run flowctl skill install. The flowwork skill teaches Claude Code the whole author → register → run → drive loop and how to use channels — so authoring and operating workflows (including responding to the events below) is largely automated. The steps here are what the skill does for you under the hood.

How it works

flowctl channel is a small MCP server that Claude Code spawns over stdio. It registers a per-session channel id, long-polls the engine for events tagged with that id, and re-emits each into your session. You start runs with --channel <id>; the engine stamps the id into the run and, on each park, enqueues an event the bridge delivers.

run start --channel <id> ─▶ engine stores the channel id on the run
   run parks (WAITING_FORM/WEBHOOK/AGENT) ─▶ engine enqueues an event
      flowctl channel long-poll ─▶ <channel interaction="form" execution_id=… schema=…>
         you gather input ─▶ flowctl form submit … / webhook call … / agent submit …  ─▶ run resumes

Each Claude session spawns its own bridge with its own channel id, so events never cross sessions.

Setup

  1. Add an MCP server entry (project .mcp.json or ~/.claude.json):
{
  "mcpServers": {
    "flowwork": {
      "command": "flowctl",
      "args": ["channel"],
      "env": { "FLOWCTL_SERVER": "http://localhost:9010/api" }
    }
  }
}
  1. Launch Claude Code with the channel enabled:
claude --dangerously-load-development-channels server:flowwork

On start you’ll see a <channel source="flowwork" event="ready" channel_id="…"> event.

Requirements

Claude Code ≥ 2.1.80, authenticated via claude.ai or a Console API key. The flowctl binary on PATH and a reachable engine (FLOWCTL_SERVER, default http://localhost:9010/api).

The loop

  1. Get the channel id — from the ready event, or the flowwork_channel_info MCP tool. Reuse it for the whole session.
  2. Start tagged: flowctl run start --workflow <name> --channel <channel_id> -q.
  3. React to events:
    • Form → read the schema in the event, gather the fields, then flowctl form submit <exec> <form_name> --data '{…}'. (Complex forms include a browser render_url.)
    • Webhookflowctl webhook call <exec> <webhook_path> --payload '{…}'.
    • Agent prompt (interaction="agent") → the run is asking you to run a prompt. The event carries the prompt, the result_type, and the allowlisted context. Do the work, then flowctl agent submit <exec> <name> --type json --result '{…}' (or --error '…').
  4. Confirm with flowctl run status <exec> / run watch.

Try it end to end

# 1. Engine (desktop auto-seeds the workspace/user):
flowwork &

# 2. Add the .mcp.json above, then start Claude with the channel:
claude --dangerously-load-development-channels server:flowwork

# 3. In the session, ask Claude to:
#    - read its channel id (flowwork_channel_info)
#    - flowctl def create --file simple-approval.js
#    - flowctl run start --workflow SimpleApproval --channel <id>
#  → a <channel interaction="form"> event appears with the approval_form schema
#    - flowctl form submit <exec> approval_form --data '{"approved":true}'
#  → flowctl run watch <exec>  shows COMPLETED
Security

The channel_id is a high-entropy bearer token generated locally; whoever holds it receives that channel’s events. The bridge only dials out (no inbound listener). On a desktop / local engine this relies on network trust, which is exactly the intended environment.

The agent() step is the workflow side of the agent leg; this page is the session side.