# acp_agent

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

> Status: 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.

```python
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

```python
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

| Engine | Exact route | Supported policy | Cross-process resume |
|---|---|---|---|
| `claude-code` | `claude-agent-acp@0.66.0` | manual, auto, deny | yes |
| `codex` | `codex-acp@1.1.14` | explicit operator-selected auto only | yes |
| `gemini` | `@google/gemini-cli@0.55.1 --acp` | manual, auto, deny when advertised | no |

[Google stopped serving Gemini CLI requests](https://github.com/google-gemini/gemini-cli/discussions/28017)
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.

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.

## Exact resume

```python
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.
- 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
`supports_resume` and supported authentication choices, without presenting a
credential-file hint as proof of valid authentication. Gemini intentionally has
no generic credential-file hint because individual OAuth credentials can remain
on disk after that provider path was retired.

## 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.
