Connect your agent

The hosted console is a demo. To supervise your own session, run the local bridge. It serves the same page on your machine and feeds it real facts, records your approvals, and resumes the conversation on the model you approve.

Requirements

Airlock session

Run the bridge from inside the Airlock session you want to supervise, in the project directory. It reads the router URL from the session environment and finds the Claude Code session id from the transcript directory for that folder.

# inside an Airlock session, in your project
python bridge/relay.py --task "what the session is doing" --open

# or from anywhere, naming the router and the working directory
python bridge/relay.py --router-url http://127.0.0.1:PORT --workdir ~/work/app --open

What you get: the real profile, active model, cooldowns, every request and failover event, context size, and this session's id. When the agent's handoff is approved and executed, the bridge runs airlock handoff set <from> <target> … with the approved target first and your existing chain kept, and prepares airlock hybrid <route> --resume <session-id>.

Plain Claude Code

No Airlock needed. Claude Code's hooks post session events to the bridge; StopFailure is the block signal.

python bridge/relay.py --source claude-code --workdir ~/work/app --model claude-sonnet-5[1m] --open
  1. Copy bridge/hooks.example.json into ~/work/app/.claude/settings.local.json.
  2. Replace every RELAY_HOOK with the absolute path to bridge/claude_code_hook.py.
  3. Start Claude Code in that directory. Each hook posts to the bridge and exits within a second; Claude Code never waits on it.

Resume becomes claude --resume <session-id> --model <alias>. Honest scope: switching Claude models helps with overloaded, server error, and model errors. It does not help with a plan-wide rate limit, because the plan is the same. Cross-provider continuation needs Airlock.

Any agent

Start with --source generic and POST small JSON documents to the bridge. See the ingest contract below.

python bridge/relay.py --source generic --model gpt-5.6-sol --provider openai --models gpt-5.6-sol,gpt-5.6-terra --open

curl -X POST http://127.0.0.1:4783/relay/api/ingest -H "Content-Type: application/json" \
  -d '{"state":{"session_id":"abc-123","cwd":"/home/me/app","model":"gpt-5.6-sol","provider":"openai"}}'
curl -X POST http://127.0.0.1:4783/relay/api/ingest -H "Content-Type: application/json" \
  -d '{"event":{"kind":"failure","failure":"rate_limit","summary":"429 after 3 retries"}}'

Open the console

Go to http://127.0.0.1:4783/app/ in the ChatGPT desktop browser or in Chrome with WebMCP enabled. The top bar shows Live and WebMCP · 7 tools. Hand the page to the agent with the prompt in the banner, or write your own. The agent inspects and proposes; you approve.

🔒
The bridge trusts only what it recorded. Your Approve click is registered on the bridge with a one-shot nonce. A page script or any other local process cannot make the bridge run the real switch without it, and the resume button is gated by a second nonce.

Testing without ChatGPT

Chrome 149+ with chrome://flags/#enable-webmcp-testing is the other supported judging environment. Install Google's Model Context Tool Inspector extension from the Chrome Web Store, open the console, and the extension lists the seven tools, lets you invoke them with JSON by hand, and can imitate an agent conversation over them. The repository's scripts/webmcp-smoke.mjs does the same through Chrome's own document.modelContext API and is what the maintainers run against the live URL.

The console itself has a command palette (CtrlK), keyboard shortcuts (A approve, R reject, ? list), inspectable tool calls (click any row in Agent activity to see its input and output), and a one-click Markdown incident report.

Notifications

When the session becomes blocked, the bridge shows a desktop notification on Windows, macOS, and Linux. Add a webhook to get pushed anywhere:

python bridge/relay.py --notify https://ntfy.sh/your-topic            # phone push via ntfy
python bridge/relay.py --notify https://hooks.slack.com/services/…     # Slack incoming webhook
python bridge/relay.py --notify https://discord.com/api/webhooks/…     # Discord webhook
python bridge/relay.py --notify https://example.com/hook                # any JSON receiver

Resume

After an executed handoff the console shows a Resume in a new terminal button and the exact command. Exit the blocked session first, then click. The bridge opens a terminal in the working directory and runs the launcher, which continues the same conversation on the approved model. The bridge never kills or restarts a session on its own.

Bridge flags

FlagMeaning
--source airlock|claude-code|genericWhere facts come from. Defaults to airlock when a router URL is present, else claude-code.
--router-url URLAirlock router. Defaults to AIRLOCK_SESSION_ROUTER_URL.
--workdir PATHThe session's directory. Used to find the session id and to resume there.
--task TEXTLabel shown in the console.
--model ID, --provider NAME, --models A,BInitial facts for claude-code and generic sources until events say otherwise.
--notify URLWebhook to POST when the session blocks.
--no-desktop-notifySkip the desktop notification.
--openOpen the console in the default browser.
--dry-runPrepare the resume command but never launch a terminal.
--port NLoopback port, default 4783.

Ingest contract

POST to /relay/api/ingest. Send a state document at start and whenever it changes, and one event per thing that happened.

{ "state": { "session_id": "abc-123", "cwd": "/home/me/app", "model": "gpt-5.6-sol",
             "provider": "openai", "models": ["gpt-5.6-sol", "gpt-5.6-terra"] } }

{ "event": { "kind": "failure", "failure": "rate_limit", "summary": "429 after 3 retries" } }
{ "event": { "kind": "turn_completed", "summary": "Edited 4 files, tests pass" } }
{ "event": { "kind": "PostToolUse", "tool_name": "Bash" } }
failureEffect
rate_limit, billing_error, account_on_hold, authentication_failedThe whole provider is marked on cooldown for 15 minutes.
overloaded, server_error, model_not_found, max_output_tokensOnly the active model is marked on cooldown.

Claude Code's hook event names are understood as kinds directly. Everything is loopback only.

What was verified for real

CheckHow
Tools discovered and executed through the browser's own WebMCP APIChrome smoke test on the deployed URL, Chrome 152 with the WebMCP flag.
Live Airlock mode on a real routerBridge started inside a real Airlock session; page showed the real profile, model, events, and the session's Claude Code id.
Real airlock handoff set through the bridgeExecuted handoff ran the command on a real install with the declared chain kept; verified and restored.
Approval gate on the bridgeForged handoff and resume requests refused; genuine approve then execute accepted.
Claude Code hooks with the real CLISessionStart, Stop, and SessionEnd reached the bridge with a real session id.
Resume, for realOn a scratch session, the Resume button opened a terminal running airlock hybrid sonnet --resume <id>; a Claude Code process launched by Airlock with that id was observed, then stopped.

Limits