Agent Client Protocol (ACP)

Run an interactive Kaya program as an ACP agent for clients such as Buzz

ACP is a small protocol boundary around Kaya. It lets an agent client start a session, send prompts, receive streamed updates, and attach MCP tools without moving execution or persistence out of Kaya.

ACP client β†’ Rust ACP adapter (kaya-acp) β†’ kayad session β†’ interactive Kaya program
                                          β””β†’ session MCP tools

The ACP adapter is a separate Rust binary (kaya-acp) built from source. It is not included in the native standalone archive's kaya, kayad and kaya-tool-server cohort. The source-build instructions and linked example projects below require Kaya repository access.

From a repository checkout, configure the native model route, start the daemon and then run the adapter:

KAYA_CAPABILITY_APPROVAL_MODE=interactive kayad start
cargo run --manifest-path packages/kaya-core-rs/Cargo.toml -p kaya-acp -- /absolute/path/to/job.yaml

or with the built binary:

kaya-acp /absolute/path/to/job.yaml

Standard output is reserved for ACP JSON-RPC; startup messages and errors go to standard error. The daemon must already be reachable before starting the adapter. Programs using chat() without model need a configured session default; see Text model bindings.

Write an ACP job

An ACP job has two pieces. job.yaml chooses the Kaya program and its initial inputs:

program: ./main.kaya
inputs:
  role: "A practical research assistant"

The Kaya program supplies the behavior. It must stop at input() before the first turn and return to input() after every response:

[system] "You are {{role}}. Be direct and show your work."

while true {
  input()
  chat(maxToolSteps: 20)
}

This is the important distinction: a YAML file is a launch profile, not the agent itself. It points to a Kaya program and supplies inputs and storage settings. The program defines the agent loop. One adapter process can host several isolated client sessions using the same job profile.

A batch program that finishes, or one that never reaches its first input boundary, is rejected during ACP session creation.

Run a Kaya-native coding harness

The first-party native-coding-agent example is a coding harness implemented in Kaya itself. Its main.kaya owns the system prompt, interactive turn loop, model-facing tool list, and maxToolSteps budget. The engine owns tool-call correlation, suspension, durable one-shot approvals, and provider lifecycle. The Rust tool server owns the confined filesystem, Git, model, and sandboxed-command implementations.

That is different from a kaya:agent/run@1 profile. An agent profile starts a Claude, Codex, OpenCode, or Agy CLI as a child and treats that executable as the coding harness. The native profile starts no external coding-agent executable; the model calls Kaya capabilities directly. Build, test, and formatter commands are still ordinary CLI programs, but they run inside the host-selected, network-disabled toolchain container rather than becoming native Kaya implementations.

Copy the example manifests, then replace every placeholder workspace/database path, the model binding, and the immutable toolchain image digest. The filesystem and repository roots must name the same checkout. The configured image must already exist in Docker; the provider never pulls it or falls back to a host shell.

Start the authenticated provider service from the repository root:

export KAYA_TOOL_SERVER_TOKEN='replace-with-a-long-random-token'
export OPENAI_API_KEY='...'
export KAYA_CODING_MODEL='replace-with-a-model-id'

cargo run \
  --manifest-path packages/kaya-core-rs/Cargo.toml \
  -p kaya-tool-server -- serve \
  --manifest /absolute/path/to/tool-server.json

Then point the daemon host at the matching client manifest and start the ACP agent:

export KAYA_TOOL_SERVER_ENDPOINT=http://127.0.0.1:8789
export KAYA_TOOL_CLIENT_MANIFEST=/absolute/path/to/tool-client.json

kaya-acp packages/kaya-core-rs/kaya-acp/examples/native-coding-agent/job.yaml

The daemon must be started as an interactive approval host. Read-only, idempotent, closed-workspace inspection runs without interruption. Contextual patches and sandboxed commands produce an ACP session/request_permission request and consume the answer only for the exact invocation shown. Start or restart it explicitly with:

KAYA_CAPABILITY_APPROVAL_MODE=interactive kayad serve

Use fail-closed instead for a headless deployment. An unset mode preserves legacy daemon behavior and installs no coding policy. Kaya never infers that an approval UI exists from the transport alone. A denied, mismatched, stale, or replayed answer is rejected before provider dispatch. If the engine cannot certify whether a started mutation completed, it reports an indeterminate outcome and does not automatically retry it.

The job's tools block is the session's complete capability authority. Every model and coding tool has an explicit kaya.model.* or kaya.tool.* alias, semantic interface/operation, authenticated attachment, and provider selector. The empty default map in tool-client.example.json proves that the program does not inherit ambient tools. Roots, executables, container policy, credentials, and upstream model IDs remain host configuration and never enter Kaya source or model-visible schemas.

This split also makes provider substitution deliberate: a deployment can bind kaya.model.native-coding or any tool alias to another conforming provider without changing main.kaya. Conversely, changing the prompt, tool selection, turn structure, or tool-step budget requires no engine or provider change.

If setup fails, inspect these boundaries in order:

  • /v1/catalog must contain each exact interface/operation admitted by the server token.
  • job.yaml must grant the same operation and select the configured attachment and provider IDs.
  • KAYA_TOOL_CLIENT_MANIFEST must be present in the daemon process environment, not only in a later client process.
  • An existing daemon must be restarted to receive changed tool-client or approval-host environment.

MCP tools from the client

An ACP client can attach stdio MCP servers to session/new. Kaya starts each server lazily for that daemon session, discovers its tools, and keeps it alive across turns. Tools are namespaced as:

<server-name>__<tool-name>

When generate() or chat() omits tools, the attached MCP tools enter its model-facing catalog. An explicit literal tools list selects the requested names; include the exact <server-name>__<tool-name> for each attached tool that call needs. Kaya function references can share the same list.

MCP launch environment values often contain credentials. Kaya keeps the descriptors in memory, does not copy them into job inputs, and closes the MCP processes with the session.

Native text generation dispatches these calls through Kaya's session MCP gateway. The selected model alias still needs an admitted text-model route (its exact grant or an explicitly granted default) and a configured provider binding that supports the requested tool-calling contract. Attaching an MCP server creates no model binding. Host-configured named tools separately require their exact session grants, such as kaya.tool.read_range.

Coding-agent presets use the separate kaya:agent/run@1 route and their own provider configuration. A provider name or installed coding-agent executable does not establish compatibility with client-attached MCP. Inspect the selected binding and structured diagnostic instead of inferring support from a model family name. See Supported Models.

Workspace metadata

The client's primary working directory becomes the Kaya session's project folder and the MCP subprocess working directory. Additional workspace roots are rejected by default. If a program needs that non-secret metadata, opt in:

kaya-acp ./job.yaml --acp-input-key acp

The job then receives acp.cwd and acp.additionalDirectories. MCP commands and environment values remain separate from inputs.

Security

A client-provided MCP command can run local code with the daemon user's permissions. The local CLI daemon accepts it by default so desktop agent clients work without extra setup. For a daemon reachable by untrusted clients, disable the capability:

KAYA_DISABLE_CLIENT_MCP=true kayad serve

CLI-agent presets additionally enforce the active profile's agentic LLM policy.

Cancellation and lifecycle

ACP cancellation currently stops the whole Kaya session. Kayad does not yet have a safe β€œabort only this turn” operation, so a cancelled ACP session cannot accept another prompt. This guarantees that its model and MCP tools are not still running after cancellation is reported.

The adapter speaks stable ACP v1. It supports initialization, new sessions, prompts, streamed text and tool events, capability approval permission prompts, cancellation, and multiple concurrent sessions.

Next: connect a Kaya agent to Buzz.