build

OpenAI-compatible HTTP

Connect an existing chat or Responses API integration to Kestrel without rewriting its request format first.

SDKintermediateCurrent releases
Verified 2026-08-04View sourceReport a docs issue

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 pathBest fitWhat you gainWhat you do not get directly
OpenAI-compatible HTTPchat-first or responses-first appsfamiliar request bodies, simple proxy routes, standard SSEoperator control, task graph, project snapshot APIs
@kestrel-agents/sdkserver-owned product logictyped agent API, session memory, streaming, subscriptionsno drop-in OpenAI client compatibility
runner APIsadvanced operational toolscomplete operator and event APIsmore app-side routing and payload work

Available endpoints

The compatibility layer exposes:

  • GET /v1/models
  • POST /v1/chat/completions
  • POST /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:

Bash
curl -sS \
  -H "Authorization: Bearer $KESTREL_RUNNER_SERVICE_TOKEN" \
  "$KESTREL_RUNNER_SERVICE_URL/v1/models"

The response lists the single compatibility model, kestrel. Other model IDs are rejected.

Example response:

JSON
{
  "data": [
    {
      "id": "kestrel",
      "object": "model"
    }
  ],
  "object": "list"
}

Required auth and actor headers

Your Next.js server should forward bearer auth and actor context on every request:

Text
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: acme

The 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.

TypeScript
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:

TypeScript
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

JSON
{
  "model": "kestrel",
  "messages": [
    {
      "role": "user",
      "content": "Summarize the latest deployment issues."
    }
  ],
  "stream": true
}

Streamed response headers worth preserving:

Text
x-kestrel-session-id: kestrel-session-001
x-kestrel-run-id: run_01workspacecopilot
x-kestrel-thread-id: thread_01workspacecopilot
x-kestrel-model-id: kestrel

Responses API

JSON
{
  "model": "kestrel",
  "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:

JSON
{
  "id": "resp_01workspacecopilot",
  "object": "response",
  "created_at": 1710000000,
  "model": "kestrel",
  "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": "kestrel-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.

StageUse this
existing OpenAI-style chat UIproxy /v1/chat/completions
responses-first or structured output appproxy /v1/responses
app now needs session memory, subscriptions, or typed routesmove to @kestrel-agents/sdk and @kestrel-agents/next
app now needs operator control, task graph, or project reviewadd 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-id
  • x-kestrel-run-id
  • x-kestrel-thread-id
  • x-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, or operator.control
  • mission_control.project.get or mission_control.action.execute
  • authorized Memory queries or mutations
  • filtered background subscriptions and replay cursors
  • exact waiting, recovery, and external-approval decisions
  • direct budget, evaluation, and quality workflows

Compatibility endpoints map OpenAI-style success and errors, but do not expose every Kestrel-native terminal state. Preserve Kestrel correlation headers, and move to the native protocol before the application depends on recovery, approvals, Mission Control, or durable subscriptions.

Continue when you need more control