<!-- Public documentation export; not library implementation source. -->
# Server-side models

Most AI integrations call the model from a server while the chart runs in the browser. A remote session keeps the chart, its fences and its native owners in the browser and gives the server the same command facade and model tools.

```ts
// Browser
import { serveAutomation } from '@lumencharts/chart-automation/sdk';

const sdk = await chart.openAutomation({ policy: { effects: ['read', 'mutation'] } });
const server = serveAutomation(sdk, {
  captureResult: async capture => ({ attachmentId: await upload(capture.blob) }),
});
myChannel.onCall(async (call, signal) => myChannel.reply(await server.handle(call, signal)));
startTurn({ chart: server.snapshot() });
```

```ts
// Server
import { createAutomationTools, connectAutomation } from '@lumencharts/chart-automation/sdk';

const chart = connectAutomation(turn.chart, {
  send: (call, signal) => myChannel.call(call, signal),
});
const tools = createAutomationTools(chart, { use: [budgets, auditLog], imageResult: toProviderImage });
const system = chart.instructions();
```

## Protocol

| Message | Shape |
| --- | --- |
| Snapshot | `{ version: 1, target, capabilities, instructions }`, sent by the browser at the start of a turn and again when `capabilities().revision` changes (`chart.update(snapshot)`) |
| Call | `{ version: 1, requestId, target, name, commandVersion?, input, timeoutMs? }` |
| Result | `{ version: 1, ok: true, value, target }` or `{ version: 1, ok: false, code, target }` |

`name` is a command name or one of `data.series`, `data.read`, `data.requestHistory` and `capture.image`. Timeouts are relative, so server and browser clocks need not agree; the remote session also enforces the snapshot's deadline ceiling locally. Results carry the chart's current target, so the server learns about a navigation and later calls are fenced to the new chart. A call built against a different `commandVersion` rejects with `unsupported`. Errors carry a chart error code and, for rejected input, optional `details` (`{ path, reason }`), never a message. `parseAutomationRemoteResult` validates and bounds `details` and drops anything else.

Capture is advertised only when the browser supplies `captureResult`, which turns the PNG into something your channel can carry; on the server, `capture.image()` and the `capture_image` tool resolve to that value.

## Transport responsibilities

The SDK is transport-neutral. Your channel owns:

- delivery and reconnection;
- durable replay by `requestId`: a repeated call returns the stored result instead of running twice;
- rejecting a known `requestId` sent with different input;
- forwarding cancellation through the `signal` on both sides.

`server.handle` never rejects; it always resolves to a result. `chart.destroy()` aborts in-flight calls on the server side, and later calls reject with `unavailable`.

---
Documentation page: https://docs.lumencharts.com/charting-library/automation/remote
