Skip to content

feat: --multi-instance storyboard mode#608

Merged
bokelley merged 1 commit into
mainfrom
bokelley/issue-2267
Apr 19, 2026
Merged

feat: --multi-instance storyboard mode#608
bokelley merged 1 commit into
mainfrom
bokelley/issue-2267

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

Round-robin storyboard steps across N seller URLs to catch horizontal-scaling persistence bugs where (brand, account)-scoped state lives in-process rather than in a shared store. Implements the client-side half of the cross-instance persistence requirement introduced in adcontextprotocol/adcp#2363.

Closes adcontextprotocol/adcp#2267.

What shipped

  • Library: runStoryboard(agentUrls: string | string[], ...). Multi-URL mode builds one client per URL, dispatches step N to clients[N % urls.length]. Each step result gains agent_url + agent_index. Result gains agent_urls[] + multi_instance_strategy. _client option rejected in multi-instance (comply()'s shared client can't round-robin). Single-URL mode is unchanged.
  • CLI: adcp storyboard run --url <a> --url <b> <storyboard_id>. Positional agent and --url are mutually exclusive. --url values are validated (http/https only, no userinfo, http requires --allow-http). --dry-run prints the per-step instance-assignment plan. JSON output includes agent_urls, per-step agent_url, agent_index.
  • --stateful filter on storyboard list. Derives from existing step.stateful metadata — returns the 40 storyboards that exercise write→read chains worth round-robining.
  • Failure attribution. When a step fails in multi-instance mode, the error carries a replica→step map, a NOT_FOUND signature check, a single-replica repro command, and a deep-link to Verifying cross-instance state. Wording mirrors the canonical example in the spec docs.
  • Docs: docs/guides/MULTI-INSTANCE-TESTING.md (contract, failure signature, three verification paths, live smoke command against the public test agent), docs/examples/multi-instance/ (docker-compose + Caddyfile + README).
  • Tests: 8 unit tests. Six cover round-robin dispatch + attribution + NOT_FOUND signature detection + backward-compat using local HTTP servers. Two cover the full MCP SDK path via GovernanceAgentStub (with a regression assert that auth tokens are never echoed into any result field).

Validated end-to-end

Ran against the live test-agent.adcontextprotocol.org with ?replica=a / ?replica=b query strings forcing two distinct MCP sessions:

  • Six of seven executed stateful steps in property_lists passed cross-replica (PR adcontextprotocol/adcp#2263 doing its job).
  • One failure was correctly attributed as intrinsic, not cross-replica (same failure reproduced in single-instance mode).
  • Round-robin dispatch is deterministic; the Reproduce single-replica command in the attribution block gives the exact single-instance repro.

Deliberate deferrals (tracked separately)

  • Dependency-aware distribution strategy — filed as adcp-client#607. Round-robin has ~50% cross-replica coverage of stateful reads when write→read pairs are separated by an even number of stateful steps. Needs dependency analysis or multi-pass — not a quick fix.
  • Default-on in comply() — requires the full capability-driven path to share one client; multi-URL dispatch is incompatible with that optimization. Separate design pass.

Expert review applied

Code review + security review run before filing. Must-fix and should-fix findings addressed:

  • --url values are now validated for scheme, userinfo, and http vs --allow-http parity (prevented --url file:///etc/passwd and userinfo-smuggled credentials from reaching the MCP transport).
  • Agent-controlled error text is sanitized (C0/C1 control chars stripped, capped at 512 chars) before embedding in the human-readable attribution block so a hostile agent can't forge terminal escape sequences in CI output.
  • NOT_FOUND signature detection now reads specific response fields (error, code, message, status, status_code) rather than regex-matching a full JSON.stringify(response) — closes the gameability concern and the JSON.stringify cycles/size footgun.
  • docker-compose fails fast if .env is missing POSTGRES_PASSWORD (removed the weak default); Caddyfile carries an explicit DEV-ONLY warning.
  • Added an MCP SDK-path test (not just rawMcpProbe) via GovernanceAgentStub.
  • Added a regression test that asserts auth tokens never appear in serialized results.

Test plan

  • npm run build — clean
  • npm run lint — 0 errors, 457 warnings (all pre-existing)
  • npm test on all storyboard + CLI tests — 1638/1638 passing locally
  • Live multi-instance run against test-agent.adcontextprotocol.org via ?replica=a|b — 6 of 7 stateful steps pass cross-replica, 1 intrinsic failure correctly attributed
  • --url validation smoke tests (javascript:, file://, userinfo, http-without-flag, malformed URL) all rejected with correct error messages
  • Single-URL path unchanged — result shape preserved, no agent_url/agent_index on steps in single-URL mode
  • CI green on push

🤖 Generated with Claude Code

@gitguardian

gitguardian Bot commented Apr 19, 2026

Copy link
Copy Markdown

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Round-robin storyboard steps across N seller URLs to catch horizontal-scaling
persistence bugs where (brand, account)-scoped state lives in-process rather
than in a shared store. Implements the client-side half of the cross-instance
persistence requirement introduced in adcontextprotocol/adcp#2363.

- CLI: --url repeatable, positional agent mutually exclusive
- Library: runStoryboard(agentUrls: string | string[], ...)
- Failure attribution: replica map + NOT_FOUND signature + docs deep-link
- --stateful filter on storyboard list
- docs/examples/multi-instance/ reference harness
- URL validation: reject javascript:/file://, reject userinfo, require --allow-http for http

Closes adcontextprotocol/adcp#2267.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley force-pushed the bokelley/issue-2267 branch from 3dc2d9c to 1ca73d4 Compare April 19, 2026 10:37
@bokelley bokelley merged commit f29780a into main Apr 19, 2026
12 checks passed
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.

Storyboard runner: add --multi-instance mode to catch cross-machine persistence bugs in CI

1 participant