zidane/ restate
The Restate runtime for the agent loop, so model calls, tool calls and loop decisions are journaled.
import { … } from 'zidane/restate'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 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.
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#
awakeableWithTimeoutFunction#
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.
// 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
function awakeableWithTimeout<T, TPromise extends Promise<unknown> = Promise<unknown>>(
ctx: RestateContextLike,
options?: AwakeableWithTimeoutOptions<TPromise>,
): RestateAwakeable<AwakeableOutcome<T>>Type parameters
| Name | Type | Default | Description |
|---|---|---|---|
| T | |||
| TPromise | extends Promise<unknown> | Promise<unknown> |
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
| ctx | RestateContextLike | Restate context that mints the awakeable and the timer. | |
| options? | AwakeableWithTimeoutOptions<TPromise> | {} | Deadline and race combinator. See |
Returns
createReplayTrackerFunction#
Create a per-invocation replay tracker. Pass it to restateRuntime
(and any restateTool / restateWaitBackground) via their
replayTracker option; consume it via isReplaying() / onLive() / replayAwareAbort.
function createReplayTracker(options?: ReplayTrackerOptions): ReplayTrackerParameters
| Name | Type | Default | Description |
|---|---|---|---|
| options? | ReplayTrackerOptions | {} | Hooks and the |
Returns
deterministicUuidFunction#
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.
function deterministicUuid(seed: string): stringParameters
| Name | Type | Description |
|---|---|---|
| seed | string | String the UUID is derived from. The same seed gives the same UUID. |
Returns
stringisRestateCancellationFunction#
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.
function isRestateCancellation(error: unknown): booleanParameters
| Name | Type | Description |
|---|---|---|
| error | unknown | Error caught around a durable await. |
Returns
booleanisRestateControlErrorFunction#
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.
function isRestateControlError(error: unknown): booleanParameters
| Name | Type | Description |
|---|---|---|
| error | unknown | Error caught around a tool or provider call. |
Returns
booleantrue for Restate control-flow errors that must propagate. Platform DOMExceptions are never control errors.
raceWithTimeoutFunction#
Race an already-minted awakeable promise against a durable
ctx.sleep(timeoutMs). Split out from 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.
function raceWithTimeout<T, TPromise extends Promise<unknown> = Promise<unknown>>(
ctx: RestateContextLike,
promise: Promise<T>,
options?: AwakeableWithTimeoutOptions<TPromise>,
): Promise<AwakeableOutcome<T>>Type parameters
| Name | Type | Default | Description |
|---|---|---|---|
| T | |||
| TPromise | extends Promise<unknown> | Promise<unknown> |
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
| ctx | RestateContextLike | Restate context that provides the timer. | |
| promise | Promise<T> | Awakeable promise to race. | |
| options? | AwakeableWithTimeoutOptions<TPromise> | {} | Deadline and race combinator. See |
Returns
Promise<AwakeableOutcome<T>>replayAwareAbortFunction#
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.
function replayAwareAbort(tracker: ReplayTracker, abort: () => void): () => voidParameters
| Name | Type | Description |
|---|---|---|
| tracker | ReplayTracker | Tracker that reports whether the invocation is replaying. |
| abort | () => void | Function that aborts the run, for example |
Returns
() => voidrestateAwakeableToolFunction#
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.
function restateAwakeableTool<T = unknown>(
options: RestateAwakeableToolOptions<T>,
): ToolDefType parameters
| Name | Default | Description |
|---|---|---|
| T | unknown |
Parameters
| Name | Type | Description |
|---|---|---|
| options | RestateAwakeableToolOptions<T> | Tool spec, Restate context and the |
Returns
restateControlErrorFunction#
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.
function restateControlError(error: unknown): Error | undefinedParameters
| Name | Type | Description |
|---|---|---|
| error | unknown | Error that escaped the agent run. |
Returns
Error | undefinedrestateRuntimeFunction#
The durable runtime of one Restate invocation. Build it inside the handler, once per invocation, and hand it to the agent:
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 })
function restateRuntime(
ctx: RestateContextLike,
options?: RestateRuntimeOptions,
): DurableRuntimeParameters
| Name | Type | Default | Description |
|---|---|---|---|
| ctx | RestateContextLike | The handler's Restate context. A virtual-object context also backs the default session and tool-state stores. | |
| options? | RestateRuntimeOptions | {} | Combinators, id seed, clock coalescing and stores. See |
Returns
restateSessionStoreFunction#
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.
function restateSessionStore(
ctx: RestateObjectContextLike,
options?: RestateSessionStoreOptions,
): SessionStoreConstruct 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
| Name | Type | Description |
|---|---|---|
| ctx | RestateObjectContextLike | Virtual-object context whose K/V state holds the session. |
| options? | RestateSessionStoreOptions | Clock override and read-only mode. See |
Returns
function restateSessionStore(
ctx: Omit<RestateObjectContextLike, 'set' | 'clear'>,
options: RestateSessionStoreOptions & { readOnly: true },
): SessionStoreConstruct 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
| Name | Type | Description |
|---|---|---|
| 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 |
Returns
Example
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 })restateSpawnToolFunction#
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.
function restateSpawnTool(options: RestateSpawnToolOptions): ToolDefParameters
| Name | Type | Description |
|---|---|---|
| options | RestateSpawnToolOptions | Dispatch function and persistence and event settings. See |
Returns
Example
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) })restateToolFunction#
Wrap a single ToolDef so its execute runs inside ctx.run. The
tool's spec and isConcurrencySafe pass through unchanged.
function restateTool<Token = unknown, Resumed = unknown>(
inner: ToolDef,
ctx: RestateContextLike,
options?: RestateToolOptions<Token, Resumed>,
): ToolDefType parameters
| Name | Default | Description |
|---|---|---|
| Token | unknown | |
| Resumed | unknown |
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
| inner | ToolDef | Tool to wrap. | |
| ctx | RestateContextLike | Restate context that journals each call. | |
| options? | RestateToolOptions<Token, Resumed> | {} | Retry, naming and durable-start settings. See |
Returns
restateToolStateStoreFunction#
Build a 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:
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.
function restateToolStateStore(
ctx: RestateObjectContextLike,
options?: RestateToolStateStoreOptions,
): ToolStateStoreParameters
| Name | Type | Default | Description |
|---|---|---|---|
| ctx | RestateObjectContextLike | Virtual-object context whose K/V state holds the tool state. | |
| options? | RestateToolStateStoreOptions | {} | State key and value size limit. See |
Returns
restateWaitBackgroundFunction#
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>.
function restateWaitBackground(
options: RestateWaitBackgroundOptions,
): (handle: ExecutionHandle, taskId: string, opts?: { signal?: AbortSignal; timeoutMs?: number }) => Promise<TaskExitInfo | null>Parameters
| Name | Type | Description |
|---|---|---|
| options | RestateWaitBackgroundOptions | Context, task registry callbacks and timeout settings. See |
Returns
(handle: ExecutionHandle, taskId: string, opts?: { signal?: AbortSignal; timeoutMs?: number }) => Promise<TaskExitInfo | null>Constants#
DEFAULT_TOOL_STATE_KEYConstant#
Default virtual-object state key holding the tool-state map.
const DEFAULT_TOOL_STATE_KEY = 'zidane_tool_state'Interfaces#
AwakeableToolCombinatorsInterface#
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>.
interface AwakeableToolCombinatorsProperties
| Name | Type | Description |
|---|---|---|
| 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 |
AwakeableWithTimeoutOptionsInterface#
Options for awakeableWithTimeout and raceWithTimeout.
interface AwakeableWithTimeoutOptions<TPromise extends Promise<unknown> = Promise<unknown>>Type parameters
| Name | Type | Default | Description |
|---|---|---|---|
| TPromise | extends Promise<unknown> | Promise<unknown> |
Properties
| Name | Type | Description |
|---|---|---|
| promises? | RestatePromiseRaceLike<TPromise> | Durable race combinator — pass |
| timeoutMs? | number | Durable deadline in milliseconds. Unset / non-positive = no timeout
(the returned awakeable is the plain |
ChildRunRequestInterface#
What the parent hands a child invocation.
interface ChildRunRequestProperties
| Name | Type | Description |
|---|---|---|
| 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 ( |
| 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 |
| sessionId? | string | Id of the parent session. |
| subagentType? | string | Named subagent type requested by the model, if any. |
| task | string | The child's prompt. |
ChildRunResultInterface#
What a child invocation returns.
interface ChildRunResultProperties
| Name | Type | Description |
|---|---|---|
| children? | readonly MergedChild[] | Descendants this child already merged — see |
| endedAt? | number | End time on the child clock, as epoch milliseconds. See |
| error? | string | Failure detail when |
| startedAt? | number | When the child started / finished, measured on the CHILD's clock (e.g.
Strongly recommended for durable hosts. These land in the merged
|
| 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
|
InFlightChildInterface#
A child that was dispatched and has not settled yet.
interface InFlightChildProperties
| Name | Type | Description |
|---|---|---|
| depth | number | Subagent depth of the child. |
| label | string | Session-scoped label, such as |
| task | string | Task the child was given. |
MergedChildInterface#
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.
interface MergedChildProperties
| Name | Type | Description |
|---|---|---|
| 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. |
ReplayDoneHooksInterface#
Minimal structural slice of the agent's Hookable for replay:done.
interface ReplayDoneHooksProperties
| Name | Type | Description |
|---|---|---|
| callHook | (name: 'replay:done', ctx: Record<string, never>) => unknown | Fires the |
ReplayTrackerInterface#
Tells whether an invocation is still replaying its journal and notifies when it goes live.
interface ReplayTrackerProperties
| Name | Type | Description |
|---|---|---|
| isReplaying | () => boolean |
|
| isResuming | () => boolean |
This is the probe for live-streaming consumers: gate emission on
|
| markLive | () => void | Mark the invocation live. Called by the runtime (and |
| 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 |
ReplayTrackerOptionsInterface#
Options for createReplayTracker.
interface ReplayTrackerOptionsProperties
| Name | Type | Description |
|---|---|---|
| hooks? | ReplayDoneHooks | Agent hooks to notify at the replay→live transition. When set, the
tracker fires |
| 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 Powers |
RestateAwakeableInterface#
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>).
interface RestateAwakeable<T>Type parameters
| Name | Description |
|---|---|
| T |
Properties
| Name | Type | Description |
|---|---|---|
| id | string | Journal-stable id. Send it to whatever resolves the awakeable. |
| promise | Promise<T> | Settles when the awakeable is resolved or rejected. |
RestateAwakeableToolOptionsInterface#
Options for restateAwakeableTool.
interface RestateAwakeableToolOptions<T = unknown>Type parameters
| Name | Default | Description |
|---|---|---|
| T | unknown |
Properties
| Name | Type | Description |
|---|---|---|
| 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 |
| 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 |
| promises? | AwakeableToolCombinators | Durable combinators — pass |
| runOptions? | RestateRunOptions |
|
| timeoutMs? | number | Durable park deadline in milliseconds (a journaled |
| 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. |
RestateCombinatorsInterface#
The SDK RestatePromise statics the runtime uses. Method syntax on
purpose: it keeps the SDK's generic signatures assignable, so
promises: RestatePromise typechecks.
interface RestateCombinatorsProperties
| Name | Type | Description |
|---|---|---|
| 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 |
RestateContextLikeInterface#
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.
interface RestateContextLikeProperties
| Name | Type | Description |
|---|---|---|
| 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. |
| rand | { uuidv4: () => string } | Deterministic random source seeded from the invocation id. |
| resolveAwakeable? | <T>(id: string, payload?: T) => void | Resolve another invocation's awakeable (the resumer side). Optional:
only a resumer needs it, see |
| 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 |
| sleep | (millis: number) => Promise<void> | Durable timer — suspends the invocation for What it's for in this adapter: durable timeouts. Racing
|
RestateDurableStartInterface#
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:
startruns inside its ownctx.runentry, 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).- the wait — either
attach(onectx.runholding the wait in the result entry; only for short, bounded waits) orwait(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.
interface RestateDurableStart<Token = unknown, Resumed = unknown>Type parameters
| Name | Default | Description |
|---|---|---|
| Token | unknown | |
| Resumed | unknown |
Properties
| Name | Type | Description |
|---|---|---|
| attach? | (token: Token, input: Record<string, unknown>, toolCtx: ToolContext) => Promise<string | ToolResultContent[]> | Await / re-attach to the work identified by Required unless |
| 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:
Contract:
|
| start | (input: Record<string, unknown>, toolCtx: ToolContext) => Token | Promise<Token> | Kick off the side effect; journaled before |
| wait? | RestateDurableWait<Token, Resumed> | Awakeable-based completion wait — the durable alternative to
|
RestateDurableWaitInterface#
Awakeable-based completion wait for RestateDurableStart.wait.
Replay contract (mirrors restateAwakeableTool):
- The awakeable is minted BEFORE the journaled
registerentry, 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.
interface RestateDurableWait<Token = unknown, Resumed = unknown>Type parameters
| Name | Default | Description |
|---|---|---|
| Token | unknown | |
| Resumed | unknown |
Properties
| Name | Type | Description |
|---|---|---|
| inline? | (token: Token, input: Record<string, unknown>, toolCtx: ToolContext) => boolean | Per-call fast path: when this returns This is for MUST be a pure function of the journaled |
| promises? | RestatePromiseRaceLike<Promise<unknown>> | Durable race combinator for the timeout race — pass |
| register | (token: Token, awakeableId: string, input: Record<string, unknown>, toolCtx: ToolContext) => void | Promise<void> | Register |
| 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. |
| timeoutMs? | number | ((input: Record<string, unknown>, toolCtx: ToolContext) => number | undefined) | Durable park deadline in milliseconds — becomes the
|
RestateObjectContextLikeInterface#
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.
interface RestateObjectContextLike extends RestateContextLikeProperties
| Name | Type | Description |
|---|---|---|
awakeablefrom RestateContextLike | <T>() => RestateAwakeable<T> | Creates an awakeable: a journal-stable id and a promise that waits for an outside resolver. |
| clear | (name: string) => void | Removes a state key. |
datefrom RestateContextLike | { now: () => Promise<number> } | Durable clock. |
| get | <T>(name: string) => Promise<T | null> | Reads a state value. Resolves to |
| key | string | Key of the virtual-object instance the handler is bound to. |
randfrom RestateContextLike | { uuidv4: () => string } | Deterministic random source seeded from the invocation id. |
resolveAwakeable?from RestateContextLike | <T>(id: string, payload?: T) => void | Resolve another invocation's awakeable (the resumer side). Optional:
only a resumer needs it, see |
runfrom RestateContextLike | <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 |
| set | <T>(name: string, value: T) => void | Writes a state value. The SDK journals the change. |
sleepfrom RestateContextLike | (millis: number) => Promise<void> | Durable timer — suspends the invocation for What it's for in this adapter: durable timeouts. Racing
|
RestatePromiseRaceLikeInterface#
Structural subset of the SDK's RestatePromise static surface used by
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.
interface RestatePromiseRaceLike<TPromise extends Promise<unknown> = Promise<unknown>>Type parameters
| Name | Type | Default | Description |
|---|---|---|---|
| TPromise | extends Promise<unknown> | Promise<unknown> |
Properties
| Name | Type | Description |
|---|---|---|
| race | (values: readonly TPromise[]) => Promise<unknown> | Resolves with the first of |
RestateRunOptionsInterface#
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.
interface RestateRunOptionsProperties
| Name | Type | Description |
|---|---|---|
| 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. |
RestateRuntimeOptionsInterface#
Options for restateRuntime.
interface RestateRuntimeOptionsProperties
| Name | Type | Description |
|---|---|---|
| coalesce? | boolean | { reanchorEvery?: number } | Coalesce |
| idSeed? | string | Entropy of the |
| promises? | RestateCombinators | Durable combinators: pass |
| replayTracker? | ReplayTracker | Per-invocation replay tracker ( |
| resolveAwakeable? | (id: string, value: JsonValue) => void | Promise<void> | Resolve an awakeable of another invocation (an ingress client, e.g.
|
| runOptions? | Partial<Record<StepKind, RestateRunOptions>> |
|
| sessionStore? | SessionStore | Where sessions live. Default: |
RestateSessionStoreOptionsInterface#
Options for restateSessionStore.
interface RestateSessionStoreOptionsProperties
| Name | Type | Description |
|---|---|---|
| clock? | AgentClock | Time source for the journaled header timestamps ( Defaults to |
| readOnly? | boolean | Never write state. Required when the store is built from a SHARED
handler's context ( |
RestateSpawnToolOptionsInterface#
Options for restateSpawnTool.
interface RestateSpawnToolOptionsProperties
| Name | Type | Description |
|---|---|---|
| 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. |
| emitChildEvents? | boolean | Replay each finished child's transcript onto the parent's hook bus as
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
They re-fire on a parent replay, exactly as the in-process tool's
|
| 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: Restate cancels the whole call tree, so this reaches children at any depth
and needs no per-child bookkeeping. Why not per-child ids: the Timing: cancelling appends a journal command, so it must not happen during
a replay. Drive aborts through |
| 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 |
| persist? | boolean | Merge each child's turns and run record into the parent's session.
Default: Ordering nuance versus the in-process tool: the merge runs on
|
RestateToolOptionsInterface#
Options for restateTool.
interface RestateToolOptions<Token = unknown, Resumed = unknown>Type parameters
| Name | Default | Description |
|---|---|---|
| Token | unknown | |
| Resumed | unknown |
Properties
| Name | Type | Description |
|---|---|---|
| coerceResultBlocks? | boolean | Collapse malformed structured result blocks ( Default: |
| detachedTasks? | DetachedTasksCapability |
Default: |
| disableShellBackground? | boolean | Hide and reject the built-in shell tool's background mode. Default: derived from |
| durableStart? | RestateDurableStart<Token, Resumed> | Two-phase start/attach journaling for crash-safe long tools — see
|
| 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 |
| replayTracker? | ReplayTracker | Per-invocation replay tracker (see |
| runOptions? | RestateRunOptions |
|
| softErrors? | boolean | Keep an ordinary, recoverable tool throw (a failed shell command, a
transport error, an MCP Without this, the throw rides out of the journaled Applies to the single-phase journaling path. The two-phase
Default: |
RestateToolStateStoreOptionsInterface#
Options for restateToolStateStore.
interface RestateToolStateStoreOptionsProperties
| Name | Type | Description |
|---|---|---|
| key? | string | Override the state key. Defaults to |
| maxValueBytes? | number | Per-value size budget enforced when a tool stages a write. Defaults to
the library's |
RestateWaitBackgroundOptionsInterface#
Options for restateWaitBackground.
interface RestateWaitBackgroundOptionsProperties
| Name | Type | Description |
|---|---|---|
| ctx | RestateContextLike | The Restate context the awakeable + journal entries bind to. |
| entryPrefix? | string | Journal-entry prefix. Default |
| probe | (handle: ExecutionHandle, taskId: string) => readonly TaskEntry[] | Promise<readonly TaskEntry[]> | Snapshot the host's task registry (the |
| promises? | RestatePromiseRaceLike<Promise<unknown>> | Durable race combinator for the timeout race — pass |
| register | (awakeableId: string, taskId: string, handle: ExecutionHandle) => void | Promise<void> | Persist |
| replayTracker? | ReplayTracker | Per-invocation replay tracker (see |
| runOptions? | RestateRunOptions |
|
| settle? | (awakeableId: string, taskId: string, exit: TaskExitInfo | null, handle: ExecutionHandle) => void | Promise<void> | Finalize the resumer row once the wait resolves — |
| timeoutMs? | number | Default park deadline in milliseconds. A per-call |
Types#
RestatePromiseLikeType#
A promise that also exposes the SDK map combinator (RestatePromise).
type RestatePromiseLike<T> = Promise<T> & { map: <U>(mapper: (value?: T, failure?: unknown) => U) => Promise<U> }Type parameters
| Name | Description |
|---|---|
| T |
Re-exports#
These names are documented on the entry point that owns them.