feat(examples): Band × Docker three-agent design-meeting demo (INT-1118)#480
Open
AlexanderZ-Band wants to merge 22 commits into
Open
feat(examples): Band × Docker three-agent design-meeting demo (INT-1118)#480AlexanderZ-Band wants to merge 22 commits into
AlexanderZ-Band wants to merge 22 commits into
Conversation
AlexanderZ-Band
marked this pull request as ready for review
July 22, 2026 16:56
Three framework-diverse agents — PM (Claude SDK), Developer (Codex), Architect
(CrewAI) — each in its own Docker sandbox, collaborate in one Band room to design
a URL shortener. A host-side conductor + circuit breaker bounds the conversation;
the spine is credential custody: real keys never enter a VM.
- breaker.py: pure, unit-tested conversation circuit breaker (soft nudge +
add-fallback, hard-kill on cap/wall-clock, clean terminate on decision)
- conductor.py: host-side room driver enforcing the breaker; human presenter seat
- agents/{pm,dev,architect}: per-sandbox workspaces (adapter + persona + uv.lock)
- launch.sh: one-command build/up/down, env-file switch (prod default / dev via
.env.test), per-sandbox log panes + auto-open Band UI
- provision.py: register/teardown agents (demo-marker-scoped sweep + rollback)
- Dockerfile.cli: base kit + pinned claude/codex CLIs
- skill/SKILL.md + README runbook
Validated live on dev: never-in-VM inference (claude + codex authenticate through
the sbx proxy via a placeholder), provisioning, sandbox→Band egress, the circuit
breaker (wall-clock hard-kill fired), and teardown.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nager breaker Address a launcher review (SBX 0.35.0): - Rebuild launch.sh around explicit resource ownership: a .demo/run manifest records every sandbox, scoped secret, and global policy rule the run creates; cleanup removes ONLY those and reports each failure (no blanket `|| true`), so a stray/pre-existing band-demo-* is never clobbered and leftover secrets are surfaced. Phases: preflight -> create -> cleanup. - Grant provider/Band egress as GLOBAL rules BEFORE `sbx create` (per the project sbx helper: sbx can't scope a rule to a not-yet-created sandbox and the agent connects at startup) — removes the startup/policy race. - Remove scoped secrets on teardown (`sbx secret rm <sandbox> --host <host> -f`); they persist in the host store otherwise. - Split preflight: `build` needs tools only (Docker daemon + initialized sbx policy checked); live keys are required only for `up`. - Arm cleanup before the first mutation; abort rather than clobber an existing demo sandbox. Breaker: add context-manager protocol — `with breaker:` guards the meeting like a lock, and leaving the block always closes it (a runaway can't outlive the guarded region), exceptions not suppressed. +2 tests (18 total). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split the breaker's single `_handoff` flag into three independently-proven facts —
handoff_requested / architect_present / decision_received — to fix live-demo
failure modes:
- A non-verdict Architect message ("let me review…") no longer ends the meeting:
only a message with an explicit DECISION:/VERDICT: marker does
(ObservedMessage.is_final_decision, projected by the conductor; architect
persona leads its ruling with `DECISION:`).
- A mere @mention no longer suppresses the add-fallback — only an actually-present
Architect does. A PM invite that never landed still gets a deterministic add.
- Two-tier handoff: soft_cap → nudge the PM; handoff_deadline further messages with
the Architect still absent → add it. No more nudge+force-add in one cycle.
- Grace timer now measured on the conductor clock (poll receipt time), not the
platform message timestamp — clock skew can't skew it.
- Documented that a decided meeting is bounded by wall_clock + grace (not "absolute").
Tests: +6 for the ambiguities (non-verdict message, mention-without-add, clock skew,
decision-beats-cap, present-architect). 22 total, all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the runbook + skill in line with the reworked launcher and breaker:
- Files/build: Dockerfile.cli (base + claude & codex CLIs); images are
band-python-kit{,-cli}:local; build needs Docker + sbx only, no keys.
- Never-in-VM proof: LLM keys are placeholders too (*_PROXY_KEY), not just Band.
- Circuit breaker: two-tier handoff (nudge -> handoff-deadline add), decision
requires a DECISION: marker, timers on the conductor clock, decided meeting
bounded by wall_clock + grace (not "absolute").
- Cleanup: manifest-owned; removes only what the run recorded; also on exit.
- Replaced the stale "rehearsal-gated confirmations" with a factual Status
(validated live on dev; what's left = one full happy-path run + codex WS noise).
Also expose DEMO_HANDOFF_DEADLINE via ConductorSettings so every cap is env-tunable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nistic open Make the design meeting watchable instead of three agents agreeing in different accents (INT-1118 review): - Give each persona a concrete stake that forces a trade-off: Maya wants a shareable-link MVP this sprint (aliases/expiry/analytics, no accounts/admin); Sam holds an operability line (random base62, Postgres SoT, collision/abuse story; resists custom aliases in v1); Jordan may challenge one assumption before ruling and protects migration/ops simplicity. - Deterministic opening: the conductor briefs the PM alone (both agents are still participants), and Maya opens by @mentioning Sam with one specific question — so both agents don't open at once. - Architect verdict is machine-readable and audience-visible: first line `VERDICT: APPROVED | APPROVED WITH CHANGES | NEEDS REWORK` (the breaker's decision marker already matches it). - README/SKILL updated to the VERDICT wording. Bounded as before — Maya still drives to a call and hands off; the breaker caps are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
sbx 0.35.0 set-custom has no stdin for --value, so the real key briefly appears in the launcher's process args (visible to same-user host processes). Documented as a run-on-a-trusted-host caveat — no stdin alternative exists to fix it in code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, cp, codex) - Cleanup keeps its manifest on failure: RUN_DIR is removed only after a fully clean teardown, so `down` can retry recorded resources (was: deleted always). - Single source for breaker caps: BreakerConfig holds the defaults; ConductorSettings reads them (env still overrides), ending the 300-vs-600 wall-clock drift. Guarded by a new test asserting the two agree. - Single source for provider LLM data: one provider_llm() supplies host/env/placeholder to both egress and secret setup (no hardcoded host list in grant_egress). - Staged workspace copy uses `cp -R "$src/." "$stage"` (includes dotfiles; robust). - codex login now fails loudly with a redacted error instead of swallowing it. - Breaker context-manager docstrings no longer overstate: __exit__ bounds the breaker's own state; stopping agents is teardown's job (kept the CM — requested). Deferred/declined with reasoning: one cross-language demo manifest (large refactor, bounded 3-role duplication; the concrete drift it cited is fixed above); reading *_PROXY_KEY via BaseSettings (it's subprocess credential plumbing, not app config). 23 tests green; shellcheck + ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The panes tail each sandbox log, but the agents never configured logging, so only WARNING+ surfaced — the panes showed startup/sync then went quiet (INT-1118 review). - Each agent entrypoint now calls logging.basicConfig(level=INFO): the Band lifecycle trace (messages, tool calls, replies) is written to stderr, which the kit's startup log captures (fd-inherited through the launcher's execve) and the pane tails. That's the fix for "show all three live agent processes". - spawn_terminals no longer silently skips off macOS: without osascript it prints the exact `sbx exec … tail -f` command per sandbox so the panes are still openable by hand. The tail command lives in one pane_cmd() source. Declined with reasoning: a separate app-owned agent.log + process-tree redirect — the startup log already durably captures the agent's stdout/stderr (verified live), so a second file duplicates the same stream; the piped codex/claude protocol output (finding 3) is not what the demo shows — the adapter's INFO logs are. Verified: shellcheck + ruff clean, 23 tests green, all three entrypoints parse. The richer-pane effect itself is confirmable only in the live run (task #10). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ale docs - up() no longer tears down the moment the meeting ends: it pauses (non-headless) so the presenter can keep chatting with the participants and ask questions in the Band UI. Teardown (which reaps the agents — after which their names stop resolving in room history, the source of the '@unknown' seen post-run) waits for an explicit Enter. Headless runs skip the pause so automation doesn't hang. - Fix stale docstrings that claimed 'sbx secret set -g openai'; the impl injects a placeholder via scoped 'sbx secret set-custom' on api.openai.com. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Register agents as 'Maya (PM)' / 'Sam (Dev)' / 'Jordan (Architect)' so the Band UI labels and mention chips read clearly; align the PM's architect lookup name to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the blind post-meeting read pause (needed a TTY, broke when run
non-interactively) with a message-driven open floor owned by the conductor:
- After the verdict, hand the room to the presenter instead of closing on
grace. The meeting ends on an end phrase (end meeting / /end / wrap up /
adjourn), on open-floor idle (default 7 min of presenter silence), or on
Ctrl-C (trap INT/TERM -> clean teardown). Headless keeps immediate
grace-terminate via DEMO_INTERACTIVE=false so automation never waits.
- End phrase honored at any time, pre- and post-verdict.
- Conductor tracks its own posts (shared presenter identity) so the invite
and closer don't count as presenter activity.
- tmux log panes (one window, visible in Warp) replacing osascript-only
Terminal.app windows; kill the session on teardown.
- Fix room web URL to /chat/{id} (singular); add a timestamped room title.
- Longer autonomous phase (soft_cap 10, hard_cap 24, wall_clock 900s).
- Shell-quote agents.env so titled display names survive sourcing.
- Drop the presenter skill (can't run interactively in-session); README is
the single source of truth.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r ending Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…; factual README Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, tuning Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ts; de-dup README - conductor: unstamped message sorted with a tz-aware sentinel — a naive datetime.min raised TypeError against tz-aware platform stamps and crashed the poll loop. Regression test drives it through a fake messages client. - breaker tests: anchor the open-floor timeline on VERDICT_AT so deadlines read as (VERDICT_AT + idle) instead of bare timestamps. - README: state the ending mechanics once (Ending the meeting) and cross-link from the Circuit-breaker tiers and Expected-output instead of repeating them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AlexanderZ-Band
force-pushed
the
feat/docker-demo-ready-decide-the-demo-scenario-for-the-INT-1118
branch
from
July 22, 2026 17:03
e220465 to
7fbe568
Compare
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retarget the three-agent design meeting from a URL shortener to a recursive topic — how to run untrusted AI agents in isolated Docker sandboxes — so the agents debate the very isolation the demo runs them under. Rewrites the PM/Dev/ Architect personas (plain container vs. sbx/Firecracker microVM vs. gVisor/Kata, wire-injected secrets, egress) and the DEMO_TOPIC default; handoff + VERDICT machinery unchanged. Enable AdapterFeatures emission so the meeting's mechanics surface live in the room: EXECUTION (tool_call/tool_result) + THOUGHTS on all three, MEMORY tools on the Claude SDK + CrewAI agents, TASK_EVENTS kept on Codex. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retarget the scenario prose, DEMO_TOPIC default, and Watch-it-run to the recursive Docker-isolation topic, and note that agents now emit tool calls, results, and reasoning into the room. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CrewAIAdapter.SUPPORTED_EMIT is {EXECUTION} only, so Emit.THOUGHTS on the
architect was silently dropped and logged a UserWarning at startup. Emit only
EXECUTION there, and correct the README to say reasoning surfaces only where the
framework exposes it (not CrewAI).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rio-for-the-INT-1118
Loading an example script (conductor.py) inserts its package root at sys.path[0] on import; leaking that reordered the global path and shadowed the arena test's top-level `prompts` import (two example trees both expose a `prompts` module), breaking collection. The loaded module's imports are already cached in sys.modules, so the path is only needed during the load.
Example trees expose same-named top-level modules (four different `prompts`), and tests reached them through global sys.path mutation in three places (example_agents conftest, a module-level insert in the a2a orchestrator test, and leaks from exec'd example scripts) — so which module `import prompts` found depended on collection order, which is how the docker-demo tests broke arena collection. Make load_script_module the single mechanism: it now puts the script's own directory on sys.path only for the duration of the load (restoring the path afterwards, covering the script's own inserts too) and requires globally unique module names. Convert the arena and a2a orchestrator tests to it and delete the conftest path inserts nothing consumes now.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A live, watchable three-agent design meeting run from the Band Docker kit. Three
agents — each a different framework in its own Docker sandbox (sbx) —
collaborate in one Band room:
band-demo-pmband-demo-devband-demo-architectThe topic is recursive: the agents design how to run untrusted AI agents in
isolated Docker sandboxes — the very thing running them. Maya and Sam align,
Maya hands off to Jordan, Jordan returns one
VERDICT:.Spine — credential custody
Every API key stays on the host and is injected on the wire; no key ever enters a
VM. The VM holds only placeholders (
proxy-managed/ ansk-…-shaped sentinelunder
*_PROXY_KEY); the sbx proxy swaps them on outbound requests. The kitlauncher fails the launch if a real Band key is found in the VM.
Safety — circuit breaker
A host-side conductor drives the room and enforces a pure state-machine breaker
(
breaker.py) so the meeting always ends: nudge → add-fallback → hard-kill(turns or wall-clock), and a verdict opens the floor to the presenter (interactive)
or closes on grace (headless). Only agent messages move the caps; interjections
never trip it. Timers run on the conductor clock, not platform timestamps.
Run
Agents emit tool calls/results (and reasoning where the framework supports it)
into the room, so the handoff mechanics are visible in the Band UI. All ceilings
and the topic are env-tunable (
DEMO_*).Tests
Breaker and conductor message-projections are unit-tested offline (no platform,
no network):
uv run pytest tests/example_agents/test_docker_demo_breaker.py \ tests/example_agents/test_docker_demo_conductor.py -v --no-covFull walkthrough, tuning, and the never-in-VM proof:
examples/docker_demo/README.md.