Useful ToolsACP Agent

acp_agent

Delegate one bounded task to Claude Code, Codex, Gemini CLI, or another reviewed ACP agent through one typed client.

The implementation from ConnectOnion PR #901 is merged and included in the reviewed 1.7.0a2 candidate. It is not part of the current stable package or publicly available until that preview is published.

One client, named engines

ConnectOnion is the ACP client and the selected coding engine is the child ACP agent. The model may choose a reviewed engine name, prompt, working directory, and prior session ID. Process commands, approval policy, and the workspace root remain operator-owned.

main.py
from connectonion import Agent from connectonion.useful_tools import acp_agent agent = Agent("lead", tools=[acp_agent]) agent.input("Ask Claude Code over ACP to inspect the failing tests")

API

main.py
acp_agent( prompt: str, engine: str = "", # claude-code | codex | gemini session_id: str = "", # exact Claude/Codex session to resume cwd: str = "", # operator workspace or a descendant timeout: int = 600, ) -> str # bounded JSON envelope

Custom ACP commands use an operator-created ACPAgent instance. They are intentionally absent from the model-facing function schema.

Engine permission contract

EngineExact routeSupported policyCross-process resume
claude-codeclaude-agent-acp@0.66.0manual, auto, denyyes
codexcodex-acp@1.1.14explicit operator-selected auto onlyyes
gemini@google/gemini-cli@0.55.1 --acpmanual, auto, deny when advertisedno

Real testing found that the pinned Codex adapter's read-only mode can run shell and outbound network work without an ACP permission request. ConnectOnion therefore rejects named Codex ACP under manual or deny before spawning it. Use the native codex tool for approval-aware Codex work.

Google stopped serving Gemini CLI requests for free, Pro, and Ultra individual OAuth accounts on June 18, 2026. The named Gemini route requires a Gemini API key, Vertex AI, or enterprise Code Assist. A legacy ~/.gemini/oauth_creds.json file is not a readiness signal.

Exact resume

main.py
import json first = json.loads(acp_agent("Inspect the tests", engine="claude-code")) second = json.loads(acp_agent( "Now propose the smallest fix", engine="claude-code", session_id=first["session_id"], )) assert second["resumed"] is True

Continuation follows the capabilities returned by initialize: the client prefers sessionCapabilities.resume, otherwise uses loadSession, and sends exactly one lifecycle request. Explicitly null agentCapabilities means no optional capability was advertised, so continuation fails before a request. Failure never falls back to the other method or a fresh child session.

Initialization also requires protocolVersion to be the JSON integer 1. Strings, booleans, and unsupported major versions stop before any session lifecycle request. The legacy loadSession flag likewise accepts only a JSON boolean or null; strings and numbers are not capabilities. Schema coercion is not protocol agreement.

Real conformance testing found that Gemini CLI 0.55.1 does not persist its advertised ACP session across these one-process-per-turn invocations. A named Gemini turn therefore returns an empty session_id; supplying one fails before launch instead of pretending to resume. Authentication failures also return an explicit error, and no child may silently start a browser login flow.

What reaches the parent and browser

  • The bounded final child message and stable tool lifecycle IDs/titles cross the edge.
  • If the child emits startup notices under an earlier ACP message ID, they stay separate instead of being prepended to the final result.
  • Raw child tool inputs and outputs do not become ordinary progress events.
  • Child thought chunks do not become persisted ConnectOnion thoughts.
  • A child plan does not replace the parent agent's canonical TodoList.
  • The React package owns browser protocol decoding; O Chat renders the normalized state.

Provider environment boundary

Child processes start with the ACP SDK's trimmed HOME, PATH, and shell environment instead of inheriting every ambient secret. Claude receives only an explicitly set CLAUDE_CONFIG_DIR or ANTHROPIC_API_KEY; Codex receives only its selected API key or CODEX_HOME; Gemini receives only explicitly configured Gemini API-key or Vertex authentication variables and cannot open a browser login. Unrelated environment credentials do not cross the child boundary. engine_status() reports this boundary, including supported authentication choices and supports_resume, without presenting a credential-file hint as proof of valid authentication. Gemini intentionally has no generic credential-file hint because retired individual OAuth credentials can remain on disk.

Workspace boundary, not an OS sandbox

The child working directory must resolve inside the operator-bound workspace, including through symlinks. The convenience tool resolves that root when called instead of retaining the directory from module import. These rules limit launch-directory selection; hostile child code still requires an operator-provided container or operating-system sandbox.

Star us on GitHub

If ConnectOnion saves you time, a ⭐ goes a long way — and earns you a coffee chat with our founder.