Orchestrator
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:120
Composition root that creates, wires, and coordinates all microkernels.
The Orchestrator owns zero private state and emits zero events. It sequences calls across microkernels in the right order. Consumers interact with microkernels directly for domain operations.
Example
Section titled “Example”const connector = new WebSocketConnector({ url: 'wss://api.example.com/ws' });const orchestrator = new Orchestrator(connector, { debug: true });
await orchestrator.connect();const response = await orchestrator.processIntent('show me sales data');
// Listen for events via microkernels directly:orchestrator.requestCoordinator.events.on('processing_completed', (e) => { ... });orchestrator.chartManager.setDimensions({ columns: 10, rows: 6 });Throws
Section titled “Throws”If options.initialLayout is not a tiling layout (a grid layout
only repositions instances that already exist, and none do at
construction).
Throws
Section titled “Throws”If options.initialLayout is present and options.layoutMode is
'grid' — the seed’s type and the mode must agree.
Throws
Section titled “Throws”If options.initialLayout declares the same instanceId twice.
Throws
Section titled “Throws”If an options.initialLayout tree contains a split token whose
weight suffix is malformed — anything an engine response would have
repaired, such as "V:1,5000" or "V:01,1". A seed is rejected rather
than repaired: the lenient-LLM / strict-developer asymmetry means the
author is told, instead of being handed geometry they did not write.
Throws
Section titled “Throws”If an options.initialLayout tree names the same instance in two
leaves. An instance renders as exactly one tile; weight the split token
instead of repeating the leaf.
Throws
Section titled “Throws”If an options.initialLayout tree leaf is not declared in its
instances, or a declared instance is referenced by no tree leaf.
Throws
Section titled “Throws”If an options.initialLayout instance names a vessel that is not
registered via options.vessels, or a variant that vessel does not
declare.
Constructors
Section titled “Constructors”Constructor
Section titled “Constructor”new Orchestrator(
connector,options?):Orchestrator
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:151
Parameters
Section titled “Parameters”connector
Section titled “connector”options?
Section titled “options?”OrchestratorOptions = {}
Returns
Section titled “Returns”Orchestrator
Accessors
Section titled “Accessors”chartManager
Section titled “chartManager”Get Signature
Section titled “Get Signature”get chartManager():
ChartManager
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:307
Returns
Section titled “Returns”commandProcessor
Section titled “commandProcessor”Get Signature
Section titled “Get Signature”get commandProcessor():
CommandProcessor
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:299
Returns
Section titled “Returns”connectionManager
Section titled “connectionManager”Get Signature
Section titled “Get Signature”get connectionManager():
ConnectionManager
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:295
Returns
Section titled “Returns”requestCoordinator
Section titled “requestCoordinator”Get Signature
Section titled “Get Signature”get requestCoordinator():
RequestCoordinator
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:311
Returns
Section titled “Returns”vesselManager
Section titled “vesselManager”Get Signature
Section titled “Get Signature”get vesselManager():
VesselManager
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:303
Returns
Section titled “Returns”Methods
Section titled “Methods”addAssistantMessage()
Section titled “addAssistantMessage()”addAssistantMessage(
message):Promise<void>
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:714
Parameters
Section titled “Parameters”message
Section titled “message”string
Returns
Section titled “Returns”Promise<void>
addAssistantMessageAcknowledged()
Section titled “addAssistantMessageAcknowledged()”addAssistantMessageAcknowledged(
args):Promise<AssistantMessageOutcome>
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:740
Insert an assistant message into engine history and report whether the engine stored it or recognized it as a duplicate.
Used by greeting flows that must prove persistence before speech and must
not speak twice for one session: 'inserted' means this call created the
record; 'deduped' means an equal id already existed.
Requires an active connector that implements AcknowledgedAssistantConnector.
Parameters
Section titled “Parameters”Stable message id; identity for engine-side dedupe.
message
Section titled “message”string
Exact assistant text to persist.
Returns
Section titled “Returns”Promise<AssistantMessageOutcome>
Throws
Section titled “Throws”If called after dispose.
Throws
Section titled “Throws”If the active connector lacks acknowledged-assistant support.
Throws
Section titled “Throws”Propagates connector transport, engine, close, and timeout failures.
connect()
Section titled “connect()”connect():
Promise<void>
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:694
Returns
Section titled “Returns”Promise<void>
disconnect()
Section titled “disconnect()”disconnect():
Promise<void>
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:698
Returns
Section titled “Returns”Promise<void>
dispose()
Section titled “dispose()”dispose():
Promise<void>
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:832
Retire scheduled updates and request error reporting before transport shutdown.
Coalesced flushes resolve to null because their response cannot be applied.
In-flight intents still settle with their transport outcome.
Returns
Section titled “Returns”Promise<void>
flushUpdates()
Section titled “flushUpdates()”flushUpdates():
Promise<MaelstromResponse|null>
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:526
Send whatever the UpdateCoordinator has buffered now, instead of waiting for its debounce to fire, and apply the response the same way processIntent does.
Use it when something must reach the engine at a known moment — a test, a teardown, a UI gesture that should not sit out the debounce window. If a request is already in flight this coalesces with it rather than sending a second one: the buffered updates are put back and the returned promise settles with that request’s outcome.
RESOLVES null — deliberately the same value for every “nothing landed”
case, because none of them is a failure the caller can act on:
- the Orchestrator is disposed;
- nothing was buffered;
- a concurrent setConnector superseded the response, which was therefore never applied (the updates are requeued for the next flush);
- the send itself failed because of that swap — the transport error is swallowed rather than thrown, and logged at warn with the epoch that fenced it, which is the only thing distinguishing it from a request that was never sent.
REJECTS with the transport’s error when a send fails under the current connector — an ordinary failure, not a fenced one, is never swallowed.
Returns
Section titled “Returns”Promise<MaelstromResponse | null>
getCapabilitySnapshot()
Section titled “getCapabilitySnapshot()”getCapabilitySnapshot():
CapabilitySnapshot
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:371
Capability catalog (vessels, variants, actions) for get_capabilities pulls.
Returns
Section titled “Returns”getConfig()
Section titled “getConfig()”getConfig():
ResolvedOrchestratorOptions
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:319
Returns
Section titled “Returns”getHistory()
Section titled “getHistory()”getHistory(
sessionId):Promise<readonlySessionHistoryEntry[]>
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:769
Fetch the engine-recorded history for the given session.
Use this to rehydrate a conversation after a refresh, on a new device,
or in a parallel tab — anywhere a developer has persisted a session id
and wants to seed UI state from the server. The session id is passed
explicitly (rather than read from the orchestrator’s own
config.sessionId) so callers can fetch any session they have an id
for, including a session that predates this orchestrator instance.
Errors from the connector (transport failure, engine error) propagate
as thrown Errors — same shape as addAssistantMessage and
processIntent.
Parameters
Section titled “Parameters”sessionId
Section titled “sessionId”Returns
Section titled “Returns”Promise<readonly SessionHistoryEntry[]>
getStructuralSnapshot()
Section titled “getStructuralSnapshot()”getStructuralSnapshot():
UIStructuralSnapshot
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:334
Structural-only UI snapshot for grounding + get_ui_state pulls.
Returns
Section titled “Returns”getViewState()
Section titled “getViewState()”getViewState():
OrchestratorViewState
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:323
Returns
Section titled “Returns”invokeAction()
Section titled “invokeAction()”invokeAction(
instanceId,action,params?):void
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:605
Invoke a vessel action locally — the client-authoritative path for actions a user triggers directly in the UI (e.g. a button click), as opposed to actions the engine returns in an execution plan.
WHAT it does, in order:
- Validates that
instanceIdexists and thatactionis declared on the instance’s current variant. If either check fails it logs a warning and no-ops — it never throws, so it is safe to call straight from a render callback. - If the action declares a
transitionsTovariant that exists on the vessel, it updates the store’s variant synchronously, before the runner runs — the store is authoritative and the UI reflects the new variant immediately (no optimistic-override-then-rollback dance). - Runs the action’s registered runner through the same CommandProcessor /
ActionManager machinery as engine-plan actions (runner-readiness wait,
execution timeout,
action_erroremission). - If the runner fails (error result, throw, or timeout), reverts the variant — but only if the store variant is still the one this call set, so a newer transition or an engine layout that landed in between wins.
- When (and only when) a transition actually happened in step 2, schedules
a
vessel_mutationupdate so the engine can re-plan layout. Bursts are coalesced by the UpdateCoordinator’s debounce.
WHY: vessels declare their own state machines, so a locally-invoked action
with a transitionsTo is a fact the client already knows — applying it
locally makes the client the authority instead of waiting on an engine
round-trip.
INVARIANTS:
- Never throws; invalid input warns and no-ops.
- The variant transition is applied before the runner is awaited.
- A revert never clobbers a variant a later write already changed.
- No
transitionsTo, or no effective variant change → no engine update.
Parameters
Section titled “Parameters”instanceId
Section titled “instanceId”InstanceId
The instance to act on; must exist in the store.
action
Section titled “action”ActionName
The action name; must be declared on the current variant.
params?
Section titled “params?”Record<string, unknown>
Optional parameter object passed to the runner verbatim.
Returns
Section titled “Returns”void
Example
Section titled “Example”// In a vessel's render callback:<button onClick={() => orchestrator.invokeAction(instanceId, 'expand')}> Expand</button>onSettled()
Section titled “onSettled()”onSettled(
cb):Unsubscribe
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:401
Subscribe to the render-settled signal.
Fires whenever the CommandProcessor’s command queue drains back to
idle — the moment a batch of layout + action commands has fully
applied. Both settle sources required by grounding reach this
transition: an engine response dispatched via processIntent /
flushUpdates (set_layout + execute_actions as one atomic batch),
and direct manipulation (a user click / variant change, which reaches
the processor through commandProcessor.executeActions() the same way
a React executeAction call does). Neither path bypasses the queue, so
a single subscription to the queue-idle transition grounds both.
This is the grounding-push trigger only — it is NOT how speak-after-render
is sequenced. That flow awaits a specific intent’s own
commandProcessor.waitForIdle() after processIntent resolves, so it
correlates to that intent’s response rather than racing this global signal.
May fire more than once per logical settle (each queue-drain-to-idle transition invokes it); downstream consumers that only want the latest state should debounce.
Parameters
Section titled “Parameters”() => void
Returns
Section titled “Returns”processIntent()
Section titled “processIntent()”processIntent(
intent):Promise<MaelstromResponse>
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:456
Send a user intent to the engine and apply the response it plans.
Any updates buffered by the UpdateCoordinator ride along in the same request, so an intent typed right after a resize or a vessel mutation asks the engine to plan against what the board actually looks like. The response is dispatched as one atomic batch — layout first, then actions — and only then are queued flush promises settled, so a consumer awaiting a flush observes fully updated state.
Single-flight: only one engine request may be outstanding, so this rejects with RequestInFlightError rather than queueing.
REJECTS WITH:
RequestInFlightError— a request is already in flight; retry whenrequestCoordinator.isProcessingclears.ConnectorSupersededError— a concurrent setConnector replaced the connector while this request was in flight. The response was deliberately never applied, so the intent must be re-issued; seesetConnectorfor why the fence exists.- the transport’s own error — either an ordinary send failure, or the failure a swap itself caused when the outgoing connector was disconnected out from under this request. Both shapes mean the intent must be re-issued.
Error('Orchestrator is disposed')— called after dispose.
INVARIANTS:
- Whenever a fenced outcome discards the response, the updates that rode along are requeued for the next flush — but the intent itself is not. Re-issuing it is the caller’s call to make.
- A response that arrives after dispose still resolves here: the request was legitimately issued, it simply is never dispatched into a torn-down CommandProcessor.
Parameters
Section titled “Parameters”intent
Section titled “intent”string
Returns
Section titled “Returns”Promise<MaelstromResponse>
scheduleUpdate()
Section titled “scheduleUpdate()”scheduleUpdate(
reason):void
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:497
Parameters
Section titled “Parameters”reason
Section titled “reason”ChartResizeReason | VesselMutationReason
Returns
Section titled “Returns”void
setConnector()
Section titled “setConnector()”setConnector(
next):void
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:802
Replace the connector on a live Orchestrator, preserving the session.
Use this when the transport changes but the session does not — re-auth is the motivating case. The arranged board, vessel registry, and chart dimensions all survive; only the transport is swapped.
Any request already in flight is fenced, not cancelled: EngineConnector
exposes no abort, so the old request runs to completion and its response
is discarded rather than applied. The guarantee is scoped to dispatch: a
response computed under superseded credentials is never handed to the
CommandProcessor, so it cannot reach the UI — but it has already been
committed to requestCoordinator.lastResponse and emitted as
processing_completed by the time the fence runs, so anything subscribed
to those two surfaces still observes it.
A caller’s pending processIntent always rejects under a swap,
in one of two shapes: ConnectorSupersededError when the response
arrived after the swap, or the transport’s own error when the swap made
the send itself fail (the outgoing connector was disconnected out from
under it). Either way the intent took no effect and must be re-issued.
A pending flushUpdates resolves null for both shapes instead —
its updates are requeued, so there is nothing for the caller to redo.
Parameters
Section titled “Parameters”Returns
Section titled “Returns”void
setLayoutMode()
Section titled “setLayoutMode()”setLayoutMode(
mode):void
Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:356
Change the layout mode for subsequent engine requests.
The mode is read fresh on every request and view-state read — it is never captured at construction — so this takes effect on the next request with no rebuild and no session teardown. The client does not lay anything out itself; the mode is forwarded to the engine, which decides.
This does not touch the stored layout. If the stored layout’s own
type no longer agrees with mode, buildChartRequest resynthesizes
a fresh, empty layout of the requested type at request-build time
instead of forwarding the stale one — see the comment there for why
that has to happen at build time rather than here. That resynthesis
costs the arranged board, so this warns when it commits to it: the
same combination the constructor rejects outright is merely lossy
here, and the loss is otherwise invisible.
Parameters
Section titled “Parameters”LayoutMode
Returns
Section titled “Returns”void