# zidane/restate

> The Restate runtime for the agent loop, so model calls, tool calls and loop decisions are journaled.

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

Use this entry point for a server-side agent that must survive process restarts mid-run. `restateRuntime(ctx)` journals (records) each model request, tool call and loop decision as one step, so a replay reuses the results, and its default session store keeps the session in virtual-object state. Pass the provider and tools unwrapped. A tool call that a crash cut off is reported to the model as interrupted instead of run twice, unless the tool sets `replay: 'safe'`. Skip it for interactive sessions and for requests that finish inside one HTTP call. [Crash recovery](https://zidane.si/docs/durability.md) compares it with the SQLite runtime.

The second argument is a `RestateRuntimeOptions`. Its `promises` field takes a `RestateCombinators`, which the SDK's `RestatePromise` satisfies. With it, concurrent steps start together and awakeables can have a deadline. Without it, steps run one at a time.

```ts
import type { ObjectContext } from '@restatedev/restate-sdk'
import { RestatePromise } from '@restatedev/restate-sdk'
import { createAgent } from 'zidane'
import { anthropic } from 'zidane/providers/anthropic'
import { restateRuntime } from 'zidane/restate'
import { createSession, loadSession } from 'zidane/session'

declare const ctx: ObjectContext

const durable = restateRuntime(ctx, { promises: RestatePromise, idSeed: ctx.key })
const store = durable.stores.session
const session = await loadSession(store, ctx.key) ?? await createSession({ id: ctx.key, store })
const agent = createAgent({
  provider: anthropic(),
  durable,
  session,
  behavior: { cache: true, compactStrategy: 'tail', maxCostUsd: 5 },
})
const stats = await agent.run({ prompt: 'Triage the open alerts.' })
console.log(stats.turns)
```

## Functions

### `awakeableWithTimeout`

Function.

Mint an awakeable that resolves `{ timedOut: false, value }` with the
resumer's payload, or `{ timedOut: true }` when `timeoutMs` elapses
first. The id is journal-stable (send it out-of-band as usual); the
timeout is a durable `ctx.sleep`, so a parked invocation suspends
instead of holding a worker.

```ts
// waitBackground for a durable host, in three lines:
const { id, promise } = awakeableWithTimeout<TaskExitInfo>(ctx, { timeoutMs, promises: RestatePromise })
await registerExitResumer(taskId, id) // runner → ctx.resolveAwakeable(id, info)
const outcome = await promise
return outcome.timedOut ? null : outcome.value
```

```ts
function awakeableWithTimeout<T, TPromise extends Promise<unknown> = Promise<unknown>>(
  ctx: RestateContextLike,
  options?: AwakeableWithTimeoutOptions<TPromise>,
): RestateAwakeable<AwakeableOutcome<T>>
```

**Type parameters**

- `T`
- `TPromise` (`extends Promise<unknown>`, default `Promise<unknown>`)

**Parameters**

- `ctx` (`RestateContextLike`): Restate context that mints the awakeable and the timer.
- `options?` (`AwakeableWithTimeoutOptions<TPromise>`, default `{}`): Deadline and race combinator. See [`AwakeableWithTimeoutOptions`](https://zidane.si/docs/reference/restate.md#AwakeableWithTimeoutOptions).

**Returns** `RestateAwakeable<AwakeableOutcome<T>>`

Source: [src/restate/awakeable.ts:88](https://github.com/Tahul/zidane/blob/main/src/restate/awakeable.ts#L88)

### `createReplayTracker`

Function.

Create a per-invocation replay tracker. Pass it to `restateRuntime`
(and any `restateTool` / `restateWaitBackground`) via their
`replayTracker` option; consume it via `isReplaying()` / `onLive()` / [`replayAwareAbort`](https://zidane.si/docs/reference/restate.md#replayAwareAbort).

```ts
function createReplayTracker(options?: ReplayTrackerOptions): ReplayTracker
```

**Parameters**

- `options?` (`ReplayTrackerOptions`, default `{}`): Hooks and the `resuming` flag. See [`ReplayTrackerOptions`](https://zidane.si/docs/reference/restate.md#ReplayTrackerOptions).

**Returns** `ReplayTracker`

Source: [src/restate/replay.ts:148](https://github.com/Tahul/zidane/blob/main/src/restate/replay.ts#L148)

### `deterministicUuid`

Function.

RFC-4122-v4-shaped UUID derived from a seed string — pure function, no
RNG. Version/variant bits are set so consumers (and DB uuid columns)
treat it as a regular v4 UUID.

```ts
function deterministicUuid(seed: string): string
```

**Parameters**

- `seed` (`string`): String the UUID is derived from. The same seed gives the same UUID.

**Returns** `string`

Source: [src/restate/clock.ts:23](https://github.com/Tahul/zidane/blob/main/src/restate/clock.ts#L23)

### `isRestateCancellation`

Function.

The SDK's `CancelledError`: the invocation was cancelled (Admin API, a
caller's `ctx.cancel`, or a cancelled parent). Restate journals the cancel
signal, so a replay surfaces it at the same await.

```ts
function isRestateCancellation(error: unknown): boolean
```

**Parameters**

- `error` (`unknown`): Error caught around a durable await.

**Returns** `boolean`

Source: [src/restate/errors.ts:99](https://github.com/Tahul/zidane/blob/main/src/restate/errors.ts#L99)

### `isRestateControlError`

Function.

Restate control-flow errors must escape the agent loop. If they are turned
into model-visible tool results, the runtime can retry/suspend incorrectly.
`restateRuntime` answers `isControlError` with it.

Kept structural so `zidane/restate` does not need to import the SDK at
runtime just to recognize the common error shapes.

```ts
function isRestateControlError(error: unknown): boolean
```

**Parameters**

- `error` (`unknown`): Error caught around a tool or provider call.

**Returns** `boolean`: `true` for Restate control-flow errors that must propagate. Platform `DOMException`s are never control errors.

Source: [src/restate/errors.ts:14](https://github.com/Tahul/zidane/blob/main/src/restate/errors.ts#L14)

### `raceWithTimeout`

Function.

Race an already-minted awakeable promise against a durable
`ctx.sleep(timeoutMs)`. Split out from [`awakeableWithTimeout`](https://zidane.si/docs/reference/restate.md#awakeableWithTimeout) so a
caller that must run journaled steps between minting and parking (see
`restateWaitBackground`) can arm the race at park time: a
`RestatePromise.race` starts awaiting the SDK's single-waiter progress
channel as soon as it is built, so building it early and then awaiting a
`ctx.run` is two concurrent waiters — `awaitNext already pending`.

Without a positive `timeoutMs` no timer is armed and the promise only
gets tagged. With one, `options.promises` is required.

```ts
function raceWithTimeout<T, TPromise extends Promise<unknown> = Promise<unknown>>(
  ctx: RestateContextLike,
  promise: Promise<T>,
  options?: AwakeableWithTimeoutOptions<TPromise>,
): Promise<AwakeableOutcome<T>>
```

**Type parameters**

- `T`
- `TPromise` (`extends Promise<unknown>`, default `Promise<unknown>`)

**Parameters**

- `ctx` (`RestateContextLike`): Restate context that provides the timer.
- `promise` (`Promise<T>`): Awakeable promise to race.
- `options?` (`AwakeableWithTimeoutOptions<TPromise>`, default `{}`): Deadline and race combinator. See [`AwakeableWithTimeoutOptions`](https://zidane.si/docs/reference/restate.md#AwakeableWithTimeoutOptions).

**Returns** `Promise<AwakeableOutcome<T>>`

Source: [src/restate/awakeable.ts:115](https://github.com/Tahul/zidane/blob/main/src/restate/awakeable.ts#L115)

### `replayAwareAbort`

Function.

Wrap an abort function so it defers during journal replay.

Live invocation → `abort()` runs immediately, exactly like calling
`agent.abort()` directly. Replaying invocation → the abort is queued
and applied at the first live boundary (the start of the first step that
runs live: a model request, a tool body or a decision), where the resulting
`AgentAbortedError` + host catch-arm journaling append to the journal
instead of forking it mid-replay. Multiple calls during replay
collapse into one queued abort.

```ts
function replayAwareAbort(tracker: ReplayTracker, abort: () => void): () => void
```

**Parameters**

- `tracker` (`ReplayTracker`): Tracker that reports whether the invocation is replaying.
- `abort` (`() => void`): Function that aborts the run, for example `agent.abort()`.

**Returns** `() => void`

Source: [src/restate/replay.ts:206](https://github.com/Tahul/zidane/blob/main/src/restate/replay.ts#L206)

### `restateAwakeableTool`

Function.

Build a `ToolDef` that parks on a Restate awakeable until an external
resumer resolves it. See the module doc for the replay contract.

Do NOT additionally wrap the returned tool in `restateTool` — the
journal discipline is already internal, and an outer `ctx.run` would
illegally nest the suspension point inside a journaled closure.

```ts
function restateAwakeableTool<T = unknown>(
  options: RestateAwakeableToolOptions<T>,
): ToolDef
```

**Type parameters**

- `T` (default `unknown`)

**Parameters**

- `options` (`RestateAwakeableToolOptions<T>`): Tool spec, Restate context and the `notify` callback.

**Returns** `ToolDef`

Source: [src/restate/awakeable-tool.ts:130](https://github.com/Tahul/zidane/blob/main/src/restate/awakeable-tool.ts#L130)

### `restateControlError`

Function.

The Restate control error behind `error`: itself, or the first one on its
`.cause` chain. On `restateRuntime` the agent loop already rethrows
control errors unwrapped; this covers code outside the loop that wraps a
failure in an error of its own (cause = the original). Rethrow the
original, or Restate treats it as an ordinary, retryable failure.

```ts
function restateControlError(error: unknown): Error | undefined
```

**Parameters**

- `error` (`unknown`): Error that escaped the agent run.

**Returns** `Error | undefined`

Source: [src/restate/errors.ts:80](https://github.com/Tahul/zidane/blob/main/src/restate/errors.ts#L80)

### `restateRuntime`

Function.

The durable runtime of one Restate invocation. Build it inside the
handler, once per invocation, and hand it to the agent:

```ts
const durable = restateRuntime(ctx, { promises: RestatePromise, idSeed: ctx.key })
const store = durable.stores.session
const session = await loadSession(store, ctx.key) ?? await createSession({ id: ctx.key, store })
const agent = createAgent({ provider, tools, durable, session })
```

```ts
function restateRuntime(
  ctx: RestateContextLike,
  options?: RestateRuntimeOptions,
): DurableRuntime
```

**Parameters**

- `ctx` (`RestateContextLike`): The handler's Restate context. A virtual-object context also backs the default session and tool-state stores.
- `options?` (`RestateRuntimeOptions`, default `{}`): Combinators, id seed, clock coalescing and stores. See [`RestateRuntimeOptions`](https://zidane.si/docs/reference/restate.md#RestateRuntimeOptions).

**Returns** `DurableRuntime`

Source: [src/restate/runtime.ts:191](https://github.com/Tahul/zidane/blob/main/src/restate/runtime.ts#L191)

### `restateSessionStore`

Function.

Construct a `SessionStore` backed by the bound virtual-object's K/V
state. The store is single-tenant — every method ignores the
`sessionId` argument because the virtual object is already keyed by
`ctx.key`. Calling `appendTurns('other-id', …)` writes to the
CURRENT object's state, not the one named in the argument. Bind one
virtual-object key per session and the contract holds.

```ts
function restateSessionStore(
  ctx: RestateObjectContextLike,
  options?: RestateSessionStoreOptions,
): SessionStore
```

Construct a `SessionStore` backed by the bound virtual-object's K/V
state. The store is single-tenant — every method ignores the
`sessionId` argument because the virtual object is already keyed by
`ctx.key`. Calling `appendTurns('other-id', …)` writes to the
CURRENT object's state, not the one named in the argument. Bind one
virtual-object key per session and the contract holds.

**Parameters**

- `ctx` (`RestateObjectContextLike`): Virtual-object context whose K/V state holds the session.
- `options?` (`RestateSessionStoreOptions`): Clock override and read-only mode. See [`RestateSessionStoreOptions`](https://zidane.si/docs/reference/restate.md#RestateSessionStoreOptions).

**Returns** `SessionStore`

```ts
function restateSessionStore(
  ctx: Omit<RestateObjectContextLike, 'set' | 'clear'>,
  options: RestateSessionStoreOptions & { readOnly: true },
): SessionStore
```

Construct a `SessionStore` backed by the bound virtual-object's K/V
state. The store is single-tenant — every method ignores the
`sessionId` argument because the virtual object is already keyed by
`ctx.key`. Calling `appendTurns('other-id', …)` writes to the
CURRENT object's state, not the one named in the argument. Bind one
virtual-object key per session and the contract holds.

**Parameters**

- `ctx` (`Omit<RestateObjectContextLike, 'set' | 'clear'>`): Virtual-object context whose K/V state holds the session.
- `options` (`RestateSessionStoreOptions & { readOnly: true }`): Clock override and read-only mode. See [`RestateSessionStoreOptions`](https://zidane.si/docs/reference/restate.md#RestateSessionStoreOptions).

**Returns** `SessionStore`

**Example**

```ts
import type { RestateObjectContextLike } from 'zidane/restate'
import { createSession, loadSession } from 'zidane/session'
import { restateSessionStore } from 'zidane/restate'

declare const ctx: RestateObjectContextLike // the virtual object handler context

const store = restateSessionStore(ctx)
// The durable clock keeps the stale-'running' check replay-safe.
const loaded = await loadSession(store, ctx.key, { clock: { now: () => ctx.date.now() } })
// Exclusive handlers only (the key's sole writer): close tool calls a
// crashed attempt left open.
await loaded?.healInterruptedToolCalls()
const session = loaded ?? await createSession({ id: ctx.key, store })
```

Source: [src/restate/session.ts:139](https://github.com/Tahul/zidane/blob/main/src/restate/session.ts#L139)

### `restateSpawnTool`

Function.

Creates a `spawn` tool whose children run as separate Restate invocations. `dispatch` starts each child and the run joins the results with the runtime's own combinator.

```ts
function restateSpawnTool(options: RestateSpawnToolOptions): ToolDef
```

**Parameters**

- `options` (`RestateSpawnToolOptions`): Dispatch function and persistence and event settings. See [`RestateSpawnToolOptions`](https://zidane.si/docs/reference/restate.md#RestateSpawnToolOptions).

**Returns** `ToolDef`

**Example**

```ts
import type { ChildRunRequest, ChildRunResult } from 'zidane/restate'
import { restateSpawnTool } from 'zidane/restate'

// Start the child invocation, for example with `ctx.serviceClient(childService).runChild(req)`.
declare function startChild(req: ChildRunRequest): Promise<ChildRunResult>

const spawn = restateSpawnTool({ dispatch: req => startChild(req) })
```

Source: [src/restate/spawn.ts:299](https://github.com/Tahul/zidane/blob/main/src/restate/spawn.ts#L299)

### `restateTool`

Function.

Wrap a single `ToolDef` so its `execute` runs inside `ctx.run`. The
tool's `spec` and `isConcurrencySafe` pass through unchanged.

```ts
function restateTool<Token = unknown, Resumed = unknown>(
  inner: ToolDef,
  ctx: RestateContextLike,
  options?: RestateToolOptions<Token, Resumed>,
): ToolDef
```

**Type parameters**

- `Token` (default `unknown`)
- `Resumed` (default `unknown`)

**Parameters**

- `inner` (`ToolDef`): Tool to wrap.
- `ctx` (`RestateContextLike`): Restate context that journals each call.
- `options?` (`RestateToolOptions<Token, Resumed>`, default `{}`): Retry, naming and durable-start settings. See [`RestateToolOptions`](https://zidane.si/docs/reference/restate.md#RestateToolOptions).

**Returns** `ToolDef`

Source: [src/restate/tool.ts:284](https://github.com/Tahul/zidane/blob/main/src/restate/tool.ts#L284)

### `restateToolStateStore`

Function.

Build a [`ToolStateStore`](https://zidane.si/docs/reference/types.md#ToolStateStore) over the bound virtual object's state.

`restateRuntime(ctx)` builds one for its sessions (`durable.stores.toolState`),
which the agent uses by default; build your own to pick another `key`:

```ts
const agent = createAgent({
  provider,
  durable,
  session,
  toolState: restateToolStateStore(ctx, { key: 'my_tool_state' }),
})
```

Single-writer like the session store: the state belongs to the object key,
so bind one key per conversation and one agent per invocation.

Build it INSIDE the handler. The returned store closes over this `ctx`, so
an agent constructed once at module scope and reused across invocations
would commit through a context that is already finished, and would never
re-hydrate (the controller memoizes the load for the life of the agent).
Same rule as `restateSessionStore` / `restateRuntime`,
but easier to get wrong here, because `toolState` looks like
agent-lifetime configuration rather than a per-invocation binding.

The map is a schema you own: one JSON object, no versioning, no migration.
A value written by an earlier deploy is still there after you change its
shape, so either read tolerantly or move to a fresh `key`.

Enabling this adds a `ctx.get` at run start, which changes the journal
shape for NEW invocations. Existing in-flight invocations replay against
the old shape — drain them (or accept the failed attempts) before rolling
it out.

```ts
function restateToolStateStore(
  ctx: RestateObjectContextLike,
  options?: RestateToolStateStoreOptions,
): ToolStateStore
```

**Parameters**

- `ctx` (`RestateObjectContextLike`): Virtual-object context whose K/V state holds the tool state.
- `options?` (`RestateToolStateStoreOptions`, default `{}`): State key and value size limit. See [`RestateToolStateStoreOptions`](https://zidane.si/docs/reference/restate.md#RestateToolStateStoreOptions).

**Returns** `ToolStateStore`

Source: [src/restate/tool-state.ts:95](https://github.com/Tahul/zidane/blob/main/src/restate/tool-state.ts#L95)

### `restateWaitBackground`

Function.

Build a durable `ExecutionContext.waitBackground` — see the module doc for
the ordering contract. The returned function is the seam itself:
`(handle, taskId, options?) => Promise<TaskExitInfo | null>`.

```ts
function restateWaitBackground(
  options: RestateWaitBackgroundOptions,
): (handle: ExecutionHandle, taskId: string, opts?: { signal?: AbortSignal; timeoutMs?: number }) => Promise<TaskExitInfo | null>
```

**Parameters**

- `options` (`RestateWaitBackgroundOptions`): Context, task registry callbacks and timeout settings. See [`RestateWaitBackgroundOptions`](https://zidane.si/docs/reference/restate.md#RestateWaitBackgroundOptions).

**Returns** `(handle: ExecutionHandle, taskId: string, opts?: { signal?: AbortSignal; timeoutMs?: number }) => Promise<TaskExitInfo | null>`

Source: [src/restate/wait-background.ts:133](https://github.com/Tahul/zidane/blob/main/src/restate/wait-background.ts#L133)

## Constants

### `DEFAULT_TOOL_STATE_KEY`

Constant.

Default virtual-object state key holding the tool-state map.

```ts
const DEFAULT_TOOL_STATE_KEY = 'zidane_tool_state'
```

Source: [src/restate/tool-state.ts:41](https://github.com/Tahul/zidane/blob/main/src/restate/tool-state.ts#L41)

## Interfaces

### `AwakeableToolCombinators`

Interface.

The SDK `RestatePromise` statics the tool uses. Method syntax on purpose:
it keeps the SDK's generic `all<const T extends …>` signatures assignable,
so `promises: RestatePromise` typechecks with an explicit `<T>`.

```ts
interface AwakeableToolCombinators
```

**Properties**

- `all` (`(values: readonly Promise<unknown>[]) => Promise<unknown>`): Resolves with every value once all have settled.
- `any?` (`(values: readonly Promise<unknown>[]) => Promise<unknown>`): First fulfilled value; rejects only when every input rejects.
- `race` (`(values: readonly Promise<unknown>[]) => Promise<unknown>`): Resolves with the first of `values` to settle.

Source: [src/restate/awakeable-tool.ts:99](https://github.com/Tahul/zidane/blob/main/src/restate/awakeable-tool.ts#L99)

### `AwakeableWithTimeoutOptions`

Interface.

Options for [`awakeableWithTimeout`](https://zidane.si/docs/reference/restate.md#awakeableWithTimeout) and [`raceWithTimeout`](https://zidane.si/docs/reference/restate.md#raceWithTimeout).

```ts
interface AwakeableWithTimeoutOptions<TPromise extends Promise<unknown> = Promise<unknown>>
```

**Type parameters**

- `TPromise` (`extends Promise<unknown>`, default `Promise<unknown>`)

**Properties**

- `promises?` (`RestatePromiseRaceLike<TPromise>`): Durable race combinator — pass `RestatePromise` from the SDK.
  Required whenever `timeoutMs` is positive: the helpers throw instead
  of falling back to native `Promise.race`.
- `timeoutMs?` (`number`): Durable deadline in milliseconds. Unset / non-positive = no timeout
  (the returned awakeable is the plain `ctx.awakeable()` passthrough).

Source: [src/restate/awakeable.ts:37](https://github.com/Tahul/zidane/blob/main/src/restate/awakeable.ts#L37)

### `ChildRunRequest`

Interface.

What the parent hands a child invocation.

```ts
interface ChildRunRequest
```

**Properties**

- `depth` (`number`): Subagent depth of the CHILD (parent depth + 1). The handler is responsible
  for refusing runaway recursion — this tool always dispatches, because a
  short-circuit would have to return a non-durable promise and the durable
  fleet rejects those.
- `label` (`string`): Session-scoped label the parent reserved (`child-1`, `child-2`, …).
- `model?` (`string`): Model override requested by the model, if any.
- `parentCallId?` (`string`): Id of the parent tool call that spawned the child.
- `parentRunId?` (`string`): Identity of the work that spawned this child. Present when the parent has
  them; forward these to whatever sink the child publishes live events to so
  a UI can attribute and group them (the parent's own `child:*` events carry
  the same ids).
- `sessionId?` (`string`): Id of the parent session.
- `subagentType?` (`string`): Named subagent type requested by the model, if any.
- `task` (`string`): The child's prompt.

Source: [src/restate/spawn.ts:78](https://github.com/Tahul/zidane/blob/main/src/restate/spawn.ts#L78)

### `ChildRunResult`

Interface.

What a child invocation returns.

```ts
interface ChildRunResult
```

**Properties**

- `children?` (`readonly MergedChild[]`): Descendants this child already merged — see [`MergedChild`](https://zidane.si/docs/reference/restate.md#MergedChild). The parent
  merges them recursively, so a nested fan-out lands in the session with the
  right depths and emits `child:*` events at every level.
- `endedAt?` (`number`): End time on the child clock, as epoch milliseconds. See `startedAt`.
- `error?` (`string`): Failure detail when `status` is `'error'`.
- `startedAt?` (`number`): When the child started / finished, measured on the CHILD's clock (e.g.
  `await ctx.date.now()` around its run).

  Strongly recommended for durable hosts. These land in the merged
  `SessionRun`, which the parent persists through a journaled state write, so
  they must be replay-stable. Values measured inside the child's own
  invocation are: they arrive in the journaled call result. Omit them and the
  parent falls back to the child's first / last turn timestamps, then to its
  own run start.
- `stats` (`AgentStats`): Cumulative stats of the child run, including its own descendants.
- `status` (`'error' | 'completed' | 'aborted' | 'timeout'`): How the child ended.
- `text` (`string`): The child's final assistant text.
- `turns` (`SessionTurn[]`): The child's turns, in order. Merged into the parent's session under a
  `SessionRun` for this child, so the persisted tree matches what
  `createSpawnTool({ persist: true })` produces in-process.

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

### `InFlightChild`

Interface.

A child that was dispatched and has not settled yet.

```ts
interface InFlightChild
```

**Properties**

- `depth` (`number`): Subagent depth of the child.
- `label` (`string`): Session-scoped label, such as `child-1`.
- `task` (`string`): Task the child was given.

Source: [src/restate/spawn.ts:255](https://github.com/Tahul/zidane/blob/main/src/restate/spawn.ts#L255)

### `MergedChild`

Interface.

A descendant a child already merged, handed up so the top-level parent can
rebuild the whole subtree.

Why it exists: a child dispatched to a service runs sessionless, so it has
nowhere to write ITS children's turns. Without passing them up, a depth-2
fan-out loses the grandchildren entirely — the parent sees the child's `spawn`
tool calls and nothing of the work they did. Collect these from
`RestateSpawnToolOptions.onChildMerged` and return them as
`ChildRunResult.children`.

```ts
interface MergedChild
```

**Properties**

- `children?` (`readonly MergedChild[]`): Its own merged descendants, recursively.
- `depth` (`number`): Depth relative to the run that merged it (1 for its direct children).
- `endedAt?` (`number`): End time on the child clock, as epoch milliseconds.
- `label` (`string`): Label this level assigned. The merging parent re-labels from its own session counter.
- `startedAt?` (`number`): Start time on the child clock, as epoch milliseconds.
- `stats` (`AgentStats`): Cumulative stats of the child run.
- `status` (`'error' | 'completed' | 'aborted' | 'timeout'`): How the child ended.
- `task` (`string`): Task the child was given.
- `turns` (`SessionTurn[]`): The child turns, in order.

Source: [src/restate/spawn.ts:118](https://github.com/Tahul/zidane/blob/main/src/restate/spawn.ts#L118)

### `ReplayDoneHooks`

Interface.

Minimal structural slice of the agent's Hookable for `replay:done`.

```ts
interface ReplayDoneHooks
```

**Properties**

- `callHook` (`(name: 'replay:done', ctx: Record<string, never>) => unknown`): Fires the `replay:done` hook.

Source: [src/restate/replay.ts:71](https://github.com/Tahul/zidane/blob/main/src/restate/replay.ts#L71)

### `ReplayTracker`

Interface.

Tells whether an invocation is still replaying its journal and notifies when it goes live.

```ts
interface ReplayTracker
```

**Properties**

- `isReplaying` (`() => boolean`): `true` until the first live (non-journaled) execution of a step
  (model request, tool body, decision). See the module doc for the fresh-
  invocation caveat. Use this for journal-safety decisions (deferred
  abort) where the conservative answer is the correct one.
- `isResuming` (`() => boolean`): `true` only while ACTIVELY replaying a resumed journal — i.e. the
  invocation was constructed with `resuming: true` AND hasn't gone live
  yet. Always `false` on a fresh invocation (`resuming: false`).

  This is the probe for live-streaming consumers: gate emission on
  `!isResuming()` to suppress re-emitted deltas during a resume replay
  while still emitting the opening output of a fresh run. (Contrast
  `isReplaying`, which is conservatively `true` at the start of a
  fresh run too.)
- `markLive` (`() => void`): Mark the invocation live. Called by the runtime (and `restateTool` /
  `restateWaitBackground`) from inside their journaled closures: a closure
  only executes when the journal cursor is past it. Idempotent; only the
  first call transitions.
- `onLive` (`(cb: () => void) => () => void`): Register a callback for the replay→live transition. Fires once.
  When the tracker is already live, the callback runs synchronously
  before `onLive` returns. Returns an unregister function (no-op
  after firing).

Source: [src/restate/replay.ts:106](https://github.com/Tahul/zidane/blob/main/src/restate/replay.ts#L106)

### `ReplayTrackerOptions`

Interface.

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

```ts
interface ReplayTrackerOptions
```

**Properties**

- `hooks?` (`ReplayDoneHooks`): Agent hooks to notify at the replay→live transition. When set, the
  tracker fires `replay:done` exactly once, right after the `onLive`
  callbacks. Rejections are swallowed (surfaced under `ZIDANE_DEBUG`)
  — a misbehaving listener must not break the live execution that
  triggered the transition.
- `resuming?` (`boolean`): Whether THIS invocation is resuming a prior journal (a crash-recovery /
  resume replay) rather than running fresh. The host supplies it — it's the
  one bit the tracker can't infer on its own (before the first `ctx.run`
  settles, "fresh, not yet live" and "replaying a resumed journal" look
  identical from inside the runtime). Determine it from your own state: e.g.
  the bound object already has persisted session turns, or the SDK reports a
  non-first attempt.

  Powers `ReplayTracker.isResuming` — the probe streaming consumers
  should gate on (vs `ReplayTracker.isReplaying`, which stays
  conservative for the abort/journal-safety use case). On a fresh invocation
  (`resuming: false`, the default) `isResuming()` is `false` from the very
  start, so a streamer emits the opening deltas / start-step instead of
  suppressing them until the first journaled call settles.

Source: [src/restate/replay.ts:77](https://github.com/Tahul/zidane/blob/main/src/restate/replay.ts#L77)

### `RestateAwakeable`

Interface.

Awakeable handle returned by `RestateContextLike.awakeable`.

The SDK's full shape returns a `RestatePromise<T>`; we narrow to the
`Promise<T>` it extends because the adapter only awaits the result
(and a `RestatePromise<T>` is structurally assignable to `Promise<T>`).

```ts
interface RestateAwakeable<T>
```

**Type parameters**

- `T`

**Properties**

- `id` (`string`): Journal-stable id. Send it to whatever resolves the awakeable.
- `promise` (`Promise<T>`): Settles when the awakeable is resolved or rejected.

Source: [src/restate/types.ts:42](https://github.com/Tahul/zidane/blob/main/src/restate/types.ts#L42)

### `RestateAwakeableToolOptions`

Interface.

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

```ts
interface RestateAwakeableToolOptions<T = unknown>
```

**Type parameters**

- `T` (default `unknown`)

**Properties**

- `ctx` (`RestateContextLike`): The Restate context the awakeable + journal entries bind to.
- `description` (`string`): Model-facing tool description.
- `inputSchema` (`Record<string, unknown>`): JSON schema for the tool input.
- `isConcurrencySafe?` (`boolean`): Concurrency-safety flag passed through to the `ToolDef`. Defaults to
  `false` — a parked wait holding a scheduler slot should barrier.
- `name` (`string`): Canonical tool name (what the model calls).
- `notify` (`(info: { awakeableId: string; input: Record<string, unknown>; toolCtx: ToolContext }) => void | Promise<void>`): Send the awakeable id (plus the call's input) out-of-band so a
  resumer can later call `ctx.resolveAwakeable(awakeableId, value)`.
  Runs inside `ctx.run` — fires exactly once across replays.
- `promises?` (`AwakeableToolCombinators`): Durable combinators — pass `RestatePromise` from the SDK. Needed for
  `timeoutMs` (race) and for joining durable fleets (all).
- `runOptions?` (`RestateRunOptions`): `ctx.run` options for the notify entry. Defaults to
  `{ maxRetryAttempts: 1 }`, matching `restateTool`'s rationale.
- `timeoutMs?` (`number`): Durable park deadline in milliseconds (a journaled `ctx.sleep` raced
  against the awakeable). On timeout the call fails with a tool error.
  Requires `promises`. Unset / non-positive = park until resolved.
- `toResult?` (`(value: T, input: Record<string, unknown>) => string | ToolResultContent[]`): Map the resolved awakeable payload to the tool result. Default:
  strings pass through, everything else is JSON-stringified.

Source: [src/restate/awakeable-tool.ts:46](https://github.com/Tahul/zidane/blob/main/src/restate/awakeable-tool.ts#L46)

### `RestateCombinators`

Interface.

The SDK `RestatePromise` statics the runtime uses. Method syntax on
purpose: it keeps the SDK's generic signatures assignable, so
`promises: RestatePromise` typechecks.

```ts
interface RestateCombinators
```

**Properties**

- `all` (`(values: readonly Promise<unknown>[]) => Promise<unknown>`): Resolves with every value once all have settled.
- `allSettled?` (`(values: readonly Promise<unknown>[]) => Promise<unknown>`): Settles once every value has settled.
- `race` (`(values: readonly Promise<unknown>[]) => Promise<unknown>`): Resolves with the first of `values` to settle.

Source: [src/restate/runtime.ts:80](https://github.com/Tahul/zidane/blob/main/src/restate/runtime.ts#L80)

### `RestateContextLike`

Interface.

Structural subset of `restate.Context` covering everything the
adapter needs.

`run` journals one side effect; on replay the journaled value returns
without re-executing `fn`. `date.now()` is async because the SDK
routes it through `ctx.run` internally — replay returns the journaled
timestamp. `rand.uuidv4()` is sync because the SDK seeds an in-memory
RNG from the invocation id, which is itself replay-stable.

`awakeable<T>()` mints a journal-stable id paired with a pending
promise. The id is sent out-of-band (webhook, queue, collab stream)
and a separate handler resumes the wait via
`ctx.resolveAwakeable(id, …)` / `ctx.rejectAwakeable(id, …)`. The
adapter never resolves awakeables directly; consumers driving
human-in-the-loop tools (`ask_user`, `request_review`, …) do that
from the resumer side. Mirrors `@restatedev/restate-sdk` Context —
the real SDK accepts an optional `Serde<T>` we omit because the
adapter doesn't exercise it.

```ts
interface RestateContextLike
```

**Properties**

- `awakeable` (`<T>() => RestateAwakeable<T>`): Creates an awakeable: a journal-stable id and a promise that waits for an outside resolver.
- `date` (`{ now: () => Promise<number> }`): Durable clock. `now()` resolves to epoch milliseconds, the same on replay.
- `rand` (`{ uuidv4: () => string }`): Deterministic random source seeded from the invocation id. `uuidv4()` gives the same ids on replay.
- `resolveAwakeable?` (`<T>(id: string, payload?: T) => void`): Resolve another invocation's awakeable (the resumer side). Optional:
  only a resumer needs it, see `restateRuntime`'s `resolveAwakeable`.
- `run` (`<T>(name: string, fn: () => T | Promise<T>, options?: RestateRunOptions) => Promise<T>`): Journals the result of one side effect. On replay the journaled value is returned and `fn` does not run again.
- `sleep` (`(millis: number) => Promise<void>`): Durable timer — suspends the invocation for `millis` and survives
  process death (replay resumes the same timer). Mirrors the SDK's
  `Context.sleep(duration)`; we narrow the parameter to plain millis
  (the SDK also accepts a `Duration`, which a wider implementation
  signature satisfies structurally).

  What it's for in this adapter: durable timeouts. Racing
  `awakeable.promise` against `sleep(timeoutMs)` is the canonical
  "wait with a deadline" shape — see [`awakeableWithTimeout`](https://zidane.si/docs/reference/restate.md#awakeableWithTimeout),
  which packages exactly that race for `waitBackground`-style parks.

Source: [src/restate/types.ts:69](https://github.com/Tahul/zidane/blob/main/src/restate/types.ts#L69)

### `RestateDurableStart`

Interface.

Opt-in two-phase journaling for long-running, re-attachable tools.

The default `restateTool` wrapping journals only the COMPLETED result —
a crash mid-execute re-runs the tool on replay, which is unacceptable
for builds / migrations / deploys. `durableStart` splits the execution:

1. `start` runs inside its own `ctx.run` entry, journaled BEFORE the
   body — it kicks off the side effect and returns a re-attach token
   (a background task id, a job handle, an idempotency key).
2. the wait — either `attach` (one `ctx.run` holding the wait in the
   result entry; only for short, bounded waits) or `wait` (awakeable
   park: register a resumer in a journaled entry, suspend outside
   the journal, synthesize the result in a final entry). On replay
   both receive the SAME token and re-attach to the already-running
   work instead of starting it again.

The wrapped tool's own `execute` is bypassed; the start + wait pair IS
the execution.

```ts
interface RestateDurableStart<Token = unknown, Resumed = unknown>
```

**Type parameters**

- `Token` (default `unknown`)
- `Resumed` (default `unknown`)

**Properties**

- `attach?` (`(token: Token, input: Record<string, unknown>, toolCtx: ToolContext) => Promise<string | ToolResultContent[]>`): Await / re-attach to the work identified by `token` and produce the
  tool result. Runs inside ONE `ctx.run` entry, so the body must
  settle within the runtime's attempt lifecycle — a long busy-poll in
  here starves the journal, trips the server's inactivity/abort
  timeouts, and each retry restarts the wait from scratch. Use this
  only for waits bounded well below the inactivity timeout (or as the
  degraded mode on hosts without a push seam); prefer `wait`
  for anything long-running.

  Required unless `wait` is provided; ignored when both are set.
- `onAbort?` (`(token: Token, input: Record<string, unknown>, toolCtx: ToolContext) => void | Promise<void>`): Cleanup for a job nobody will wait for. Without it, the journaled token
  identifies a remote job that keeps running with nobody attached — a
  leak for hosts whose jobs outlive the session. Two triggers:

  - **Restate cancellation** while parked on the awakeable (Admin API
    cancel, a cancelled parent, `bun start --restate` Ctrl-C). Runs
    inside a journaled `<entry>-abort` `ctx.run`, then the
    `CancelledError` propagates and ends the invocation. Restate journals
    the cancel, so a replay of that attempt gets here again, and the
    entry makes the cleanup run once across attempts.
  - **The per-call abort signal** (run abort, `agent.cancelTool`,
    sibling-cascade cancel, `agent.destroy()` mid-run), during `attach`
    or the park. Fires outside the journal, best effort, and does NOT
    stop the park: the signal is live state a replay doesn't see, so
    un-parking on it would take a path the journal can't reproduce.
    Kill the job here; its exit resolves the awakeable (journaled), or
    cancel the invocation to stop waiting.

  Contract:

  - At most once per attempt; may run again on a replay that did not
    complete the journaled entry. Make it idempotent against "job
    already gone".
  - May run concurrently with a still-unwinding `attach`.
  - NOT called on process death — that's not termination: the
    journaled token re-attaches on replay instead.
  - Errors are swallowed (surfaced under `ZIDANE_DEBUG`) — cleanup
    failure must not mask the abort itself.
- `start` (`(input: Record<string, unknown>, toolCtx: ToolContext) => Token | Promise<Token>`): Kick off the side effect; journaled before `attach`. Must be cheap + idempotent-safe up to journal persistence.
- `wait?` (`RestateDurableWait<Token, Resumed>`): Awakeable-based completion wait — the durable alternative to
  `attach` for long-running work on hosts with a push seam
  (e.g. a runner that fires `task:exit` → resumer → `ctx.resolveAwakeable`).
  `executeTwoPhase` orchestrates three phases with the same journal
  discipline as `restateAwakeableTool`:

  1. `register` — a short, journaled `ctx.run`: hand the awakeable
     id to the host as the exit resumer for `token`. Fires exactly
     once across replays (marker invariant included).
  2. park — `await awakeable.promise`, OUTSIDE any `ctx.run`. The
     invocation suspends cleanly: no held worker, immune to
     inactivity/abort timeouts. With `timeoutMs`, the park is a
     durable race against `ctx.sleep` (replay-stable — no
     `Date.now()` deadline that resets per attempt).
  3. `result` — a final journaled `ctx.run`: synthesize the tool
     result from the resumer payload (`null` = timeout; kill the
     job and shape the timeout result here).

Source: [src/restate/tool.ts:61](https://github.com/Tahul/zidane/blob/main/src/restate/tool.ts#L61)

### `RestateDurableWait`

Interface.

Awakeable-based completion wait for `RestateDurableStart.wait`.

Replay contract (mirrors `restateAwakeableTool`):

- The awakeable is minted BEFORE the journaled `register` entry, so on
  replay both come back in the same order with the same id — a replayed
  call re-parks on the SAME awakeable without re-registering.
- The journaled marker records the awakeable id it was registered for;
  a mismatch on replay (journal drift) throws instead of parking on an
  id nobody will ever resolve.

```ts
interface RestateDurableWait<Token = unknown, Resumed = unknown>
```

**Type parameters**

- `Token` (default `unknown`)
- `Resumed` (default `unknown`)

**Properties**

- `inline?` (`(token: Token, input: Record<string, unknown>, toolCtx: ToolContext) => boolean`): Per-call fast path: when this returns `true` for the journaled
  `token`, the awakeable park is SKIPPED entirely — no awakeable is
  minted, no `-register` entry is journaled, and the invocation never
  suspends. `result` is invoked directly with `resumed = null` inside
  the final result entry.

  This is for `start` phases that sometimes have nothing to wait on
  (e.g. a shell tool that only spawns a real background task above a
  duration threshold and otherwise runs the command inline in
  `result`). Parking such calls pays a full suspend → resolve →
  re-invoke → replay round-trip for nothing.

  MUST be a pure function of the journaled `token` (+ input) so the
  decision is identical across replays — a token-derived branch keeps
  the journal entry shape deterministic. When omitted, every call
  parks (the default).
- `promises?` (`RestatePromiseRaceLike<Promise<unknown>>`): Durable race combinator for the timeout race — pass `RestatePromise`
  from the SDK. Required when `timeoutMs` resolves to a positive value
  (see `awakeableWithTimeout`).
- `register` (`(token: Token, awakeableId: string, input: Record<string, unknown>, toolCtx: ToolContext) => void | Promise<void>`): Register `awakeableId` as the exit resumer for the work identified
  by `token` — the host must call `ctx.resolveAwakeable(awakeableId,
  payload)` when the work completes. Runs inside `ctx.run`; must be
  short and idempotent-safe up to journal persistence.
- `result` (`(token: Token, resumed: Resumed | null, input: Record<string, unknown>, toolCtx: ToolContext) => string | ToolResultContent[] | Promise<string | ToolResultContent[]>`): Synthesize the tool result once the park settles. `resumed` is the
  resumer's payload, or `null` when `timeoutMs` elapsed first — kill
  the still-running job and shape the timeout result in that branch.
  Runs inside the final journaled `ctx.run` (the result entry).
- `timeoutMs?` (`number | ((input: Record<string, unknown>, toolCtx: ToolContext) => number | undefined)`): Durable park deadline in milliseconds — becomes the
  `awakeableWithTimeout` duration (a `ctx.sleep` raced against the
  awakeable, replay-stable). A function receives the call's input so
  the model's own `timeout` parameter can drive it. Unset /
  non-positive = park indefinitely.

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

### `RestateObjectContextLike`

Interface.

Structural subset of `restate.ObjectContext` — adds keyed K/V state
scoped to the virtual-object instance the handler is bound to.

`get` returns `null` for missing keys (SDK contract). `set` returns
void; the SDK journals the mutation transparently.

```ts
interface RestateObjectContextLike extends RestateContextLike
```

**Properties**

- `awakeable` (`<T>() => RestateAwakeable<T>`, from `RestateContextLike`): Creates an awakeable: a journal-stable id and a promise that waits for an outside resolver.
- `clear` (`(name: string) => void`): Removes a state key.
- `date` (`{ now: () => Promise<number> }`, from `RestateContextLike`): Durable clock. `now()` resolves to epoch milliseconds, the same on replay.
- `get` (`<T>(name: string) => Promise<T | null>`): Reads a state value. Resolves to `null` when the key is missing.
- `key` (`string`): Key of the virtual-object instance the handler is bound to.
- `rand` (`{ uuidv4: () => string }`, from `RestateContextLike`): Deterministic random source seeded from the invocation id. `uuidv4()` gives the same ids on replay.
- `resolveAwakeable?` (`<T>(id: string, payload?: T) => void`, from `RestateContextLike`): Resolve another invocation's awakeable (the resumer side). Optional:
  only a resumer needs it, see `restateRuntime`'s `resolveAwakeable`.
- `run` (`<T>(name: string, fn: () => T | Promise<T>, options?: RestateRunOptions) => Promise<T>`, from `RestateContextLike`): Journals the result of one side effect. On replay the journaled value is returned and `fn` does not run again.
- `set` (`<T>(name: string, value: T) => void`): Writes a state value. The SDK journals the change.
- `sleep` (`(millis: number) => Promise<void>`, from `RestateContextLike`): Durable timer — suspends the invocation for `millis` and survives
  process death (replay resumes the same timer). Mirrors the SDK's
  `Context.sleep(duration)`; we narrow the parameter to plain millis
  (the SDK also accepts a `Duration`, which a wider implementation
  signature satisfies structurally).

  What it's for in this adapter: durable timeouts. Racing
  `awakeable.promise` against `sleep(timeoutMs)` is the canonical
  "wait with a deadline" shape — see [`awakeableWithTimeout`](https://zidane.si/docs/reference/restate.md#awakeableWithTimeout),
  which packages exactly that race for `waitBackground`-style parks.

Source: [src/restate/types.ts:116](https://github.com/Tahul/zidane/blob/main/src/restate/types.ts#L116)

### `RestatePromiseRaceLike`

Interface.

Structural subset of the SDK's `RestatePromise` static surface used by
[`awakeableWithTimeout`](https://zidane.si/docs/reference/restate.md#awakeableWithTimeout). Pass `RestatePromise` from `@restatedev/restate-sdk` so
the race goes through the journaled combinator. Native `Promise.race`
over durable promises is not supported (see the "no native combinators"
rule in docs/RESTATE.md). Method syntax on purpose, as in
`RestateCombinators`: it keeps the SDK's generic `race` assignable, so
`promises: RestatePromise` typechecks.

```ts
interface RestatePromiseRaceLike<TPromise extends Promise<unknown> = Promise<unknown>>
```

**Type parameters**

- `TPromise` (`extends Promise<unknown>`, default `Promise<unknown>`)

**Properties**

- `race` (`(values: readonly TPromise[]) => Promise<unknown>`): Resolves with the first of `values` to settle.

Source: [src/restate/awakeable.ts:30](https://github.com/Tahul/zidane/blob/main/src/restate/awakeable.ts#L30)

### `RestateRunOptions`

Interface.

Subset of `restate.Context.run` used by the adapter.
The SDK's full signature accepts a richer options bag; this captures
what the adapter exercises.

```ts
interface RestateRunOptions
```

**Properties**

- `initialRetryInterval?` (`unknown`): Initial delay before the first retry.
- `maxRetryAttempts?` (`number`): Max retry attempts before surfacing a terminal error.
- `maxRetryDuration?` (`unknown`): Max retry duration before surfacing a terminal error.
- `maxRetryInterval?` (`unknown`): Maximum delay between retries.
- `retryIntervalFactor?` (`number`): Exponential factor applied to retry intervals.
- `serde?` (`unknown`): Optional SDK serde passthrough for callers that need custom journal encoding.

Source: [src/restate/types.ts:20](https://github.com/Tahul/zidane/blob/main/src/restate/types.ts#L20)

### `RestateRuntimeOptions`

Interface.

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

```ts
interface RestateRuntimeOptions
```

**Properties**

- `coalesce?` (`boolean | { reanchorEvery?: number }`): Coalesce `now()`: journal a real `ctx.date.now()` anchor every
  `reanchorEvery` calls (`true`: every 8) and derive a monotonic sequence
  in between, cutting the suspensions a journaled `now()` costs.
  Timestamps inside a window are logical (ordered, near the anchor), not
  wall-accurate. Default: every `now()` is journaled.
- `idSeed?` (`string`): Entropy of the `uuid()` sequence: pass a per-invocation stable value
  (the virtual object key, a workflow id) and construction draws nothing
  from `ctx.rand`. Default: one `ctx.rand.uuidv4()` draw at construction,
  which is stable as long as the runtime is built at a deterministic point
  of the handler (before any work that draws a varying number of times).
  Ids are hashed from `(idSeed, counter)`, never drawn from `ctx.rand`
  per call: other code sharing `ctx.rand` can draw a different number of
  times on a replay, which would shift every later id.
- `promises?` (`RestateCombinators`): Durable combinators: pass `RestatePromise` from `@restatedev/restate-sdk`.
  With them, concurrent steps (a batch of concurrency-safe tool calls)
  start together and park once, and awakeables can have a deadline.
  Without them, steps run one at a time (concurrent native awaits of
  Restate promises fail the attempt with `awaitNext already pending`).
- `replayTracker?` (`ReplayTracker`): Per-invocation replay tracker (`createReplayTracker`): marked live the
  first time a step's handler actually runs instead of replaying.
- `resolveAwakeable?` (`(id: string, value: JsonValue) => void | Promise<void>`): Resolve an awakeable of another invocation (an ingress client, e.g.
  `(id, value) => ingress.resolveAwakeable(id, value)`). Default: the
  context's own `ctx.resolveAwakeable`, which a resumer handler has.
- `runOptions?` (`Partial<Record<StepKind, RestateRunOptions>>`): `ctx.run` options of the step entries, by step kind (`'model'`,
  `'tool'`, `'decision'`, …). Default `{ maxRetryAttempts: 1 }`: a
  failure is recorded as the step's value, so these only matter for a
  handler that throws Restate's `RetryableError`, which escapes to the
  SDK's retry policy when these allow another attempt.
- `sessionStore?` (`SessionStore`): Where sessions live. Default: `restateSessionStore(ctx)` (the bound
  virtual object's state) on an object context, else an in-memory store
  (the journal still replays the run; nothing outlives the invocation).

Source: [src/restate/runtime.ts:91](https://github.com/Tahul/zidane/blob/main/src/restate/runtime.ts#L91)

### `RestateSessionStoreOptions`

Interface.

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

```ts
interface RestateSessionStoreOptions
```

**Properties**

- `clock?` (`AgentClock`): Time source for the journaled header timestamps (`createdAt` /
  `updatedAt`) the store stamps in `save()`, `saveMeta()` and `updateStatus()`.

  Defaults to `ctx.date.now()`: one journaled round-trip per call.
  `restateRuntime` passes its own clock, so with `coalesce` these
  finalize-time timestamps ride the coalesced anchor instead of each
  spending their own `ctx.date.now()`; on a suspend-per-round-trip (FaaS)
  host that's \~2 fewer suspend/replay cycles per run. Whatever you pass MUST
  be replay-stable: the value lands in persisted state via `ctx.set`, so a
  drifting clock would corrupt the journal on replay.
- `readOnly?` (`boolean`): Never write state. Required when the store is built from a SHARED
  handler's context (`restate.handlers.object.shared`), which Restate
  forbids from writing: reads of a legacy `session-turns` object are served
  without migrating it, and every mutating method throws.

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

### `RestateSpawnToolOptions`

Interface.

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

```ts
interface RestateSpawnToolOptions
```

**Properties**

- `description?` (`string`): Override the tool description shown to the model.
- `dispatch` (`(req: ChildRunRequest, ctx: ToolContext) => Promise<ChildRunResult>`): Start the child invocation and return the runtime's promise **untouched**
  (e.g. `ctx.serviceClient(childService).runChild(req)`). Do not wrap it in
  `then` / `catch` / `race`, and do not `await` it here.
- `emitChildEvents?` (`boolean`): Replay each finished child's transcript onto the parent's hook bus as
  `child:*` events, so hosts already rendering subagent activity work
  unchanged. Default: `true`.

  These are NOT live — hook bubbling cannot cross an invocation boundary, so
  they fire when the child finishes, in submission order. For live output,
  publish from the child's own bus inside your handler and correlate with the
  `parentRunId` / `parentCallId` / `sessionId` carried in
  [`ChildRunRequest`](https://zidane.si/docs/reference/restate.md#ChildRunRequest); see `docs/RESTATE.md` § Parallel subagents.

  They re-fire on a parent replay, exactly as the in-process tool's
  `child:tool:*` does — dedupe by `callId` / `turnId`, or gate on
  `createReplayTracker`.
- `onAbort?` (`(inFlight: readonly InFlightChild[], ctx: ToolContext) => void`): Called once when the parent aborts with children still in flight. Wire it
  and an aborted parent stops its children instead of orphaning them.

  The practical implementation is to cancel the parent's OWN invocation:

  ```ts
  onAbort: () => ctx.invocation(ctx.request().id).cancel()
  ```

  Restate cancels the whole call tree, so this reaches children at any depth
  and needs no per-child bookkeeping. `ctx.request().id` is available
  synchronously and is constant across retries and suspensions. The cost is
  that the parent invocation ends too — an in-agent abort becomes "stop
  everything", so persist whatever summary you need BEFORE aborting.

  Why not per-child ids: the `invocationId` on the runtime's call handle is
  itself a journal-backed promise (`SingleRestatePromise`, see
  `@restatedev/restate-sdk` `context_impl.js`), so reading it while the call
  is in flight registers a second consumer on the SDK's single-consumer
  progress channel — `awaitNext already pending`, a failed attempt, and a
  replay on every fan-out. Verified against a real server. Per-child
  cancellation needs the two-phase (send → id → attach) tool shape, where the
  id is consumed before the result promise exists.

  Timing: cancelling appends a journal command, so it must not happen during
  a replay. Drive aborts through `replayAwareAbort` (see `./replay`), which
  defers an external abort to the first live boundary.
- `onChildMerged?` (`(merged: MergedChild, ctx: ToolContext) => void`): Called for each child as it merges, with the subtree it brought along.

  Sessionless agents (a child dispatched to a service) need this: they have
  nowhere to persist their own children, so they collect here and hand the
  result up as `ChildRunResult.children`. Without it a nested fan-out
  loses everything below depth 1.
- `persist?` (`boolean`): Merge each child's turns and run record into the parent's session.
  Default: `true` when the parent has a session.

  Ordering nuance versus the in-process tool: the merge runs on
  `tool-results:after`, so a child's turns are appended just AFTER the
  parent's tool-result turn rather than just before it. Their `createdAt`
  values still order them correctly for consumers that sort chronologically.

Source: [src/restate/spawn.ts:178](https://github.com/Tahul/zidane/blob/main/src/restate/spawn.ts#L178)

### `RestateToolOptions`

Interface.

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

```ts
interface RestateToolOptions<Token = unknown, Resumed = unknown>
```

**Type parameters**

- `Token` (default `unknown`)
- `Resumed` (default `unknown`)

**Properties**

- `coerceResultBlocks?` (`boolean`): Collapse malformed structured result blocks (`{ type: 'image' }` with no
  payload, foreign block shapes) to a text placeholder before the result is
  journaled, so a downstream provider wire serializer never has to project an
  unprojectable block (which, under Restate, would throw inside the journaled
  provider call and become fatal). Well-formed results pass through
  untouched. Applies to the single-phase journaling path.

  Default: `true`.
- `detachedTasks?` (`DetachedTasksCapability`): `detachedTasks` capability of the execution context the agent will
  run with (see `ContextCapabilities.detachedTasks` /
  `resolveDetachedTasksCapability`). Drives the
  `disableShellBackground` default: a `'durable'` context keeps its
  background tasks across worker crashes, so the wrapper leaves the
  shell tool's background mode intact.

  Default: `undefined`, treated as non-durable (background disabled).
- `disableShellBackground?` (`boolean`): Hide and reject the built-in shell tool's background mode.

  Default: derived from `RestateToolOptions.detachedTasks` —
  disabled unless the execution context declares `'durable'` detached
  tasks. An explicit boolean always wins.
- `durableStart?` (`RestateDurableStart<Token, Resumed>`): Two-phase start/attach journaling for crash-safe long tools — see
  [`RestateDurableStart`](https://zidane.si/docs/reference/restate.md#RestateDurableStart). Per-tool by nature.
- `entryName?` (`(seq: number, name: string, input: Record<string, unknown>, callId?: string) => string`): Override the journal-entry name. Receives the canonical tool name, the
  call's input, and the model's `callId` (when the runtime provides one).
  Default: `tool-<name>-<seq>` where `<seq>` is a per-wrapper monotonic
  counter. The counter is replay-stable as long as dispatch order is
  deterministic; key on `callId` instead for a model-deterministic name
  that's robust even under `maxConcurrentTools > 1`.
- `replayTracker?` (`ReplayTracker`): Per-invocation replay tracker (see `createReplayTracker`). When set,
  the wrapper marks it live from inside its journaled closures — i.e.
  the first time a tool body (or a `durableStart.start` phase)
  ACTUALLY executes rather than replaying from the journal. Pass the
  same tracker as `restateRuntime({ replayTracker })` of the handler
  invocation.
- `runOptions?` (`RestateRunOptions`): `ctx.run` options forwarded on every wrapped tool call. Defaults to
  `{ maxRetryAttempts: 1 }` — tool errors are typically deterministic
  (validation failures, missing files) so blind retries waste budget.
  Override per-tool for network-heavy custom tools that benefit from
  SDK-level retries.
- `softErrors?` (`boolean`): Keep an ordinary, recoverable tool throw (a failed shell command, a
  transport error, an MCP `isError`) NON-fatal under Restate.

  Without this, the throw rides out of the journaled `ctx.run` action,
  exhausts the retry budget, and surfaces as a Restate `TerminalError` —
  which the runtime treats as a control error, aborting the
  whole run. With it, the throw is captured INSIDE the journal as a sentinel
  value and re-thrown as a plain (non-control) error OUTSIDE it, so the loop
  produces a model-visible `tool_result { isError }` and continues —
  identical to the non-Restate soft-error model. Real Restate control
  errors are never softened; an abort is rethrown as an `AbortError`.

  Applies to the single-phase journaling path. The two-phase
  `RestateToolOptions.durableStart` mode owns its own start/wait
  error shape and is unaffected.

  Default: `true`.

Source: [src/restate/tool.ts:191](https://github.com/Tahul/zidane/blob/main/src/restate/tool.ts#L191)

### `RestateToolStateStoreOptions`

Interface.

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

```ts
interface RestateToolStateStoreOptions
```

**Properties**

- `key?` (`string`): Override the state key. Defaults to [`DEFAULT_TOOL_STATE_KEY`](https://zidane.si/docs/reference/restate.md#DEFAULT_TOOL_STATE_KEY).
- `maxValueBytes?` (`number`): Per-value size budget enforced when a tool stages a write. Defaults to
  the library's `DEFAULT_MAX_VALUE_BYTES` (64 KiB), which is far below
  Restate's own journal-entry ceiling on purpose: an oversized state value
  doesn't fail politely there — the invocation fails with `RT0003`
  (MessageSizeLimit) and is retried under the configured policy, i.e. a fat
  write becomes a poison pill rather than a dropped one.

Source: [src/restate/tool-state.ts:44](https://github.com/Tahul/zidane/blob/main/src/restate/tool-state.ts#L44)

### `RestateWaitBackgroundOptions`

Interface.

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

```ts
interface RestateWaitBackgroundOptions
```

**Properties**

- `ctx` (`RestateContextLike`): The Restate context the awakeable + journal entries bind to.
- `entryPrefix?` (`string`): Journal-entry prefix. Default `wait-background`.
- `probe` (`(handle: ExecutionHandle, taskId: string) => readonly TaskEntry[] | Promise<readonly TaskEntry[]>`): Snapshot the host's task registry (the `listBackground` shape). Called
  AFTER `register`, inside a journaled `ctx.run`. The helper finds `taskId`
  in the result: terminated → fast-path return, running → park, absent →
  `null`.
- `promises?` (`RestatePromiseRaceLike<Promise<unknown>>`): Durable race combinator for the timeout race — pass `RestatePromise` from
  the SDK. Required when a timeout applies (`timeoutMs` here or per call);
  see [`raceWithTimeout`](https://zidane.si/docs/reference/restate.md#raceWithTimeout).
- `register` (`(awakeableId: string, taskId: string, handle: ExecutionHandle) => void | Promise<void>`): Persist `awakeableId` as the exit resumer for `taskId` BEFORE the probe —
  the host's task-exit path must later call `ctx.resolveAwakeable(awakeableId,
  exitInfo)`. Runs inside a journaled `ctx.run`; keep it short and
  idempotent-safe up to journal persistence.
- `replayTracker?` (`ReplayTracker`): Per-invocation replay tracker (see `createReplayTracker`).
- `runOptions?` (`RestateRunOptions`): `ctx.run` options for the journaled phases. Defaults to `{ maxRetryAttempts: 1 }`.
- `settle?` (`(awakeableId: string, taskId: string, exit: TaskExitInfo | null, handle: ExecutionHandle) => void | Promise<void>`): Finalize the resumer row once the wait resolves — `exit` is the task's
  [`TaskExitInfo`](https://zidane.si/docs/reference/contexts.md#TaskExitInfo), or `null` on timeout / unknown task. Runs
  inside a journaled `ctx.run`. Optional.
- `timeoutMs?` (`number`): Default park deadline in milliseconds. A per-call `options.timeoutMs`
  (what the `wait_task` tool passes) wins. Unset / non-positive = park until
  the resumer fires or the invocation is cancelled.

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

## Types

### `RestatePromiseLike`

Type.

A promise that also exposes the SDK `map` combinator (`RestatePromise`).

```ts
type RestatePromiseLike<T> = Promise<T> & { map: <U>(mapper: (value?: T, failure?: unknown) => U) => Promise<U> }
```

**Type parameters**

- `T`

Source: [src/restate/types.ts:105](https://github.com/Tahul/zidane/blob/main/src/restate/types.ts#L105)

## Re-exports

These names are documented on the entry point that owns them.

- [zidane/durable](https://zidane.si/docs/reference/durable.md): [`AwakeableOutcome`](https://zidane.si/docs/reference/durable.md#AwakeableOutcome)
