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.
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
- Add an MCP server entry (project
.mcp.jsonor~/.claude.json):
{
"mcpServers": {
"flowwork": {
"command": "flowctl",
"args": ["channel"],
"env": { "FLOWCTL_SERVER": "http://localhost:9010/api" }
}
}
}
- 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.
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
- Get the channel id — from the ready event, or the
flowwork_channel_infoMCP tool. Reuse it for the whole session. - Start tagged:
flowctl run start --workflow <name> --channel <channel_id> -q. - 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 browserrender_url.) - Webhook →
flowctl webhook call <exec> <webhook_path> --payload '{…}'. - Agent prompt (
interaction="agent") → the run is asking you to run a prompt. The event carries the prompt, theresult_type, and the allowlisted context. Do the work, thenflowctl agent submit <exec> <name> --type json --result '{…}'(or--error '…').
- Form → read the schema in the event, gather the fields, then
- 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
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.