docs · guides

Run an ACP agent#

The built-in acp kind runs any agent that speaks the Agent Client Protocol: you register it, tell Hekma the command that starts it, and Hekma handles the protocol. In Hekma 0.11.0 you can register, configure and test-start an ACP agent from the CLI, but to keep one running and send it prompts you need a program that embeds the engine. This guide covers both, with recipes for OpenCode and Hermes Agent.

What ACP is#

The Agent Client Protocol (ACP) is a standard way for an editor or another program to drive a coding or personal agent. The agent runs as a child process and exchanges JSON-RPC messages with its client over stdin and stdout. Many agents ship an ACP mode: OpenCode (opencode acp), Hermes Agent (hermes-acp) and Gemini CLI among them.

Under the acp adapter kind, Hekma is the client. At start it launches the agent, sends initialize, and opens a session with session/new; the agent counts as running only once both succeed. Each prompt is one session/prompt request. Because the protocol uses the agent's stdin and stdout, Hekma must keep those pipes open for as long as you want to talk to the agent.

Register and configure#

Register the agent with --kind acp, then set the command that starts it in ACP mode:

hekma agent register coder --kind acp
hekma agent config set coder acp.command opencode
hekma agent config set coder acp.args acp
KeyValue
acp.commandThe agent's executable, on PATH or as an absolute path. Required.
acp.argsIts arguments, split on spaces. Optional. For an argument containing a space, see acp.args.

Starting without acp.command fails before anything runs, naming both keys (the hint in this message still says kt, an older name for hekma):

$ hekma agent start coder
✗ Could not resolve the adapter to start Agent Instance 'coder': no launch command is configured for the acp kind; set the config keys 'acp.command' (required, the ACP agent executable) and 'acp.args' (optional, its arguments) — e.g. `kt agent config set coder acp.command /path/to/agent`.

Every other config feature works as for any agent: budgets and rates, agent.* keys for environment variables, secrets for API keys, and a memory backing, which reaches the agent as HERMES_HOME.

Check that it starts#

A plain start is the quickest way to check the command and the handshake. It returns once the session is open, and the agent stops when the command exits:

$ hekma agent start coder
✓ Started Agent Instance coder
running
• note: the started process is supervised only for this engine session and stops when this command exits; start it with --detach to keep it running across commands.

The agent's protocol replies are in its log. A successful start shows the agent's answer to initialize and a session id:

$ hekma agent logs coder
2026-10-22T19:08:45Z [agent-out] {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true, ...},"agentInfo":{"name":"OpenCode","version":"2.0.32"}}}
2026-10-22T19:08:45Z [agent-out] {"jsonrpc":"2.0","id":2,"result":{"sessionId":"ses_e9afbf85cffeJSoEEazN6iTVbv", ...}}
2026-10-22T19:08:45Z [engine] engine: starting -> running

The next command then shows the agent as failed with "orphan not found on engine restart"; that is expected after a plain start. An agent that answers initialize with a protocol version other than 1, or with an error (for example because it is not logged in), fails the start with exit code 1 and the reason.

The 0.11.0 limit: you need a host process#

From the CLI alone, an ACP agent cannot be kept running and prompted in 0.11.0. Two facts combine:

  • A plain hekma agent start keeps the agent only for its own lifetime, and the command returns as soon as the session is open. By the time you run hekma agent send, the agent is gone.
  • hekma agent start --detach starts the agent with stdin closed, because nothing would be left to write to it. An ACP agent cannot work without stdin: OpenCode exits at once and the start fails with "exited immediately during startup with code 0".

So hekma agent send has no ACP agent to reach. To prompt an ACP agent, one process must hold the engine from start to stop:

  • Embed the engine. A Rust program using hekma-engine calls start, then send_input for each prompt, and stop, all on one open engine. Embed the engine shows how.
  • Use a long-running host. hekma serve (Unreleased: after 0.11.0) keeps the engine open as a service; see Run Hekma as a service. Its Control API has no route for sending prompts yet.
Diagram source (Mermaid)
sequenceDiagram
    participant H as Host program (embeds the engine)
    participant A as ACP agent
    H->>A: start = launch, initialize, session/new
    Note over H,A: The agent is running and the host keeps its stdin open
    H->>A: send_input = session/prompt
    A-->>H: session/update messages, written to the agent log
    A-->>H: prompt result with stopReason
    H->>A: stop = session/cancel if a turn is running, then SIGTERM

Prompts and turns#

