fix(examples): make coding_agents Docker stack spin up cleanly [INT-500]#447
fix(examples): make coding_agents Docker stack spin up cleanly [INT-500]#447nir-singher-band wants to merge 2 commits into
Conversation
The coding_agents two-agent Compose example (Claude SDK planner + Codex reviewer) crashed on `docker compose up`. Two root causes, both fixed here, plus guardrails so neither recurs. 1. Phantom config directory. Compose bind-mounts ./agent_config.yaml, and Docker auto-creates the source as an empty *directory* when the file is missing — so both runners crashed loading config. create_agents.py now writes the combined, keyed agent_config.yaml directly (single source of truth), replacing the old copy-the-example-then-paste-creds flow that left the phantom dir behind. It refuses to overwrite an existing config without FORCE=1 (re-running registers new agents and would orphan the old ones). 2. Platform mismatch / unreachable host. Agents registered against one Band platform can't be found by containers pointed at another; a misspelled or unreachable BAND_REST_URL crashes both with "httpx.ConnectError: [Errno -2] Name or service not known". create_agents.py now logs the platform it registered against and warns to match .env; .env.example and the README troubleshooting section document the symptom. Also in this change: - Mount ./prompts into the reviewer service — it read /app/prompts/reviewer.md but nothing was mounted there, so it booted without its role prompt. - Treat the repo block as optional throughout (example, README, script): with no repo block the agents boot chat-only, needing no GitHub/SSH. - test_communication.py reads the combined agent_config.yaml; drop the stale "3 agents" docstring. - gitignore coding_agents per-run artifacts (.agent_ids.txt, legacy planner/reviewer.yaml) so credentials/IDs can't be committed.
| if not api_key: | ||
| raise ValueError("BAND_API_KEY environment variable is required") | ||
|
|
||
| if CONFIG_PATH.is_file() and os.environ.get("FORCE", "").strip().lower() not in ( |
There was a problem hiding this comment.
is_file() misses the stale agent_config.yaml directory this PR is meant to recover from. The script will register both agents, then fail at write_text() with IsADirectoryError, without writing cleanup IDs. Check exists() or handle directories before registering agents.
| ) | ||
| logger.info("Registered against: %s", base_url) | ||
| logger.info( | ||
| "IMPORTANT: set BAND_REST_URL in .env to the SAME platform (%s), or the " |
There was a problem hiding this comment.
The setup guide has users configure BAND_REST_URL in .env, but this script does not load .env. On a non-default platform it registers agents on app.band.ai, while Compose connects to the URL in .env. Please load the same configuration source or make the command pass BAND_REST_URL explicitly.
| config[agent_def["key"]] = section | ||
| created.append({"name": agent.name, "id": agent.id, "role": agent_def["role"]}) | ||
|
|
||
| body = yaml.dump(config, default_flow_style=False, sort_keys=False) |
There was a problem hiding this comment.
Could we move config and cleanup-file writing into small helpers? main() currently registers agents, builds config, writes files, and reports the result. Separating persistence would make the flow easier to follow and test.
| CLEANUP_PATH.write_text("".join(f"{a['id']}\n" for a in created), encoding="utf-8") | ||
| logger.info("Agent IDs saved to %s for cleanup", CLEANUP_PATH.name) | ||
|
|
||
| logger.info("\n=== Summary ===") |
There was a problem hiding this comment.
Could we move this summary output into a log_summary() helper? It would keep main() focused on the registration flow and put the user-facing setup instructions in one place.
| } | ||
| ) | ||
| config[agent_def["key"]] = section | ||
| created.append({"name": agent.name, "id": agent.id, "role": agent_def["role"]}) |
There was a problem hiding this comment.
If reviewer registration fails, the planner ID is never written to the cleanup file. FORCE=1 also overwrites the previous cleanup IDs. Both paths can leave agents orphaned. Could we persist each successful registration immediately or clean up created agents on failure?
|
|
||
| def load_agent_config(filename: str) -> dict: | ||
| """Load agent config from YAML file.""" | ||
| def load_config_file(filename: str) -> dict: |
There was a problem hiding this comment.
Could we use band.config.load_agent_config() here? It already supports keyed config and validates missing files, empty values, and required credentials, so we do not need another YAML loader.
| planner = config["planner"] | ||
| reviewer = config["reviewer"] | ||
|
|
||
| base_url = os.environ.get("BAND_REST_URL", "https://app.band.ai") |
There was a problem hiding this comment.
This script also ignores the .env file used by Compose, so the documented command targets app.band.ai on custom deployments. Could we load the same settings here as create_agents.py?
|
|
||
| ```bash | ||
| cp agent_config.yaml.example agent_config.yaml | ||
| BAND_API_KEY=band_u_... python create_agents.py |
There was a problem hiding this comment.
Since this script is now the required setup path, can we add PEP 723 metadata and document uv run create_agents.py? Plain python currently depends on the SDK already being installed.
Summary
Validated the
examples/coding_agentstwo-agent Docker Compose stack (Claude SDK planner + Codex reviewer) end-to-end againstapp.band.aiand fixed everything that stopped it from spinning up. Both containers now boot, connect, load their role prompts, and coordinate in a room.Root causes fixed
1. Phantom
agent_config.yamldirectory → config-load crash.Compose bind-mounts
./agent_config.yaml; when the file is missing Docker auto-creates the source as an empty directory, so both runners crashed reading config.create_agents.pynow writes the combined, keyedagent_config.yamldirectly (single source of truth), replacing the old "copy the example, paste creds" step that produced the phantom dir. It refuses to overwrite an existing config withoutFORCE=1(re-running registers new agents and would orphan the old ones).2. Platform mismatch / unreachable host →
httpx.ConnectError: [Errno -2] Name or service not knowncrash loop.Agents registered on one Band platform can't be found by containers pointed at another, and a misspelled/unreachable
BAND_REST_URLfails DNS before auth.create_agents.pynow logs the platform it registered against and warns to match.env;.env.exampleand the README troubleshooting section document the exact symptom → fix.Also in this change
./promptsinto the reviewer service; it read/app/prompts/reviewer.mdbut nothing was mounted there, so it was booting without its Band collaboration prompt.repoblock the agents boot chat-only, needing zero GitHub/SSH. AddREPO_URL(±REPO_BRANCH/REPO_INDEX) to wire a shared repo.test_communication.pyreads the combinedagent_config.yaml; dropped the stale "3 agents" docstring..gitignores thecoding_agentsper-run artifacts (.agent_ids.txt, legacyplanner.yaml/reviewer.yaml) so credentials/IDs can't be committed.Test plan
Verified live on
app.band.ai:python create_agents.pyregisters planner + reviewer and writesagent_config.yaml(no phantom dir).docker compose up -d→ both containersUpand stable (no restart loop)./app/prompts/reviewer.md), and the planner completing a room turn..env,agent_config.yaml,.agent_ids.txtare gitignored and excluded.