A generic ACP (Agent Client Protocol) client provider plugin for Animus. It drives any ACP-speaking agent CLI (Gemini, OpenCode, and future ACP harnesses) over stdio and gates every tool call through Animus's approval decision — so one adapter replaces N bespoke per-CLI provider plugins, and every harness gets human-in-the-loop approvals for free.
See the design docs in the kernel repo:
docs/design/acp-provider-client.md and
docs/design/provider-runtime-and-approval.md.
Per agent/run the provider:
- Resolves the harness command from the request
model(or an override — see Config), e.g.gemini-*→gemini --acp,opencode/gpt/o1/o3→opencode acp. - Spawns that binary with
cwd = request.cwd, env from the request (andCLAUDECODEremoved), wraps its stdio into anagent-client-protocolclient connection. - Drives
initialize→session/new(forwardingmcp_servers) →session/prompt, streamingsession/updatenotifications into Animus's normalizedSessionEvents. - Implements the agent→client callbacks:
session/request_permission(the approval gate), plus worktree-clampedfs/read_text_file/fs/write_text_file— which are themselves routed through the sameapprove-hookgate (a harness can touch the filesystem via these callbacks without ever sendingsession/request_permission, so they must be gated too; both fail closed on deny/error).
When a run carries extras.approvals == true, every
session/request_permission is routed through the committed Animus verb:
animus agent approve-hook --format generic \
--agent-id <id> --project-root <root> --timeout-secs <t>
# stdin: {"tool_name": "<tool>", "input": <raw input>}
# stdout: {"decision": "allow"|"deny", "source": ..., "reason": ...}
- Approvals signal: enabled when
extras.approvals == true(direct path) OR an approval identity is pinned atruntime_contract.mcp.agent_id. The v0.1.16animus-plugin-runtimerebuildsSessionRequest.extrasfrom a fixed allow-list and drops a top-levelapprovalskey, forwarding onlyruntime_contract— so under normal plugin operation the gate keys off the pinnedruntime_contract.mcp.agent_id. This fails safe (only turns gating ON); the agent profile'sapproval_policygoverns the verdict. agent_idis read fromruntime_contract.mcp.agent_id(fallbackextras.agent_id);project_rootfromrequest.project_root(fallbackcwd); theanimusbinary from$ANIMUS_BIN(fallback PATH).- Per-request harness override:
extras.acp_commandorruntime_contract.acp_command. - allow → select the first allow option (prefer
allow_once). - deny → select the first reject option (prefer
reject_once). - Any error (verb missing, nonzero exit, unparseable stdout, or no matching
option) → fail closed: select a reject option, or
Cancelledif none. The gate NEVER defaults to allow. - When approvals are disabled, the gate is a pass-through (first
allow_once).
| Var | Purpose |
|---|---|
ACP_AGENT_BIN |
Global harness binary override (wins over the model table). |
ACP_AGENT_ARGS |
Space-separated ACP-mode args used with ACP_AGENT_BIN. |
ACP_DEFAULT_MODEL |
Fallback model id when a request omits one. |
ANIMUS_BIN |
Path to the animus binary for the approval gate. |
Per-request override: extras.acp_command as a string ("gemini --acp") or an
object ({"bin": "...", "args": [...]}).
cargo build
cargo test # unit tests incl. the approval outcome mapping + fail-closed
cargo clippy --all-targetsv1 (Phase S): one Animus run = one ACP prompt turn. resume_agent and HTTP/
remote transport are not yet implemented. Validated against gemini --acp
(handshake/session lifecycle) and opencode acp (full turn), with the
request_permission → approve-hook gate proven end-to-end over real stdio.