Skip to content
Developer Preview

Quickstart: your first session in TypeScript

This is the shortest complete path from nothing to a working @muse-code/sdk program: start the host, run a turn, answer its permission request, cancel a turn, reload the session in a new process, and shut down. Follow it top to bottom. You do not need to read the SDK source.

The same journey ships as the runnable @muse-code/sdk-quickstart package, which runs against a release-built host on every change to the SDK and these docs. It asserts what the protocol is specified to do, so every step below is one a release-built host satisfies today. If a step ever regresses, the journey marks it blocked and fails loudly, and this page gains a caveat saying which step and on what open issue.

  • Node 20 or newer.

  • The @muse-code/sdk package, published on npm:

    Terminal window
    npm install @muse-code/sdk

    It has zero runtime dependencies — the only thing it needs at run time is a muse binary to talk to. TypeScript users also want @types/node, because the SDK’s types name Node’s own (ChildProcess, Readable) on its spawn and stream surfaces.

  • A muse binary. Its MSP surface is what your program talks to; the MSP concepts guide explains the model behind every call you are about to make.

  • A configured provider: run muse once and log in, or point your program’s HOME at a profile that already has credentials. A host with no credentials fails every turn.

spawnMspConnection starts your own muse serve child and returns a handshake you have not sent anything on yet. You cannot send traffic before the handshake completes — that is enforced by the type, not by convention.

const handshake = spawnMspConnection({
command: museBin,
args: ["serve"],
cwd: workspaceRoot,
onStderr: (chunk) => stderr.push(chunk),
});

Pass env if you want a specific HOME; the quickstart package does, so it never touches your real sessions. If the host exits immediately instead of answering, the exit code tells you why — see exit classification. In particular, code 5 means that build’s experimental SDK tier is switched off and no invocation of that binary will serve.

const msp = await handshake.initialize({
clientInfo: { name: "my-app", version: "1.0.0" },
});

msp.initializeResult tells you who answered: serverInfo, museHome, sessionDurability, and the protocol schema.fingerprint. The SDK compares that fingerprint to the one it was built against and sets msp.fingerprintWarning if they differ. A difference is a warning, never an error — an older SDK keeps working against a newer host.

From here on you talk through msp.connection. Subscribe to notifications right away: the connection holds a single handler — onNotification replaces it rather than adding a listener — and anything the host pushes before you subscribe is dropped, not buffered.

msp.connection.onNotification((notification) => { /* ... */ });
const result = await msp.connection.command(
"session/start",
{ workspaceRoot },
);
const sessionId = result.session.sessionId;

Use command rather than request for anything that changes state. It mints a commandId for you and remembers it, so a retry after a dropped reply joins the original command instead of starting a second one — the commands and idempotency guide explains why that matters. result.viewCursor is your position in the session’s event stream.

The host also pushes a session/started notification with the same session — it can land in the same read as the command’s reply, which is why you subscribed back in step 2.

4. Send a prompt and read the answer as it streams

Section titled “4. Send a prompt and read the answer as it streams”
const ack = await msp.connection.command(
"turn/start",
{ sessionId, input: [{ type: "text", text: "Reply with the single word: hello" }] },
);
// ack.status === "accepted", ack.turnId

The ack means the turn was admitted, not finished. Its disposition is "started" for a fresh turn or "queued" when the session has not yet settled to idle — both are admitted; turn/started still fires for that turnId. Like terminal below, disposition is an open set — status: "accepted" alone means the turn was admitted. The answer arrives as notifications:

notification what it means
turn/started the turn is running
item/started a new item (e.g. the agent’s message) has begun
item/delta more text for that item
item/completed that item is final
turn/completed the turn is over; terminal says how it ended

Accumulate item/delta for live output, and treat the item/completed text as authoritative. turn/completed.terminal says how the turn ended: "completed", "cancelled" or "failed" today, but it is an open set — treat any unknown value as terminal. On "failed" the notification carries reason and error. A reason like not logged in means the host has no credentials — see What you need.

When the agent wants to do something that needs your consent, the host pushes approval/requested. Answer it:

const decided = await msp.connection.command(
"approval/decide",
{
sessionId,
approvalId,
requirementId: { approvalId, sourceIndex: 0 },
choiceId: "allow_once",
feedback: null,
},
);
// decided.terminal === true

approval/resolved follows, naming the same approvalId. The turn then continues. The approvals guide covers the full requirement and choice model.

await msp.connection.command("turn/cancel", { sessionId, turnId });

Cancellation is not instant and it is not an error. Wait for turn/completed; its terminal will be "cancelled".

const exit = await msp.close(); // { code: 0, signal: null }

close() closes stdin, and the host drains and exits. The session’s writer lease is released with it — which is what lets a later process load the same session.

Spawn and handshake exactly as before, then:

const resumed = await msp.connection.command(
"session/resume",
{ sessionId, excludeItems: false },
);

You get back:

  • resumed.session — the same sessionId, its workspaceRoot, status, turnCount, providerId and modelId.
  • resumed.viewCursor — where the session now is.
  • resumed.history — the conversation so far. history.mode says what was actually served: for a session this small it is { mode: "inline", items: [...] }; long or compacted sessions get a snapshot with items: null instead — check mode before reading items (see the resume guide).
  • resumed.pendingRequests — approvals and questions still waiting for you.

Pass cursor instead to resume from a specific point. The resume guide explains the cursor and history contract.

Same close(). Same clean exit.

  • MSP concepts — sessions, turns, approvals, resume, and exit codes as one mental model.
  • MSP wire protocol — the bytes underneath, if you are building a client without the SDK.
  • The generated MSP reference and SDK reference — one page per method, notification, error, type and export used above.