Skip to content

feat(examples): Band × Docker three-agent design-meeting demo (INT-1118)#480

Open
AlexanderZ-Band wants to merge 22 commits into
mainfrom
feat/docker-demo-ready-decide-the-demo-scenario-for-the-INT-1118
Open

feat(examples): Band × Docker three-agent design-meeting demo (INT-1118)#480
AlexanderZ-Band wants to merge 22 commits into
mainfrom
feat/docker-demo-ready-decide-the-demo-scenario-for-the-INT-1118

Conversation

@AlexanderZ-Band

@AlexanderZ-Band AlexanderZ-Band commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

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:

Agent Framework Sandbox LLM (host-held)
Maya (PM/lead) Claude SDK band-demo-pm Anthropic
Sam (Lead Dev) Codex band-demo-dev OpenAI
Jordan (Architect) CrewAI band-demo-architect OpenAI

The 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 / an sk-…-shaped sentinel
under *_PROXY_KEY); the sbx proxy swaps them on outbound requests. The kit
launcher 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

cd examples/docker_demo
cp .demo.env.example .demo.env   # keys + optional endpoints
./launch.sh build                # once (Docker + sbx, no keys)
./launch.sh up                   # provision, sandbox, run; auto-teardown on exit

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-cov

Full walkthrough, tuning, and the never-in-VM proof: examples/docker_demo/README.md.

@linear-code

linear-code Bot commented Jul 22, 2026

Copy link
Copy Markdown

INT-1118

@AlexanderZ-Band
AlexanderZ-Band marked this pull request as ready for review July 22, 2026 16:56
AlexanderZ-Band and others added 15 commits July 22, 2026 20:00
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
AlexanderZ-Band force-pushed the feat/docker-demo-ready-decide-the-demo-scenario-for-the-INT-1118 branch from e220465 to 7fbe568 Compare July 22, 2026 17:03
AlexanderZ-Band and others added 4 commits July 22, 2026 20:15
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>
@AlexanderZ-Band
AlexanderZ-Band requested a review from a team July 23, 2026 05:42
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant