{"id":"162449f4-ba53-4503-96b9-2b2d696183f2","slug":"go-adk-acpagent","kind":"mcp","name":"go-adk-acpagent","summary":"ACP-backed Google ADK agent adapter for Go","owner":"normahq","repoUrl":"https://github.com/normahq/go-adk-acpagent","license":"MIT","licenseTier":"permissive","trust":"community","stars":3,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-07-05T05:22:00.000Z","addedAt":"2026-10-21T09:15:07.114Z","logoUrl":"https://avatars.githubusercontent.com/u/267209684?v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["utilities"],"tags":["acp","adk","agent","go","google-adk","llm","agent-client-protocol","coding-agent"],"runtime":"go","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-26T01:46:18.356Z","sample":false,"url":"https://conduits.dev/tools/go-adk-acpagent","language":"Go","transports":["stdio"],"install":{},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# go-adk-acpagent\n\n[![test](https://github.com/normahq/go-adk-acpagent/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/normahq/go-adk-acpagent/actions/workflows/test.yml)\n[![lint](https://github.com/normahq/go-adk-acpagent/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/normahq/go-adk-acpagent/actions/workflows/lint.yml)\n[![security](https://github.com/normahq/go-adk-acpagent/actions/workflows/security.yml/badge.svg?branch=main)](https://github.com/normahq/go-adk-acpagent/actions/workflows/security.yml)\n[![release](https://github.com/normahq/go-adk-acpagent/actions/workflows/release.yml/badge.svg)](https://github.com/normahq/go-adk-acpagent/actions/workflows/release.yml)\n[![Go Reference](https://pkg.go.dev/badge/github.com/normahq/go-adk-acpagent/v2.svg)](https://pkg.go.dev/github.com/normahq/go-adk-acpagent/v2)\n[![License](https://img.shields.io/github/license/normahq/go-adk-acpagent)](LICENSE)\n[![Version](https://img.shields.io/github/v/tag/normahq/go-adk-acpagent?label=version)](https://github.com/normahq/go-adk-acpagent/tags)\n\n**Run ACP-compatible coding agents as Google ADK `agent.Agent`\nimplementations.**\n\n`go-adk-acpagent/v2` starts a coding-agent subprocess, talks to it with Agent\nClient Protocol (ACP) over stdio, binds ACP sessions to ADK sessions, and emits\nADK events for streamed text, thoughts, tool calls, usage, plan updates, and\nprovider errors.\n\nUse it when your application is built on Google ADK v2 but the coding agent you\nwant to run is exposed as an ACP command.\n\n## What You Get\n\n| Capability | Behavior |\n| --- | --- |\n| ADK agent | Implements the ADK `agent.Agent` interface. |\n| ACP lifecycle | Starts one ACP subprocess per agent instance and closes it on shutdown. |\n| Session binding | Creates, stores, reuses, and resumes ACP sessions through ADK session state. |\n| Event mapping | Converts ACP updates into ADK events and state deltas. |\n| Structured prompts | Preserves ordered ADK content and negotiates optional image/audio/embedded-resource blocks from ACP `initialize` capabilities; file data falls back to baseline resource links. |\n| Permissions | Maps protocol callbacks into ADK-facing `PermissionRequest` values handled by `PermissionHandler`. |\n| Session config | Applies ACP session-bound values such as model, mode, or thought level. |\n| MCP forwarding | Sends configured MCP servers to ACP session creation and resume calls. |\n| Diagnostics | Uses `*slog.Logger` for adapter logs and a separate writer for provider stderr. |\n\n## Try It With OpenCode\n\n```sh\ngo get github.com/normahq/go-adk-acpagent/v2\n```\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"log/slog\"\n\t\"os\"\n\n\tacpagent \"github.com/normahq/go-adk-acpagent/v2\"\n\t\"google.golang.org/adk/v2/agent\"\n\t\"google.golang.org/adk/v2/runner\"\n\t\"google.golang.org/adk/v2/session\"\n\t\"google.golang.org/genai\"\n)\n\nfunc main() {\n\tif err := run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc run() error {\n\tctx := context.Background()\n\tlogger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{\n\t\tLevel: slog.LevelInfo,\n\t}))\n\n\t// 1. Create the ACP-backed ADK agent.\n\tagentRuntime, err := acpagent.NewWithContext(ctx, acpagent.Config{\n\t\tCommand:    []string{\"opencode\", \"acp\"},\n\t\tWorkingDir: \"/workspace\",\n\t\tLogger:     logger,\n\t\tStderr:     io.Discard,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"create ACP agent: %w\", err)\n\t}\n\tdefer func() {\n\t\tif err := agentRuntime.Close(); err != nil {\n\t\t\tlog.Printf(\"close ACP agent: %v\", err)\n\t\t}\n\t}()\n\n\t// 2. Wire the agent into an ADK runner.\n\tsessionService := session.InMemoryService()\n\tr, err := runner.New(runner.Config{\n\t\tAppName:        \"opencode-app\",\n\t\tAgent:          agentRuntime,\n\t\tSessionService: sessionService,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"create runner: %w\", err)\n\t}\n\n\t// 3. Create an ADK session.\n\tsess, err := sessionService.Create(ctx, &session.CreateRequest{\n\t\tAppName: \"opencode-app\",\n\t\tUserID:  \"user-1\",\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"create session: %w\", err)\n\t}\n\n\t// 4. Run a turn and stream events.\n\tprompt := genai.NewContentFromText(\"Explain this codebase\", genai.RoleUser)\n\tfor ev, err := range r.Run(ctx, \"user-1\", sess.Session.ID(), prompt, agent.RunConfig{}) {\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"run turn: %w\", err)\n\t\t}\n\t\tif ev.Content != nil {\n\t\t\tfor _, part := range ev.Content.Parts {\n\t\t\t\tif part.Text != \"\" && !part.Thought {\n\t\t\t\t\tfmt.Print(part.Text)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tfmt.Println()\n\treturn nil\n}\n```\n\n## Provider Commands\n\n| Provider | Command |\n| --- | --- |\n| OpenCode | `[]string{\"opencode\", \"acp\"}` |\n| Codex | `[]string{\"npx\", \"-y\", \"@normahq/codex-acp-bridge@latest\"}` |\n| Claude Code | `[]string{\"npx\", \"-y\", \"@zed-industries/claude-code-acp@latest\"}` |\n| Pi | `[]string{\"npx\", \"-y\", \"pi-acp\"}` |\n| Generic ACP | Any executable that speaks ACP on stdin/stdout. |\n\nRunnable examples are available for [OpenCode](examples/opencode),\n[Codex](examples/codex), [Claude Code](examples/claude), and\n[Pi](examples/pi).\n\n## Configuration Cheatsheet\n\n| Field | Purpose |\n| --- | --- |\n| `Command` | ACP subprocess argv. |\n| `LazyInitialization` | Defer process launch and initialize until invocation. |\n| `InitializeTimeout` | Bound gate wait, process launch, and initialize independently. Zero inherits `SetupTimeout`; negative is invalid. |\n| `SetupTimeout` | Bound session attachment; default initialize budget when `InitializeTimeout` is zero. |\n| `WorkingDir` | Process directory and default ACP session cwd. |\n| `Logger` | Adapter diagnostics through `*slog.Logger`. |\n| `Stderr` | ACP subprocess stderr forwarding. |\n| `PermissionHandler` | Application decision point for generic agent permission requests. |\n| `SessionConfig` | ACP session config values applied through `session/set_config_option`. |\n| `MCPServers` | MCP servers forwarded to ACP sessions. |\n| `Instruction` / `GlobalInstruction` | ADK-style instructions prepended to prompts. |\n| `ReasoningEffort` | Provider reasoning effort metadata when supported. |\n| `OutputKey` | ADK state key for the final visible model output. |\n\nACP provider error metadata helpers are available from:\n\n```go\nimport \"github.com/normahq/go-adk-acpagent/v2/acperror\"\n```\n\n## Documentation By Task\n\n| Task | Start here |\n| --- | --- |\n| Understand lifecycle | [Concepts](docs/concepts.md) |\n| Understand ACP-to-ADK event mapping | [Event mapping](docs/event-mapping.md) |\n| Choose a provider command | [Provider recipes](docs/provider-recipes.md) |\n| Manage cwd, session metadata, config values, plans, and output state | [Session state](docs/session-state.md) |\n| Debug startup, JSON-RPC streams, permissions, and provider errors | [Troubleshooting](docs/troubleshooting.md) |\n\n## Startup And Attachment Budgets\n\nSet independent budgets when cold initialize needs more time than session\nattachment:\n\n```go\nConfig{\n    Command:           []string{\"opencode\", \"acp\"},\n    LazyInitialization: true,\n    InitializeTimeout: 60 * time.Second,\n    SetupTimeout:      10 * time.Second,\n}\n```\n\n`InitializeTimeout` covers waiting for initialization already in progress,\nprocess launch, and the ACP initialize handshake. It applies to eager and lazy\ninitialization. Zero inherits `SetupTimeout` for compatibility; if both are\nzero, only caller and lifecycle cancellation/deadlines limit initialization.\nNegative values are rejected before launch. Earlier caller deadlines and\napplication cancellation still win.\n\n`SetupTimeout` continues to bound session/new and session/resume attachment\nand related setup RPCs. Neither duration limits prompts or the lifetime of a\nhealthy process. A successful client survives the initialization deadline and\nrequest cancellation, and later invocations reuse it. Lazy mode still performs\nno process launch until invocation.\n\nCall `Close` during shutdown. Cleanup closes stdin, allows 200ms for graceful\nexit, kills the owned immediate child if needed, and waits up to 5s more. An\nexceptional stalled process wait returns an explicit cleanup-timeout error;\nthe original reaper remains responsible until the wait finishes. Initialization\nerrors retain their cancellation/deadline cause, including when cleanup adds\nan error. Command wrapper descendants remain outside the immediate-child\nownership contract.\n\n## Production Notes\n\n- Call `Close` during shutdown so the ACP subprocess exits cleanly.\n- Pass the application lifecycle context to `NewWithContext`. The legacy\n  `Config.Context` field remains available for v2 compatibility.\n- Debug and trace logs omit prompt and update payload content. They retain\n  structural diagnostics such as content-block types, counts, and byte lengths.\n  Provider stderr remains separately controlled by `Stderr`.\n- Keep ACP protocol messages on stdout and provider logs on stderr.\n- Use `SessionConfig` for session-bound model, mode, thought-level, or\n  provider-specific choices. Use `SelectSessionConfigValue` for select options\n  and `BooleanSessionConfigValue` for boolean options.\n- Treat `SessionStateKey` as adapter-owned except for documented `_meta`,\n  `config_values`, and cwd overrides.\n\n## Tests\n\n```sh\ngo test -race ./...\ngo test ./... -coverprofile=coverage.out\ngo tool cover -func=coverage.out\n```\n\nCI requires at least 95% total statement coverage.\n\nProvider integration tests are opt-in because they require external ACP\ncommands and credentials:\n\n```sh\ntask test:integration:codex\ntask test:integration:opencode\n```\n"}