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
- Python 3.10 or newer. The bridge is a single standard-library file, and the built page ships next to it, so Node is optional.
- A clone of github.com/Harshkamdar67/airlock-relay.
- A WebMCP-capable browser for the agent side: the ChatGPT desktop app's browser, or Chrome 149+ with
chrome://flags/#enable-webmcp-testingenabled. - On Windows, Git Bash, which Airlock already requires. The bridge runs Airlock's launcher through it.
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
- Copy
bridge/hooks.example.jsoninto~/work/app/.claude/settings.local.json. - Replace every
RELAY_HOOKwith the absolute path tobridge/claude_code_hook.py. - 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.
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
| Flag | Meaning |
|---|---|
--source airlock|claude-code|generic | Where facts come from. Defaults to airlock when a router URL is present, else claude-code. |
--router-url URL | Airlock router. Defaults to AIRLOCK_SESSION_ROUTER_URL. |
--workdir PATH | The session's directory. Used to find the session id and to resume there. |
--task TEXT | Label shown in the console. |
--model ID, --provider NAME, --models A,B | Initial facts for claude-code and generic sources until events say otherwise. |
--notify URL | Webhook to POST when the session blocks. |
--no-desktop-notify | Skip the desktop notification. |
--open | Open the console in the default browser. |
--dry-run | Prepare the resume command but never launch a terminal. |
--port N | Loopback 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" } }
| failure | Effect |
|---|---|
rate_limit, billing_error, account_on_hold, authentication_failed | The whole provider is marked on cooldown for 15 minutes. |
overloaded, server_error, model_not_found, max_output_tokens | Only 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
| Check | How |
|---|---|
| Tools discovered and executed through the browser's own WebMCP API | Chrome smoke test on the deployed URL, Chrome 152 with the WebMCP flag. |
| Live Airlock mode on a real router | Bridge 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 bridge | Executed handoff ran the command on a real install with the declared chain kept; verified and restored. |
| Approval gate on the bridge | Forged handoff and resume requests refused; genuine approve then execute accepted. |
| Claude Code hooks with the real CLI | SessionStart, Stop, and SessionEnd reached the bridge with a real session id. |
| Resume, for real | On 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
- Live mode shows no plan-headroom numbers, because the router does not expose them.
- Routes outside the built-in catalog, such as OpenRouter and open-model slugs, are not listed.
- The bridge is loopback only and trusts local processes; it is a personal tool, not a shared service.
- Block detection for plain Claude Code was verified with the documented StopFailure payload, not with a live provider failure.
- macOS and Linux command paths follow standard patterns but were not executed during this build.