Skip to content

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.

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 });

If options.initialLayout is not a tiling layout (a grid layout only repositions instances that already exist, and none do at construction).

If options.initialLayout is present and options.layoutMode is 'grid' — the seed’s type and the mode must agree.

If options.initialLayout declares the same instanceId twice.

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.

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.

If an options.initialLayout tree leaf is not declared in its instances, or a declared instance is referenced by no tree leaf.

If an options.initialLayout instance names a vessel that is not registered via options.vessels, or a variant that vessel does not declare.

new Orchestrator(connector, options?): Orchestrator

Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:151

EngineConnector

OrchestratorOptions = {}

Orchestrator

get chartManager(): ChartManager

Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:307

ChartManager


get commandProcessor(): CommandProcessor

Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:299

CommandProcessor


get connectionManager(): ConnectionManager

Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:295

ConnectionManager


get requestCoordinator(): RequestCoordinator

Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:311

RequestCoordinator


get vesselManager(): VesselManager

Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:303

VesselManager

addAssistantMessage(message): Promise<void>

Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:714

string

Promise<void>


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.

MessageId

Stable message id; identity for engine-side dedupe.

string

Exact assistant text to persist.

Promise<AssistantMessageOutcome>

If called after dispose.

If the active connector lacks acknowledged-assistant support.

Propagates connector transport, engine, close, and timeout failures.


connect(): Promise<void>

Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:694

Promise<void>


disconnect(): Promise<void>

Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:698

Promise<void>


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.

Promise<void>


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.

Promise<MaelstromResponse | null>


getCapabilitySnapshot(): CapabilitySnapshot

Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:371

Capability catalog (vessels, variants, actions) for get_capabilities pulls.

CapabilitySnapshot


getConfig(): ResolvedOrchestratorOptions

Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:319

ResolvedOrchestratorOptions


getHistory(sessionId): Promise<readonly SessionHistoryEntry[]>

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.

SessionId

Promise<readonly SessionHistoryEntry[]>


getStructuralSnapshot(): UIStructuralSnapshot

Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:334

Structural-only UI snapshot for grounding + get_ui_state pulls.

UIStructuralSnapshot


getViewState(): OrchestratorViewState

Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:323

OrchestratorViewState


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:

  1. Validates that instanceId exists and that action is 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.
  2. If the action declares a transitionsTo variant 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).
  3. Runs the action’s registered runner through the same CommandProcessor / ActionManager machinery as engine-plan actions (runner-readiness wait, execution timeout, action_error emission).
  4. 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.
  5. When (and only when) a transition actually happened in step 2, schedules a vessel_mutation update 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.

InstanceId

The instance to act on; must exist in the store.

ActionName

The action name; must be declared on the current variant.

Record<string, unknown>

Optional parameter object passed to the runner verbatim.

void

// In a vessel's render callback:
<button onClick={() => orchestrator.invokeAction(instanceId, 'expand')}>
Expand
</button>

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.

() => void

Unsubscribe


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 when requestCoordinator.isProcessing clears.
  • 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; see setConnector for 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.

string

Promise<MaelstromResponse>


scheduleUpdate(reason): void

Defined in: packages/client/src/lib/orchestrator/orchestrator.ts:497

ChartResizeReason | VesselMutationReason

void


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.

EngineConnector

void


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.

LayoutMode

void