Embed an agent in your process with SDK sessions
The five in-process rules observed in sdk.test.ts: subscribe first, say steer or follow-up while streaming, and let SessionManager own the history.
An SDK session moves the agent into your own Node.js or Bun process: createAgentSession() returns a typed AgentSession, and the events you once parsed out of a pipe arrive as objects that keep the cumulative partial snapshots the JSON stream strips. Inside your process, rules the terminal used to handle silently become yours to state. Everything here is observed — sdk.test.ts drives a real AgentSession whose provider is scripted, on Pi 1.0.4.
Run it
Use Pi 1.0.4 and the example repository’s test runner. The canonical file is
examples/ch34-sdk/sdk.test.ts: it builds a real AgentSession through the shared
harness, scripts the model with the faux provider, and asserts the five rules below.
There is nothing to configure, because the model is scripted.
The shape of the stream is illustrative (not a canonical file): subscribe before prompting, because a fast completion can arrive first:
const unsubscribe = session.subscribe((event) => {
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("Explain this repository");
unsubscribe();The in-process lifecycle
| Stage | What happens | Why it matters |
|---|---|---|
subscribe() |
Attach before the prompt | A fast completion can arrive first |
prompt() |
Resolves only when the run finishes, retries included | The host never picks a completion event |
Streaming message_update |
Typed objects carrying cumulative partial snapshots |
The detail the JSON transformation strips |
steer() / followUp() |
The only legal way to queue input while streaming | prompt() without either rejects |
dispose() |
Aborts work, removes listeners, stops delivery | Skipping it leaks the agent |
Expected outcome
sdk.test.ts assertion |
What the test observed |
|---|---|
| Typed deltas arrive | Deltas join to the whole answer, more than one delta, and getLastAssistantText() returns it |
| Steer or follow-up while streaming | A plain prompt() rejects; steer() and followUp() both return "queued", and the run finishes cleanly |
SessionManager is authoritative |
A forged agent.state.messages never reached the provider; the real earlier question did |
| In-memory manager | getSessionFile() is undefined — no file is written |
dispose() |
A listener receives nothing further after disposal |
Mechanism and limitations
Documented: sdk.md (1.0.4) specifies createAgentSession(), subscribing before
prompting, the "queued" / "handled" return of steer() and followUp(),
SessionManager as the owner of the persisted or in-memory entry tree (assigning
session.agent.state.messages does not replace that context), and dispose()
releasing the agent’s subscriptions. Observed: sdk.test.ts runs all five rows
above against a real AgentSession. The chapter’s no-argument createAgentSession()
is documented, not run: it needs a real provider, and its default SessionManager
writes a session file unless you pass SessionManager.inMemory().
The deltas are scripted, so this shows the event plumbing, not a real model’s
behaviour. Embedding Pi is not a security boundary: your process’s extensions,
credentials, files and network are the agent’s, and restarting Pi means restarting
your service. It also does not show the asymmetry that SDK sessions do not load
codemode, tool_search or MCP as built-in extensions the way the CLI does.
Understand this example
Copy this prompt into your AI tool. No code runs here.
Apply this example
Copy this prompt into your AI tool. No code runs here.