If your application already uses Chat Completions or Responses API requests, you can point its server-side integration at Kestrel first and adopt the richer SDK capabilities later. Your existing request format stays familiar while Kestrel adds durable runs, session identifiers, and user attribution.
When to choose this path
Choose the OpenAI-compatible path when:
- your app already has OpenAI-style client code
- you only need chat or responses semantics
- you want server-owned auth, actor metadata, and durable run IDs
- you are not yet using operator inbox, task graph, or project snapshot APIs directly
Use the SDK and runner APIs instead when the application needs direct access to operator controls, project state, or background subscriptions.
| Integration path | Best fit | What you gain | What you do not get directly |
|---|---|---|---|
| OpenAI-compatible HTTP | chat-first or responses-first apps | familiar request bodies, simple proxy routes, standard SSE | operator control, task graph, project snapshot APIs |
@kestrel-agents/sdk | server-owned product logic | typed agent API, session memory, streaming, subscriptions | no drop-in OpenAI client compatibility |
| runner APIs | advanced operational tools | complete operator and event APIs | more app-side routing and payload work |
Available endpoints
The compatibility layer exposes:
GET /v1/modelsPOST /v1/chat/completionsPOST /v1/responses
All three endpoints use the same runner and produce the same durable run history as SDK requests. The compatibility surface is intentionally fixed to the Reference agent; it is not a listing of every CLI profile.
Models example
Use GET /v1/models to verify that the compatibility surface is reachable:
curl -sS \
-H "Authorization: Bearer $KESTREL_RUNNER_SERVICE_TOKEN" \
"$KESTREL_RUNNER_SERVICE_URL/v1/models"The response lists reference-react. Requests may also use the accepted compatibility alias reference-web; both select the same Reference-agent implementation. Other configured profile IDs are rejected.
Example response:
{
"data": [
{
"id": "reference-react",
"object": "model"
}
],
"object": "list"
}Required auth and actor headers
Your Next.js server should forward bearer auth and actor context on every request:
Authorization: Bearer <service-token>
x-kestrel-actor-id: user-123
x-kestrel-actor-type: end_user
x-kestrel-actor-name: Taylor Example
x-kestrel-tenant-id: acmeThe browser should not know the service token. Keep the proxy route in your app and attach actor metadata from your own auth system.
Full Next.js proxy example
This is the simplest useful pattern for a browser app that already uses OpenAI-style chat. The browser posts to your Next.js route. The route keeps authentication and correlation on the server, then sends the request to its configured Local Core bridge or remote runner service. The shared Execution Protocol turns that request into a durable Kestrel run.
import "server-only";
export async function POST(request: Request) {
const body = await request.text();
const user = await requireAuthenticatedUser(request);
const upstream = await fetch(
`${process.env.KESTREL_RUNNER_SERVICE_URL}/v1/chat/completions`,
{
method: "POST",
headers: {
"content-type": "application/json",
authorization: `Bearer ${process.env.KESTREL_RUNNER_SERVICE_TOKEN}`,
"x-kestrel-actor-id": user.id,
"x-kestrel-actor-type": "end_user",
"x-kestrel-actor-name": user.name,
"x-kestrel-tenant-id": user.tenantId,
},
body,
},
);
return new Response(upstream.body, {
status: upstream.status,
headers: upstream.headers,
});
}Responses proxy example
If the app uses the Responses API instead of chat completions, keep the same server-owned pattern:
import "server-only";
export async function POST(request: Request) {
const body = await request.text();
const user = await requireAuthenticatedUser(request);
const upstream = await fetch(
`${process.env.KESTREL_RUNNER_SERVICE_URL}/v1/responses`,
{
method: "POST",
headers: {
"content-type": "application/json",
authorization: `Bearer ${process.env.KESTREL_RUNNER_SERVICE_TOKEN}`,
"x-kestrel-actor-id": user.id,
"x-kestrel-actor-type": "end_user",
"x-kestrel-actor-name": user.name,
"x-kestrel-tenant-id": user.tenantId,
},
body,
},
);
return new Response(upstream.body, {
status: upstream.status,
headers: upstream.headers,
});
}Request examples
Chat completions
{
"model": "reference-react",
"messages": [
{
"role": "user",
"content": "Summarize the latest deployment issues."
}
],
"stream": true
}Streamed response headers worth preserving:
x-kestrel-session-id: reference-session-001
x-kestrel-run-id: run_01workspacecopilot
x-kestrel-thread-id: thread_01workspacecopilot
x-kestrel-model-id: reference-reactResponses API
{
"model": "reference-react",
"input": "Return machine readable status",
"text": {
"format": {
"type": "json_schema",
"json_schema": {
"name": "compat_status",
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string"
}
},
"required": ["status"],
"additionalProperties": false
}
}
}
}
}POST /v1/responses returns a response object and includes the Kestrel run id in its response metadata.
Example Responses API payload:
{
"id": "resp_01workspacecopilot",
"object": "response",
"created_at": 1710000000,
"model": "reference-react",
"status": "completed",
"output": [
{
"type": "message",
"id": "msg_01workspacecopilot",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "{\"status\":\"needs-review\"}"
}
]
}
],
"output_text": "{\"status\":\"needs-review\"}",
"metadata": {
"kestrel": {
"session_id": "reference-session-001",
"run_id": "run_01workspacecopilot"
}
}
}Model selection and migration path
Start with compatibility mode when preserving the existing client is the priority. Move to the SDK when you need more of Kestrel's session and operator capabilities.
| Stage | Use this |
|---|---|
| existing OpenAI-style chat UI | proxy /v1/chat/completions |
| responses-first or structured output app | proxy /v1/responses |
| app now needs session memory, subscriptions, or typed routes | move to @kestrel-agents/sdk and @kestrel-agents/next |
| app now needs operator control, task graph, or project review | add the runner client or custom protocol |
Response headers worth preserving
If your frontend needs continuity or debugging breadcrumbs, forward these response headers from the configured Kestrel execution target:
x-kestrel-session-idx-kestrel-run-idx-kestrel-thread-idx-kestrel-model-id
x-kestrel-request-id and x-kestrel-correlation-id are generated by the
@kestrel-agents/next route helpers. They are application-route correlation
headers, not OpenAI-compatibility response headers from the runner service.
Those headers make it possible to connect a chat completion back to later operator inspection or replay evidence.
Keep the existing client
Instead of writing typed agent routes first, put Kestrel behind a thin proxy route and keep the client request format unchanged.
Adopt Kestrel without rewriting the client
Compatibility mode lets you keep familiar HTTP while gaining durable runs, actor attribution, and request correlation. Your runner credential remains on the application server.
When to move beyond compatibility mode
Move to the SDK or runner protocol when you need any of these:
operator.inbox,operator.thread, oroperator.controltask.graph.getortask.graph.updatemission_control.project.getormission_control.action.executeproject.snapshot.getor Git-onlyproject.action- filtered background subscriptions over
/events/stream - direct evaluation and quality workflows
Those capabilities are what separate Kestrel from a thin compat proxy.