@maiaos/sdk reference
Status: Source candidate. The SDK is the only product-client request
boundary to Maia Node: the CLI, maia mcp and the Desktop and Expo clients use
it. It transports typed operations and never
owns business authority. Contracts own the operations; the
package README covers install and a first call.
The package rules are in packages/sdk/AGENTS.md.
Methods mirror the registered operations; many are release-disabled at the Node. The SDK never starts or stops a Node, reads Node storage or supervises processes.
Entry points
| Entry point | Contents |
|---|---|
@maiaos/sdk | Platform-neutral LocalNodeClient, request options, errors, attach reducer |
@maiaos/sdk/node | connectNodeApi, createNodeApiClient and WebSocket observations (Node.js) |
@maiaos/sdk/node/effect | The same discovery and client as Effect values; connectSessionNodeApiEffect connects as one executor Session from its launch environment; openNodeBridge speaks the SSH stdio bridge over a process the caller spawned (sshCommandArguments, bridgeScript) |
@maiaos/sdk/voice-orchestrator | Voice orchestrator prompt and tool schemas for Realtime clients |
@maiaos/sdk/voice-pi-relay | Voice-to-Pi relay contracts |
Browser and mobile bundles import only the root entry, so they never receive Node.js filesystem modules or the profile credential.
connectNodeApi finds the profile's private connection descriptor, reads its
credential and proves the Node's identity with an authenticated node.status
before it returns the client.
Client behavior
-
One typed client.
LocalNodeClienthas one method per operation, for examplecreateSession,startSession,sendTurn,waitTurns,observeSessions,stopSession,resumeSession,diffSession,archiveSession,getReceipt,listConnectionsandshowConnectionUsage. Parameters and results are the@maiaos/protocoltypes, validated on both sides. Human-only methods such aspreviewHoldPlace,confirmHoldPlace,previewHoldRelease,confirmHoldReleaseandlistHoldscheck that the Node echoes the exact binding they sent. -
Typed call.
call(method, parameters, options)serves every operation whose feature descriptor bindssdk.operation: "call"(todayreference.resolve,session.renameandevents.read).LOCAL_NODE_CALL_METHODSlists them from a static table the type checker holds to the descriptors, so a client does not bundle the feature registry. Parameters and results are typed from the protocol unions, the payload is validated before sending, and the answer must name the method and, for a command, carry a receipt with the command ID sent. An unconfirmed command failsoutcome_unknown. Such an operation has no method of its own. -
Request options. Every call takes
{ messageId, signal?, deadlineAt? }. TheAbortSignalcancels the request; a mutation cancelled after dispatch reportsoutcome_unknowninstead of guessing.deadlineAtis abigintinstant on the caller's ownClock.monotonicTimeNanos, never wall-clock time, after which an in-process requester on the sameClockstops waiting or retrying. The SDK's own requesters ignore it, so a caller that omits it sees no change. -
Errors. Failures are
MaiaClientErrorvalues with a stable code (transport_failure,cancelled,outcome_unknown,remote_error, ...) and, for Node rejections, the protocol error. Reconcile an unknown outcome withgetReceiptand the same command ID; never retry with a new one.instanceof MaiaClientErroralso recognizes an error from another copy of the SDK in the same process, such as the one a bundled CLI carries, through a process-wideSymbol.for("@maiaos/sdk/MaiaClientError")brand. That key is the contract between copies: renaming it breaks recognition between SDK versions. -
Errors as one value.
classifyClientErrorturns any failure into aMaiaError(_tag: "MaiaError");mutation: truemarks a request that may have changed Node state. Itskindiscancelled,conflict,declined(a person declined the native confirmation; nothing was sent),denied,invalid,not_found,offline,outcome_unknown(a mutation's answer never arrived),refusedorunavailable, plus the Node'scode,retryableand, for asaturatedanswer of the Node only, itsmessage(why it is full); no other code and no local failure carries one. The CLI and clients act onkind, never on a code string. -
Session tasks.
planSessionCreationsplits text into tasks (one per line, or the whole text; at most 32 of at most 16 KiB each), picks the Workspace (a Project's ready Workspaces, its main checkout first, throughorderExecutionWorkspaces; a choice for the caller, which the Node still validates) and mints every command ID once.createSessionsruns each item, four at a time:session.create,session.start,awaitSessionRunning, then the task as the firstturn.send. Each item endssent(orstartedwithout a task),refusedwith itsMaiaError, orunknown. Each outcome has a requiredreceiptsfield with the Node's receipt of every step that committed (create,start,send;{}before any). Whensession.create,session.startorturn.sendfails, refused or unknown, the error carries that step'scommandId; only anunknownone needs reconciling withreceipt.get. Asession.showfailure has none. A caller passing hand-builtpreviousoutcomes addsreceipts: {}. Called again withpreviousoutcomes, it replays only the unsettled items with the same command IDs and request bodies, so the Node answers with the original Sessions and turns and no turn is sent twice. The SDK composes existing operations here and owns no authority.@maiaos/sdk/node/effecthas the same asplanSessionCreationEffect,createSessionsEffectandawaitSessionRunningEffect. -
Node changes.
watchNodeChanges(relay, { instanceId, newId })builds eachobserve.eventssubscribe message, checks every frame's size, schema and correlation, and yieldsrecordsandresyncchanges. When the platformStreamRelaycloses it resubscribes from the last cursor, and a gap or a cursor, policy or scope reset yields aresync. A reset the old subscription cannot recover from ends the watch with a non-retryableremote_errorwhoseremoteError.codeisepoch_changed(the Node restarted: the caller reads the Node's new instance and watches that one),policy_revokedorprincipal_changed(the caller authenticates again).reduceNodeChangesis the pure frame step. -
Session attach.
attachSession(@maiaos/sdk/node) yields one Session'ssession.attachframes;attachSessionEffect(@maiaos/sdk/node/effect) discovers the Node first. The purereduceSessionAttachreducer in the root entry folds frames into the view every client renders, withsessionAttachCursorfor a reattach. -
Transport limits. The Node.js adapter accepts only the exact
127.0.0.1origin, bounds bodies to 1 MiB, admits eight concurrent requests per transport and applies a five-second deadline. Three tiers, in one map innode/deadlines.ts:- 5 s for everything else.
- 15 s for the operations that hash a vendor binary or launch a vendor
process before they reply, about three times the slowest measured plan:
connection.detect,connection.login.start,connection.system.preview,connection.system.confirm,discovery.sessions.list,integration.plan.*andintegration.apply.*for Claude Code and Codex,session.adopt.preview,session.adopt.confirm,session.history.readandsession.resume. A method is listed whether or not a given call reaches the slow step. - 50 s for
connection.usage.show, which outlasts the Node's own 45 s refresh probe, so the Node answers first.
The SSH bridge waits ten seconds longer than the request it forwards, so its deadline is never the shorter one.
A client deadline does not cancel Node work. Handlers keep their own limits (usage refresh: 45 s) and finish after the client gives up, so a request past its deadline fails
outcome_unknown. For the Codex launch paths the 15 s is a client cutoff, not a Node bound. A mutation that times out there (session.adopt.confirm,session.resume) is reconciled on a retry:session.adopt.confirmreplays by its preview ID (a confirmed preview replays its receipt), andsession.resumeby its command ID. A retry is not free. A plan retry reuses the envelope'smessageId, but resolves the target and hashes it again before it checks the stored replay. An apply replays by its command ID and plan ID. A confirm replays a recorded result, but a claim still outstanding returnsstorage_conflict, and a confirm that failed part way keeps its claimed confirmation until that is fixed.
Target observation client (planned)
Planned, not exported: one aggregate, transport-neutral observation client generated from the protocol.
client.observations.snapshot(query, options);
client.observations.subscribe(query, { after, signal });
client.observations.session({ agentSessionId, includeContent }, options);
client.observations.pipeline({ processorId, partitionId }, options);
subscribe opens one authorized feed for any number of Projects and Sessions
with a signed, resumable cursor; slow consumers get typed gap and reset results
rather than backpressuring workers. A generated, reflective operation catalog
(client.operations.list, show, invoke) is also planned.
Imported from Maia OS docs at 3dc3e07