flue
Connect a Flue 2.x application to Keiki's external harness.
Use @onkeiki/flue to run a
Flue 2.x application as a Keiki external harness. Keiki keeps channel
delivery, turn scheduling, completion, and trace history while Flue owns the
agent runtime.
Install
npm install @onkeiki/flue @flue/runtimeOr run npx @onkeiki/cli init in your app to do this setup for you (see CLI).
The package supports Flue's Node and Cloudflare targets. Use a current Node
release for local development. Cloudflare applications need
nodejs_compat because the channel signing helper uses the Node-compatible
crypto API.
Create the channel
Create a channel module with the Flue agent that should receive Keiki turns:
import { createKeikiChannel } from '@onkeiki/flue'
import { Assistant } from '../agents/assistant.ts'
export const channel = createKeikiChannel({
agent: Assistant,
signingSecret: process.env.KEIKI_SIGNING_SECRET,
})Mount the returned router below your preferred path:
app.route('/channels/keiki', channel.route())Keiki then sends signed POST /channels/keiki/dispatch requests. The route
returns 202 { "accepted": true } after Flue admits the delivery.
Configure the agent
Call useKeiki() from the Flue agent module:
import { useKeiki } from '@onkeiki/flue'
useKeiki()The hook reads each delivered Keiki signal, mounts a turn-scoped MCP connection, requests typing, sends assistant messages, and settles the Keiki turn when the Flue submission finishes. It handles interim assistant text before a tool call as well as the final response.
Signing secret
Create the external-harness secret once:
POST /api/v1/developer/agents/:agentId/harness/signing-secretStore the returned value in your secret manager as KEIKI_SIGNING_SECRET.
Pass a string or a resolver instead when the secret is supplied by your
runtime:
useKeiki({
signingSecret: () => env.KEIKI_SIGNING_SECRET,
})The channel route and the agent hook can run in different isolates. Flue signs
a short-lived kts1 credential for each turn and each MCP or turn-API
request; the credential is never placed in the signal body, attributes,
initial data, or durable history.
For Cloudflare, bind the secret to the Worker and make it available to the
Durable Object that runs the agent module. Pass the Durable Object's
environment-backed resolver to createKeikiChannel() and useKeiki() rather
than copying the secret into a message or instance ID.
For local workerd development, provide the secret in Wrangler's .dev.vars
file; for deployed Workers, configure it as a Worker secret or binding. Keep
.dev.vars out of version control. With nodejs_compat, the Worker and its
Durable Objects can read their configured values through process.env.
Serving several agents
Use one resolver for the channel and the Flue hook:
const secrets: Record<string, string> = { agentA: secretA, agentB: secretB }
const channel = createKeikiChannel({
agent: Assistant,
signingSecret: ({ agentId }) => secrets[agentId],
})
useKeiki({ signingSecret: ({ agentId }) => secrets[agentId] })Returning nothing rejects the push with 401 unknown_agent. The resolver runs
before the signature is verified, so use a local map or cache rather than making
a network call on every request. Flue calls it on every push and each signed
turn API or MCP request.
Held-back turns and tools
Keiki can hold a turn back while still running it. The Flue agent receives
the turn and can use mcp__keiki__* tools, but Keiki does not deliver the
answer to the contact when the turn is held back.
The mounted MCP connection exposes Keiki's turn-scoped tools, including channel replies, files, reactions, and integrations. Its auth callback signs a fresh credential before every request, including retries.
Instructions and retry deduplication
Instructions are included in a <keiki_instructions> block by default:
createKeikiChannel({ agent: Assistant, instructions: false })Set instructions: false to omit that block. Flue dispatches use the Keiki
turn ID as Flue's native idempotency key, so a retried dispatch is admitted
once while duplicate receipts still receive 202.
Mid-turn messages
With midTurnStrategy: 'append', an eligible push-mode turn dispatches each new batch to
the same agent/conversation instance as a keiki.arrival signal. The hook keeps the active
Keiki binding while Flue submissions for that instance are queued or running. Completion
waits until the instance is idle, so a queued arrival can send its reply under the same
Keiki turn and its sequence is included in consumedArrivals. Unconsumed batches, including
arrivals rejected by an older adapter, return to Keiki's pending queue for a later turn.
Relay-mode turns do not take arrivals.
An arrival submission that starts after the Keiki turn has reached its completion boundary
does not attach to that turn or mark its sequence consumed. Its turn API calls can return
409 turn_closed; Keiki requeues the message, and a later turn answers it.
Cancellation
A cancellation request aborts the Flue instance addressed by the same agent
and conversation identity. The platform records the resulting Keiki turn as
cancelled; a late completion attempt receives 409 turn_closed.
v1 limits
- Live token streaming is not bridged; completed assistant messages are sent.
- Flue approval prompts and input requests are not bridged.
- Relay mode is not shipped; use a push dispatch URL.