# zidane/headless

> Run a prompt to completion without a UI and get one serializable result with the full trajectory.

Import: `import { … } from 'zidane/headless'`. Source: [src/headless.ts](https://github.com/Tahul/zidane/blob/main/src/headless.ts).

Use `runHeadless` to run a prompt in a chosen working directory for automation, evaluation and RL rollouts. A turn limit and a wall-clock limit bound the run. It resolves with the status, final text, optional schema-checked output, usage and the whole transcript. The other exports format results, stream events as JSON lines and convert transcripts to provider message formats. With `ZIDANE_DEBUG` set, prompt-cache breaks are logged to stderr; include `cache` in the value for a line every turn. See [Headless and CI](https://zidane.si/docs/cli-headless.md).

```ts
import { runHeadless } from 'zidane/headless'
import { anthropic } from 'zidane/providers/anthropic'

const result = await runHeadless({
  prompt: 'Add a /health route that returns 200.',
  provider: anthropic(),
  cwd: process.cwd(),
  maxTurns: 30,
  timeoutMs: 600_000,
})
console.log(result.status, result.finalText)
```

## Functions

### `exitCodeForHeadlessResult`

Function.

Maps a run status to a process exit code: 0 for `'completed'`, 124 for `'timeout'`, 130 for `'aborted'` and 1 for `'error'`.

```ts
function exitCodeForHeadlessResult(result: HeadlessResult): number
```

**Parameters**

- `result` (`HeadlessResult`): Result of [`runHeadless`](https://zidane.si/docs/reference/headless.md#runHeadless).

**Returns** `number`

Source: [src/headless-format.ts:110](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L110)

### `formatHeadlessResult`

Function.

Formats a headless result for output. Returns the result unchanged for `'zidane'`, or its transcript as provider messages for `'provider'`.

```ts
function formatHeadlessResult(
  result: HeadlessResult,
  options: { format: HeadlessOutputFormat; providerName: string },
): FormattedHeadlessResult
```

**Parameters**

- `result` (`HeadlessResult`): Result of [`runHeadless`](https://zidane.si/docs/reference/headless.md#runHeadless).
- `options` (`{ format: HeadlessOutputFormat; providerName: string }`): Output `format` and the `providerName` that picks the message format.

**Returns** `FormattedHeadlessResult`

Source: [src/headless-format.ts:186](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L186)

### `formatHeadlessTurnEvent`

Function.

Formats a `turn` event for output. Returns the event unchanged for `'zidane'`, or its turn as provider messages for `'provider'`.

```ts
function formatHeadlessTurnEvent(
  event: { index: number; turn: SessionTurn; type: 'turn' },
  options: { format: HeadlessOutputFormat; providerName: string },
): FormattedHeadlessTurnEvent
```

**Parameters**

- `event` (`{ index: number; turn: SessionTurn; type: 'turn' }`): A `turn` event from [`HeadlessEvent`](https://zidane.si/docs/reference/headless.md#HeadlessEvent).
- `options` (`{ format: HeadlessOutputFormat; providerName: string }`): Output `format` and the `providerName` that picks the message format.

**Returns** `FormattedHeadlessTurnEvent`

Source: [src/headless-format.ts:201](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L201)

### `formattedHeadlessTurnEventToJsonl`

Function.

Serializes a formatted turn event as a newline-terminated JSON line.

```ts
function formattedHeadlessTurnEventToJsonl(event: FormattedHeadlessTurnEvent): string
```

**Parameters**

- `event` (`FormattedHeadlessTurnEvent`): Event from [`formatHeadlessTurnEvent`](https://zidane.si/docs/reference/headless.md#formatHeadlessTurnEvent).

**Returns** `string`

Source: [src/headless-format.ts:149](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L149)

### `headlessEventToJsonl`

Function.

Serialize one event as a newline-terminated JSON line (stream-json).

```ts
function headlessEventToJsonl(event: HeadlessEvent): string
```

**Parameters**

- `event` (`HeadlessEvent`): Event to serialize.

**Returns** `string`

Source: [src/headless-format.ts:140](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L140)

### `installHeadlessEventAdapter`

Function.

Converts agent hook events into [`HeadlessEvent`](https://zidane.si/docs/reference/headless.md#HeadlessEvent)s and passes them to `onEvent`. Errors thrown by `onEvent` are swallowed.

```ts
function installHeadlessEventAdapter(
  hooks: Hookable<AgentHooks>,
  onEvent: (event: HeadlessEvent) => void,
): () => void
```

**Parameters**

- `hooks` (`Hookable<AgentHooks>`): Agent hook bus to listen on.
- `onEvent` (`(event: HeadlessEvent) => void`): Called with each event.

**Returns** `() => void`: A function that removes the listeners.

Source: [src/headless-format.ts:428](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L428)

### `providerTranscriptFormatForProvider`

Function.

Picks the transcript format for a provider: `'anthropic'` for the Anthropic provider, `'openai'` for every other.

```ts
function providerTranscriptFormatForProvider(
  providerName: string,
): ProviderTranscriptFormat
```

**Parameters**

- `providerName` (`string`): Name of the provider, as in `Provider.name`.

**Returns** `ProviderTranscriptFormat`

Source: [src/headless-format.ts:158](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L158)

### `runHeadless`

Function.

Run a prompt to completion, headless, and return a single serializable
[`HeadlessResult`](https://zidane.si/docs/reference/headless.md#HeadlessResult). Safe to call concurrently for parallel rollouts —
each call builds its own agent + session and tears them down in `finally`.

```ts
function runHeadless(opts: HeadlessOptions): Promise<HeadlessResult>
```

**Parameters**

- `opts` (`HeadlessOptions`): Prompt, provider and run limits. `prompt` and `provider` are required.

**Returns** `Promise<HeadlessResult>`: The run result. Errors from the agent loop, aborts and timeouts are reported in `status` and `error` instead of being thrown.

**Example**

```ts
~~~ts
import { exitCodeForHeadlessResult, runHeadless } from 'zidane/headless'
import { anthropic } from 'zidane/providers/anthropic'

const result = await runHeadless({
  prompt: 'List the TypeScript files in this directory.',
  provider: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }),
  cwd: process.cwd(),
  maxTurns: 10,
  timeoutMs: 120_000,
})

console.log(result.status, result.finalText)
process.exitCode = exitCodeForHeadlessResult(result)
~~~
```

Source: [src/headless.ts:153](https://github.com/Tahul/zidane/blob/main/src/headless.ts#L153)

### `transcriptToOpenAIMessages`

Function.

Convert raw `session.turns` into standard OpenAI chat-completion messages:
assistant turns carry `tool_calls`, and each `tool_result` becomes its own
`role: 'tool'` message. This is the drop-in shape for an SFT renderer —
unlike `toOpenAI` (session/messages.ts), which emits an internal `_tag`
envelope meant for re-sending to a provider, not for training data.

System notes become `<system-reminder>` text; a note without text is dropped.

Fails closed on corrupt raw turns instead of fabricating tool results; silent
placeholders would create structurally-valid but semantically-poisoned SFT
examples.

```ts
function transcriptToOpenAIMessages(
  turns: SessionTurn[],
  options?: { strictToolPairing?: boolean },
): OpenAIChatMessage[]
```

**Parameters**

- `turns` (`SessionTurn[]`): Session turns, for example `result.transcript`.
- `options?` (`{ strictToolPairing?: boolean }`, default `{}`): `strictToolPairing` (default `true`) throws when the turns need tool pairing repairs. Set it to `false` to convert them as they are.

**Returns** `OpenAIChatMessage[]`

Source: [src/headless-format.ts:248](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L248)

### `transcriptToProviderMessages`

Function.

Converts session turns to the message format of a provider. Non-wire turns are dropped for Anthropic. The OpenAI path does not enforce tool pairing. System notes become `<system-reminder>` text in both formats.

```ts
function transcriptToProviderMessages(
  turns: SessionTurn[],
  providerName: string,
): unknown[]
```

**Parameters**

- `turns` (`SessionTurn[]`): Session turns, for example `result.transcript`.
- `providerName` (`string`): Name of the provider, as in `Provider.name`.

**Returns** `unknown[]`

Source: [src/headless-format.ts:168](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L168)

## Interfaces

### `HeadlessErrorInfo`

Interface.

Error that ended a headless run.

```ts
interface HeadlessErrorInfo
```

**Properties**

- `message` (`string`): Error message.
- `type` (`string`): Typed-error class name (`AgentAbortedError`, `AgentContextExceededError`, …).

Source: [src/headless-format.ts:49](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L49)

### `HeadlessOptions`

Interface.

Options for [`runHeadless`](https://zidane.si/docs/reference/headless.md#runHeadless).

```ts
interface HeadlessOptions
```

**Properties**

- `behavior?` (`AgentBehavior`): Agent behavior defaults for this headless run. Top-level shortcuts below override matching fields.
- `cwd?` (`string`): Working directory for the default process context (ignored if `execution` is set).
- `durable?` (`DurableRuntime | DurableOptions`): Durable runtime the session is stored on (`createAgent({ durable })`), or it with `onInterrupted`: runs keep checkpoints.
- `execution?` (`ExecutionContext`): Execution context. Defaults to a process context rooted at `cwd`.
- `extensions?` (`ExtensionRegistry`): Pre-built extension registry folded into the agent via `createAgent`
  (build it with `buildExtensionRegistry(defs.map(inlineLoadedExtension))`).
  Contributes the registry's tools / hooks / mcpServers / skills /
  system-fragments / subagents, with the explicit options above winning on
  collision. Because hooks fold through `createAgent`, a headless run with
  `extensions` also gets extension hooks (e.g. a `tool:transform` that
  rewrites results), not just tools.
- `includeThinking?` (`boolean`): Keep `thinking` blocks in `result.transcript` (default true).
- `maxTokens?` (`number`): Maximum output tokens per turn. Sets `behavior.maxTokens`.
- `maxTurns?` (`number`): Maximum number of loop turns. Sets `behavior.maxTurns`.
- `mcpServers?` (`McpServerConfig[]`): MCP servers to connect and expose as tools.
- `model?` (`string`): Model id. Defaults to the provider's default model.
- `onEvent?` (`(event: HeadlessEvent) => void`): Live event callback — the in-process stream-json equivalent.
- `preset?` (`Preset`): Base [`Preset`](https://zidane.si/docs/reference/presets.md#Preset) for the agent — spread in place of the default `basic`
  preset (use it to swap the system prompt / tool set wholesale). The
  top-level shortcuts above (`system`, `behavior`, `tools`, `mcpServers`,
  `skills`) still override matching preset fields.
- `prompt` (`string | PromptPart[]`): User prompt — plain string or multimodal `PromptPart[]`.
- `provider` (`Provider`): Built provider (e.g. `local()`, `openaiCompat(...)`, `anthropic(...)`).
- `resumeRunId?` (`string`): Continue this checkpoint run of `session` (`agent.resume`) before running `prompt`. Needs `durable`.
- `schema?` (`Record<string, unknown>`): JSON Schema for structured-output enforcement → populates `result.output`.
- `session?` (`Session`): Reuse / resume an existing session.
- `signal?` (`AbortSignal`): External abort signal — chained with the internal timeout controller.
- `skills?` (`SkillsConfig`): Skills configuration.
- `store?` (`SessionStore`): Session store for a fresh session (defaults to an in-memory store).
- `subagents?` (`SubagentRegistry`): Subagents folded into the preset's spawn tool, filling gaps only (an
  agent the preset or an extension already defines wins). The CLI passes
  builtin + markdown agents from `loadMarkdownAgents`.
- `system?` (`string`): Override the preset system prompt for this run.
- `thinking?` (`'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'adaptive'`): Thinking effort for the run. Defaults to `'off'`.
- `timeoutMs?` (`number`): Wall-clock cap; on expiry the run is aborted and `status` becomes `'timeout'`.
- `tools?` (`Record<string, ToolDef>`): Tool overrides. Omit to use the basic preset's tools.

Source: [src/headless.ts:54](https://github.com/Tahul/zidane/blob/main/src/headless.ts#L54)

### `HeadlessResult`

Interface.

Strictly JSON-serializable postmortem of one headless run. Everything an RL
reward function needs: the final answer (`finalText`), the verifiable
structured output (`output`, present iff a `schema` was set), usage/turns,
and the lossless `transcript` (the SFT training data).

```ts
interface HeadlessResult
```

**Properties**

- `durationMs` (`number`): Wall-clock duration of the run in milliseconds.
- `error?` (`HeadlessErrorInfo`): Error that ended the run. Absent when it did not throw.
- `finalText` (`string`): Concatenated text of the last assistant turn that produced any text.
- `finishReason?` (`TurnFinishReason`): Finish reason of the final turn that reported one.
- `numToolCalls` (`number`): Total `tool_call` blocks across the whole transcript.
- `output?` (`Record<string, unknown>`): Schema-enforced structured output (only when `opts.schema` is set).
- `sessionId` (`string`): Id of the session the run used.
- `status` (`HeadlessStatus`): How the run ended.
- `summary?` (`RunSummary`): Incident postmortem (errors, gate blocks, budget events) via run-summary.
- `transcript` (`SessionTurn[]`): Lossless transcript — raw `session.turns`. Thinking stripped when `includeThinking: false`.
- `turns` (`number`): Number of turns of the run's own loop.
- `usage` (`HeadlessUsage`): Token and cost totals, including child runs.

Source: [src/headless-format.ts:62](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L62)

### `HeadlessUsage`

Interface.

Token and cost totals of a headless run.

```ts
interface HeadlessUsage
```

**Properties**

- `cacheCreation` (`number`): Tokens written to the prompt cache.
- `cacheRead` (`number`): Tokens read from the prompt cache.
- `cost?` (`number`): Cumulative USD cost when the provider/registry could price the run.
- `input` (`number`): Input tokens, excluding cache tokens.
- `output` (`number`): Output tokens.

Source: [src/headless-format.ts:35](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L35)

### `OpenAIChatMessage`

Interface.

Chat-completion message in the OpenAI wire format.

```ts
interface OpenAIChatMessage
```

**Properties**

- `content` (`string | OpenAIChatContentPart[] | null`): Message text, `null` for a tool-call-only assistant message, or a list of content parts.
- `role` (`'system' | 'tool' | 'user' | 'assistant'`): Author of the message.
- `tool_call_id?` (`string`): Id of the tool call a `tool` message answers.
- `tool_calls?` (`{ function: { arguments: string; name: string }; id: string; type: 'function' }[]`): Tool calls requested by an assistant message.

Source: [src/headless-format.ts:214](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L214)

## Types

### `FormattedHeadlessResult`

Type.

Result of [`formatHeadlessResult`](https://zidane.si/docs/reference/headless.md#formatHeadlessResult): the [`HeadlessResult`](https://zidane.si/docs/reference/headless.md#HeadlessResult) itself, or the transcript as an array of provider messages.

```ts
type FormattedHeadlessResult = HeadlessResult | unknown[]
```

Source: [src/headless-format.ts:96](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L96)

### `FormattedHeadlessTurnEvent`

Type.

Result of [`formatHeadlessTurnEvent`](https://zidane.si/docs/reference/headless.md#formatHeadlessTurnEvent): the `turn` event itself, or its turn as an array of provider messages.

```ts
type FormattedHeadlessTurnEvent = Extract<HeadlessEvent, { type: 'turn' }> | unknown[]
```

Source: [src/headless-format.ts:101](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L101)

### `HeadlessEvent`

Type.

Live event union — the in-process equivalent of a `stream-json` line. Every
member is JSON-serializable; render to JSONL with [`headlessEventToJsonl`](https://zidane.si/docs/reference/headless.md#headlessEventToJsonl).

```ts
type HeadlessEvent =
  | { provider?: string; runId: string; type: 'start' }
  | { delta: string; type: 'text' }
  | { delta: string; type: 'thinking' }
  | { callId: string; input: Record<string, unknown>; name: string; type: 'tool_call' }
  | { callId: string; isError: boolean; name: string; output: string; type: 'tool_result' }
  | { index: number; turn: SessionTurn; type: 'turn' }
  | { event: 'before' | 'complete' | 'error'; id: string; info?: Record<string, unknown>; type: 'spawn' }
  | { errorType?: string; message: string; type: 'error' }
  | { result: HeadlessResult; type: 'result' }
```

Source: [src/headless-format.ts:124](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L124)

### `HeadlessOutputFormat`

Type.

Output shape for transcripts: `'zidane'` keeps zidane turns, `'provider'` converts them to the provider's message format.

```ts
type HeadlessOutputFormat = 'zidane' | 'provider'
```

Source: [src/headless-format.ts:90](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L90)

### `HeadlessStatus`

Type.

How a headless run ended. `'timeout'` means `timeoutMs` expired.

```ts
type HeadlessStatus = 'completed' | 'aborted' | 'error' | 'timeout'
```

Source: [src/headless-format.ts:32](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L32)

### `OpenAIChatContentPart`

Type.

One part of a multimodal OpenAI chat message.

```ts
type OpenAIChatContentPart =
  | { text: string; type: 'text' }
  | { image_url: { url: string }; type: 'image_url' }
  | { input_audio: { data: string; format: string }; type: 'input_audio' }
  | { type: 'video_url'; video_url: { url: string } }
```

Source: [src/headless-format.ts:226](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L226)

### `ProviderTranscriptFormat`

Type.

Provider message format a transcript can be converted to.

```ts
type ProviderTranscriptFormat = 'anthropic' | 'openai'
```

Source: [src/headless-format.ts:93](https://github.com/Tahul/zidane/blob/main/src/headless-format.ts#L93)