From a host process, each send (send_input in the engine API) becomes one session/prompt with your text as a single text block. The call returns as soon as the prompt is sent. The agent's streamed replies land in the agent's log as they arrive, and when the turn ends Hekma adds an engine line such as acp: turn complete (stopReason: end_turn).

ACP allows one turn at a time per session. A second prompt while a turn is still running is refused with an invalid-state error (exit code 4 from the CLI); the first turn carries on. Wait for its stop reason in the log, or stop the agent. Stopping an agent mid-turn sends session/cancel before the usual stop.

Permissions are denied#

Hekma advertises no client abilities to ACP agents: no file access, no terminal, no prompts to a user. When an agent asks permission for something, such as running a tool, Hekma picks the agent's "reject" option, or cancels the request if there is none, and records a note. The agent decides what to do next; usually it carries on without the tool or ends the turn. Configure the agent itself (for example its own permission settings) if it needs to act without asking.

Usage: context usage and ledger usage#

ACP agents report two different kinds of numbers, and Hekma keeps them apart:

Context usageLedger usage
What it isHow full the session's context window is, sent by the agent as usage_update.Tokens actually spent per model call: input, output, cached.
Where you see itACP context usage row in show, acp_context_usage in --json.list, show, usage, and the usage ledger.
Counts toward budgetsNever.Yes.

Ledger usage comes from one of two usage sources:

  • Usage lines on stderr. The agent, or a wrapper script around it, prints KTESIO_USAGE {"sequence": 0, "input_tokens": 1200, "output_tokens": 300, "cached_tokens": 400} on stderr for each model call. (Under acp, stdout carries the protocol, so the line goes to stderr; manifest agents print it on stdout.) The format is in Meter token usage.
  • The metering proxy. Set metering.upstream_base_url to the agent's real model endpoint. At start, Hekma runs a proxy on 127.0.0.1 and passes its address to the agent as OPENAI_BASE_URL; this works if the agent sends its model requests to that variable's address. An agent metered this way cannot be started with --detach. See Meter token usage.

With neither, the agent's token cells read — and Hekma says which sources it tried:

• note: coder: no billing-grade usage in the ledger — tiers attempted: observed: not-configured; sentinel: no-lines-seen; no context usage reported (ACP usage_update is context-grain, never billed)

Out of the box, neither OpenCode nor Hermes Agent prints usage lines, and OpenCode's own per-turn token counts are not one of these sources. Budgets on an ACP agent therefore do nothing until you set up one of the two sources.

Sessions resume at the next start#

Hekma saves the session id when a session opens. At the next start, if the agent advertised loadSession in its initialize reply, Hekma asks it to reopen that session with session/load, so the conversation continues:

$ hekma agent start coder
[hekma] coder: resumed the previous ACP session via session/load
✓ Started Agent Instance coder
running

If the agent does not support resuming, or refuses, Hekma opens a new session and prints a note. A failed resume never fails the start.

Recipes#

OpenCode#

Tested with OpenCode v2.0.28 for full turns, and v2.0.32 for start and session.

hekma agent register coder --kind acp
hekma agent config set coder acp.command opencode
hekma agent config set coder acp.args acp
# Keep this agent's OpenCode data and config inside its Agent Home.
agent_home="$HOME/Library/Application Support/ktesio/agents/coder"
hekma agent config set coder agent.XDG_DATA_HOME "$agent_home/xdg-data"
hekma agent config set coder agent.XDG_CONFIG_HOME "$agent_home/xdg-config"

Replace the agent_home path with your state directory on Linux or Windows; hekma agent register prints the exact Agent Home path. Without the two XDG keys, every OpenCode agent shares your normal OpenCode data and config. The default free models need no login; for a paid model, run opencode auth login first (with the same XDG variables if you set them).

Hermes Agent#

Tested with Hermes Agent v0.21.7+500.g8f81791.

hekma agent register helper --kind acp
hekma agent config set helper acp.command hermes-acp
hekma agent memory attach helper --kind filesystem

The memory backing gives this agent its own Hermes home: Hekma passes the agent's memory/ folder as HERMES_HOME. Without it, the agent uses Hermes's default home, shared with every other Hermes agent on the machine. This replaces the deprecated --kind hermes; see Supported agents.

Other ACP agents#

Point acp.command and acp.args at the agent's ACP mode, for example gemini with --experimental-acp (or --acp on newer builds). Check the start as above, and see Supported agents for what has been tested.