A trusted application server can reach Kestrel in two ways:
- During local development,
kestrel webexposes Local Core through an authenticated loopback HTTP bridge. - In a hosted environment, the application server connects to a remote runner service.
Both connections expose the same Execution Protocol. They differ in where the runtime is hosted, not in how runs and results behave.
Keep the browser outside the execution boundary
This arrangement keeps three responsibilities clear:
- the browser only speaks to the application
- the application server owns user authentication and runner credentials
- Local Core or the remote runner service owns durable execution
Do not call either execution target from browser code. Doing so would expose the runner credential and allow browser input to define trusted user or tenant information.
Expose Local Core to a local application
Make sure Local Core is ready, then run:
kestrel webThis command starts an HTTP bridge to the existing Local Core runtime. It does not start another runtime or copy Local Core state. The command prints:
KESTREL_RUNNER_SERVICE_URLKESTREL_RUNNER_SERVICE_TOKEN
Use those values in the trusted server process for your local application:
export KESTREL_RUNNER_SERVICE_URL=http://127.0.0.1:4010
export KESTREL_RUNNER_SERVICE_TOKEN=dev-secret
pnpm devWith those values in place, the shared workspaceCopilotAgent can serve:
/api/copilot/run/api/copilot/stream/api/copilot/webhook
The local request path is:
Browser
-> Next.js server route
-> workspaceCopilotAgent
-> kestrel web bridge
-> Local Core
-> Execution Protocol and runtimeConnect to a remote runner service
In a hosted environment, set the same variables to the authenticated URL and token issued for the remote runner service. The application code and Execution Protocol remain the same:
Browser
-> application server
-> SDK or framework adapter
-> remote runner service
-> Execution Protocol and runtimekestrel web is the local bridge, not a production runner deployment recipe.
Provision, authenticate, monitor, and recover a remote runner service as part
of the hosted environment described in the production operating
model.
Check runner health
Use these checks before debugging application routes:
echo "$KESTREL_RUNNER_SERVICE_URL"
echo "$KESTREL_RUNNER_SERVICE_TOKEN"
curl -sS "$KESTREL_RUNNER_SERVICE_URL/health"The health check should succeed before you spend time on route-level debugging. A healthy application with an unreachable execution target is still a broken Kestrel deployment.
Send a test command
After the health check succeeds, verify both the OpenAI-compatible endpoint and the command endpoint:
curl -sS "$KESTREL_RUNNER_SERVICE_URL/v1/models" \
-H "Authorization: Bearer $KESTREL_RUNNER_SERVICE_TOKEN"
curl -sS "$KESTREL_RUNNER_SERVICE_URL/commands" \
-H "Authorization: Bearer $KESTREL_RUNNER_SERVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "cmd-health-check",
"type": "runner.ping",
"metadata": {
"actor": {
"actorId": "operator-check",
"actorType": "operator",
"tenantId": "acme"
},
"tenantId": "acme"
},
"payload": {
"nonce": "docs-check"
}
}'Example runner.ping response:
{
"id": "event-id",
"type": "runner.pong",
"ts": "2026-07-20T12:00:00.000Z",
"payload": {
"nonce": "docs-check"
}
}For raw Execution Protocol commands, actor and tenant context belongs in the
envelope's metadata. The x-kestrel-actor-* headers apply to the
OpenAI-compatible HTTP surface, not /commands.
If /health works but /commands fails, check the token and command metadata. If
/commands works but the application route fails, inspect the application route
and its environment settings.
What to inspect first
Use this order when the application looks broken:
- confirm the configured runner URL points to the intended local or remote target
- confirm
/health - confirm
GET /v1/models - confirm
/commandswithrunner.ping - only then inspect the application routes
For a local bridge failure, inspect Local Core before restarting the application. For a remote failure, inspect the deployed runner service and its dependencies.