Turn harnesses
Choose where your agent's turn loop runs: on Keiki, or on a host you control.
Every agent runs a loop. It sends the conversation to a model, calls tools, and repeats until it has an answer. The turn harness is where that loop runs. Keiki hosts it by default. You can also run it on your own machine with Hermes, or inside your own app with the external harness.
You choose per agent. Moving one agent to your own host leaves the rest on Keiki. Each connection belongs to the agent it was set up on, so two agents can point at different hosts.
Hermes cannot receive steered or Live Test turns, so steer a Hermes agent from one of its real channels instead. The external harness supports steered and Live Test turns; Keiki marks them held back so the app's answer is captured without reaching the contact.
Keiki-hosted (default)
Keiki runs the loop for you. There is nothing to set up.
const kit = AgentKit.create({
// ...
harness: { type: 'flue' },
})Leave harness out and you get the same thing. If you register an SDK webhook with
kit.serve(), that webhook still takes precedence.
Keiki VM (Maritime)
Keiki gives the agent its own micro-VM on Maritime and runs the loop there. Everything else stays on Keiki: channels, history, prompt, tools, steering, guards, traces, billing and delivery. Steered and silent turns and Live Test work exactly as they do on the Keiki-hosted harness.
It is a platform setting, not a choice on the agent: with ENABLE_MARITIME=true, every agent
created from then on (dashboard, template, deployment or developer API) gets its own VM.
Existing agents keep their harness, and SDK agents, whose loop runs on the developer's server,
are not affected. Keiki uploads its runner, creates the VM and shows
its state on the agent page: provisioning, deploying, running or asleep. The VM sleeps between
turns and wakes on the next message in a few seconds. If a build fails or the VM is lost, the
page shows Maritime's reason and a Redeploy VM button, which replaces the VM with a fresh
one built from the current runner. Switching the agent to another harness, or deleting it,
deletes its VM.
The VM holds no Keiki or model credentials. Keiki drives each turn over short long-polls (each under Maritime's 30-second request limit), which also keep the VM awake while the turn runs. The loop's model calls, tool calls and trace spans come back over those polls and run on Keiki, so the VM never calls Keiki or OpenRouter itself. The only secret on the VM is a token that opens its own turn routes.
The platform needs ENABLE_MARITIME, MARITIME_API_KEY and the runner bundle built with
bun run build:maritime-runner (part of bun run build).
Run the loop yourself
You run the loop on a machine you control. Keiki still exposes the agent's tools and records what happens in the same turn trace.
Keiki sends each inbound turn with a stable session ID per contact, and your host keeps the conversation's context. Keiki still stores the messages for history and audit.
To set it up, create or edit an agent, select Hermes, then copy the prompt shown there and hand it to a coding agent on the host machine. It runs:
curl -fsSL https://<your-keiki-host>/api/v1/harness/install.sh \
| KEIKI_API_KEY=sk_live_… bashThe installer installs Hermes, turns on its OpenAI-compatible API server, registers Keiki's
MCP server and observability plugin, stores the local Hermes key as an encrypted secret, and
points the agent at the host. You can run it again safely. It takes its options through
CONNECTIVITY.
Set HERMES_BIN if the Hermes binary is not on PATH. If you run the installer inside the
gateway, set HERMES_INSIDE_GATEWAY=1 so it skips the restart and lets the gateway pick up
the new settings when it next starts.
External (your app)
For an application that runs its own model loop, choose the external harness and set
dispatchUrl in push mode, or use relay mode. Keiki sends your app a signed asynchronous turn
request; your app responds 202 as soon as it accepts the work, then calls
the turn-scoped /api/v1/turns/:turnId/messages, /api/v1/turns/:turnId/stream,
/api/v1/turns/:turnId/typing, and /api/v1/turns/:turnId/complete endpoints as needed. Copy
the signing secret returned once by POST /api/v1/developer/agents/:agentId/harness/signing-secret
into your app's secret manager. Keiki still handles channel delivery, exposes the agent's
tools, and records the turn trace. See the external harness contract
for the full request and response details.
With the SDK, configure the external harness like this:
harness: {
type: 'external',
connectivity: 'push',
dispatchUrl: 'https://agent.example.com/keiki/turn',
timeoutMs: '5 minutes',
}For relay mode, set connectivity: 'relay' and omit dispatchUrl.
For an outside agent that polls when available, set connectivity: 'inbox' and omit
dispatchUrl; the agent uses a harness API key to claim and complete queued turns. A
poller must be running for test and steer turns.
The Eve integration mounts POST /keiki/v1/dispatch; see the
Eve extension guide for setup and the full route URL.
The Flue integration mounts POST /channels/keiki/dispatch; see the
Flue integration guide for Node and Cloudflare setup.
For AI SDK 7 apps, see the AI SDK integration guide, including its
prepareStep helper for pulling durable arrivals between model steps.
External turns follow the agent's midTurnStrategy. With append, eligible push-mode turns
receive signed arrival requests in the active app run; the app reports consumed sequence
numbers at completion, and any unconsumed or rejected batch is returned to the queue. Relay
mode does not take arrivals, so those messages are answered by a later turn. With interrupt,
Keiki cancels the active turn and starts a fresh one with the correction. With queue, the
message waits for the active turn to finish and is answered by a later turn.
Hermes connectivity
| Mode | CONNECTIVITY | What it does |
|---|---|---|
| Relay | relay | Default. A small daemon on your host polls Keiki for turns, so you do not need to open a port. |
| Public URL | public-url | You already have an HTTPS address that forwards to the host. Set HERMES_PUBLIC_URL to it. |
| Cloudflare quick tunnel | cloudflared | Uses the agent-scoped supervisor served at /api/v1/harness/tunnel.sh. |
| ngrok | ngrok | Uses the same agent-scoped supervisor. Set NGROK_AUTHTOKEN or configure ngrok first. |
curl -fsSL https://<your-keiki-host>/api/v1/harness/install.sh \
| KEIKI_API_KEY=sk_live_… CONNECTIVITY=relay bashcurl -fsSL https://<your-keiki-host>/api/v1/harness/install.sh \
| KEIKI_API_KEY=sk_live_… CONNECTIVITY=public-url \
HERMES_PUBLIC_URL=https://hermes.example.com bashTraces and billing
Every turn gets a Keiki trace, and the tool calls from your host show up in the same trace as the rest. Keiki marks which activity came from your host.
You pay for message delivery through the usual counters. Model tokens your own provider serves are between you and that provider.
Security
- Turns travel with short-lived credentials tied to the key, the conversation, the chat and the trace.
- Hermes keys and external signing secrets are stored encrypted, never in plain config.
- Keiki will not push turns to private networks, redirects, URLs with credentials, or plain HTTP unless a self-hosted deployment opts in.