Skip to content

Add a Go ACP cookbook mirroring the Rust patterns (#22) - #41

Merged
foundev merged 2 commits into
masterfrom
claude/github-issues-review-k2x4bc
Sep 29, 2026
Merged

foundev merged 2 commits into
masterfrom
claude/github-issues-review-k2x4bc

Conversation

@foundev

@foundev foundev commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #22.

Problem

examples/ covers the basics, but nothing ties the patterns in the Rust agent-client-protocol-cookbook to acp-go's packages (acp, v2, runner, clienthost, proxyrouter, mcp, v2/mcp).

What this adds

This PR adds github.com/BrokkAi/acp-go/cookbook, a documentation-only package. doc.go holds the guide. Each recipe is one whole-file Example in its own example_*_test.go, and go test ./... runs it against a deterministic agent. Each file has exactly one example plus its helpers, so pkg.go.dev renders the whole file.

Recipe (Rust module) Example Documents
one_shot_prompt Example_oneShotPrompt, Example_runner acp, runner, clienthost
v2_one_shot_prompt Example_v2OneShotPrompt, Example_v2Runner v2 (SessionTracker, SessionHandle, CancellablePermissions), v2/runner
building_an_agent Example_buildingAnAgent agent: streamed updates, tool calls, permission requests, cancellation
ordered_application_dispatch Example_orderedApplicationDispatch acp.Notifications ordering, v2.ResumeSessionFromStart
v2_session_coordination Example_v2SessionCoordination Shared resume, replay, and close with leases on one coordinator goroutine
proxy recipe (issue scope) Example_proxyComponent proxyrouter: a v1 proxy that adds an MCP server to session/new/session/load
MCP attachment Example_mcpServers, Example_v2McpServers Stable v1 facade, mcp (native acp transport), v2/mcp

The runner recipes launch a real subprocess: the test binary re-executes itself as a fixture agent, the pattern runner's tests already use. No credentials are needed. recipes_test.go pins the edge cases the examples' happy paths don't reach.

The package docs also record:

  • The Go ordering contract each recipe relies on. When a call returns, every notification that preceded its response on the wire has already been handled.
  • Where Go differs from Rust. Go has no on_receiving_result counterpart, so a later live update can be queued ahead of a response marker. The recipe states that the marker's queue position therefore cannot separate replayed updates from live ones.
  • Which Rust recipes don't port and why. global_mcp_server, per_session_mcp_server and filtering_tools need an MCP server SDK and the MCP-over-ACP methods (Agent-side MCP-over-ACP method dispatch (mcp/connect, mcp/message, mcp/disconnect) #39). running_proxies_with_conductor depends on the conductor, which is won't port (Port the ACP proxy-chain conductor CLI (stdlib-only) #24).

README, ROADMAP, docs/rust-ecosystem-parity.md and CHANGELOG now mark #22 delivered. No new dependencies.

Notes for review

  • Proxy envelope. The proxy recipe hand-models the _proxy/successor envelope ({method, params}) from Rust SDK 2.2.0's proxy_protocol.rs, because the pinned schema has no _proxy/* methods. Like the reference conductor, it neither sets nor forwards the envelope's own _meta; the params' _meta passes through untouched. The envelope lives in example code, not library API, which keeps Port the ACP proxy-chain conductor CLI (stdlib-only) #24's decision intact. testConductor is test scaffolding only.

  • Proxy ordering. Callbacks must not write to their own connection, so the proxy queues notifications on a goroutine. Before forwarding any response or request, in either direction, it flushes that direction's queue:

    • a v1 client sees every session/update of a turn before the turn's response;
    • the agent sees a client's session/cancel before the client's cancelled permission outcome.

    One limit is documented: acp.Connection runs inbound requests concurrently, so a notification that follows a request (e.g. session/cancel right after session/prompt) can overtake it on the way to the successor.

  • MCP recipe workaround. mcp.NewSession gates on unstable.InitializeResponse, but the stable facade doesn't decode the unstable MCP flags. The recipe therefore captures the raw initialize result and decodes it twice. That replaces InitializeWithInfo on the connection, so the recipe checks the negotiated version itself and closes the connection on a mismatch. A small SDK helper could remove this workaround; that would be a follow-up, not part of this PR.

Validation

  • go test -race ./..., go vet ./..., gofmt -l ., python3 scripts/licenses.py, python3 -m unittest discover -s scripts -p '*_test.py': all clean.
  • A review pass found 9 issues; 8 are fixed in the second commit and the _meta point is documented as above. The cancel-ordering test fails 20/20 with the proxy's flush removed and passes 50/50 with it. The relative-path runner bug was reproduced with a compiled test binary (fork/exec ./cookbook.test: no such file or directory) before the fix and passes after it.
  • The whole cookbook package passed 500 consecutive -race runs.

🤖 Generated with Claude Code

https://claude.ai/code/session_014qKao9sNYaSpHw7wsaxcwR

Port the reference Rust agent-client-protocol-cookbook recipes that map
onto acp-go as a documentation-only cookbook package. Each recipe is one
runnable example that go test executes against a deterministic
in-process or subprocess agent, so the snippets cannot drift:

- one-shot v1 and draft-v2 prompts, in-process and through both runners
- building a v1 agent with streamed updates, tool calls, and permissions
- ordered application dispatch on one FIFO
- draft-v2 session resume/replay/close coordination
- a proxyrouter proxy component that adds an MCP server to sessions
- attaching MCP servers via the stable facade, mcp, and v2/mcp

The package documentation links every recipe to the package it covers,
records the Go ordering contract each relies on and where it differs
from Rust, and explains the recipes that do not port. README, ROADMAP,
the parity record, and CHANGELOG mark #22 delivered. No new dependencies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qKao9sNYaSpHw7wsaxcwR
- proxy: flush client notifications before returning a client's answer
  to a successor request, so session/cancel reaches the agent before the
  cancelled permission outcome; reject non-object session setup params
  instead of panicking; report non-EOF connection errors from Serve;
  document that envelope _meta is neither set nor forwarded, as in the
  reference conductor
- mcp: close the connection on a version mismatch, like
  InitializeWithInfo, and say the helper replaces it
- agent: close out the pending tool call with a failed update when the
  prompt is cancelled; stop dereferencing optional schema fields
- ordered dispatch: stop the agent after a failed resume too, so the
  example cannot hang
- runner fixtures: launch os.Executable() because the runners start the
  agent in its workspace, where a relative os.Args[0] does not resolve

recipes_test.go pins these paths. The cancel-ordering test fails 20 of
20 runs with the proxy flush removed and passes with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qKao9sNYaSpHw7wsaxcwR
@foundev
foundev merged commit b351202 into master Sep 29, 2026
8 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.

Add a Go ACP cookbook mirroring the Rust patterns

2 participants