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.
What you need
Section titled “What you need”-
Node 20 or newer.
-
The
@muse-code/sdkpackage, published on npm:Terminal window npm install @muse-code/sdkIt has zero runtime dependencies — the only thing it needs at run time is a
musebinary 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
musebinary. 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
museonce and log in, or point your program’sHOMEat a profile that already has credentials. A host with no credentials fails every turn.
1. Spawn the host
Section titled “1. Spawn the host”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.
2. Complete the handshake
Section titled “2. Complete the handshake”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) => { /* ... */ });3. Start a session
Section titled “3. Start a session”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.turnIdThe 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.
5. Answer a permission request
Section titled “5. Answer a permission request”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 === trueapproval/resolved follows, naming the same approvalId. The turn then
continues. The approvals guide covers the full
requirement and choice model.
6. Cancel a running turn
Section titled “6. Cancel a running turn”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".
7. Shut the host down cleanly
Section titled “7. Shut the host down cleanly”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.
8. Reload the session in a new process
Section titled “8. Reload the session in a new process”Spawn and handshake exactly as before, then:
const resumed = await msp.connection.command( "session/resume", { sessionId, excludeItems: false },);You get back:
resumed.session— the samesessionId, itsworkspaceRoot,status,turnCount,providerIdandmodelId.resumed.viewCursor— where the session now is.resumed.history— the conversation so far.history.modesays what was actually served: for a session this small it is{ mode: "inline", items: [...] }; long or compacted sessions get a snapshot withitems: nullinstead — checkmodebefore readingitems(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.
9. Shut down again
Section titled “9. Shut down again”Same close(). Same clean exit.
Where to go next
Section titled “Where to go next”- 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.