Name the eight message roles
Read the eight AgentMessage roles and the SystemMessage declaration behind them, including the replace field the 1.0.4 type does not have.
Pi uses the AgentMessage union in SDK state, lifecycle events, RPC responses, and
persisted session entries — one type across four surfaces. On 1.0.4 the coding
agent extends it with four roles, and applications can add still more by declaration
merging. The declaration below reproduces the exported types from
ch26-message-types/declared.ts; assert-equal.ts checks those reproductions two
ways against the real exports.
The eight roles
| Role | What it is for |
|---|---|
system |
the prompt and tool state; later system messages patch it |
user |
a prompt: a string or an array of text/image blocks |
assistant |
content blocks, usage, stopReason, the three-way model identity |
toolResult |
a tool’s result, joined to its call by toolCallId |
bashExecution |
a direct shell command — not an LLM tool result |
custom |
an extension context message, converted to a user message |
branchSummary |
the summary a branch_summary entry becomes in context |
compactionSummary |
the summary a compaction entry becomes in context |
The declaration that carries the caveat
SystemMessage is the role with the book’s most famous mismatch, so it is the one
worth showing. An excerpt of the declared reproduction — a declaration, not
executable code:
export interface SystemMessage {
role: "system";
content: string | TextContent[];
sections?: Record<string, string | null>;
toolsAdded?: Tool[];
toolsRemoved?: ToolReference[];
timestamp: number;
}declared.ts really does write these lines inside a namespace, and
assert-equal.ts proves each one is mutually assignable with, and has the same key
set as, the exported type. sections is a named map where a null value removes a
section, and toolsAdded / toolsRemoved patch the tool set without redeclaring
it.
The discrepancy the chapter records: the prose in message-types.md lists
replace?: boolean on SystemMessage and describes it as discarding earlier
state, but the exported 1.0.4 declaration has no replace field, and no shipped
JavaScript reads one. The book’s rule is that the declaration wins, so the
interface above omits it — reported upstream as earendil-works/pi#10462, framed as
a question rather than a bug.
Mechanism and limitations
Documented: the union, the role fields, and the four-surfaces statement are the
exported 1.0.4 types, verified at compile time by assert-equal.ts
(declaration-assignability, declarations scope in the evidence ledger). Two limits
carry over even from the type alone: stopReason: "pending" is a stream-only state
that Pi does not persist to session JSONL, so one type really does carry
different values on different surfaces; and a timestamp on the entry is ISO 8601
while the nested message timestamp is Unix milliseconds — same field name, two
formats. Observed: nothing here runs a model — the declaration check is the only
evidence row this chapter has, and the SystemMessage in the type says nothing
about how a real model produces these roles.
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.