Awesome Testing

Agents and harnesses · reviewed · reviewed Oct 6, 2026 · 5 min

How does Pi's agent runtime work?

Pi adapts provider streams, finalizes assistant messages, executes tool calls, and returns results to another model request. AgentSession owns persistence and continuation; a parent-linked session file retains history while a selected, compacted projection supplies model context.

A harness you can follow through the source

Pi makes the general harness model concrete: a provider adapter returns a message, a loop handles tool work, and a session runtime keeps the conversation usable over time. The model supplies a proposed next step; TypeScript code owns the surrounding transitions.

This case study follows Pi 1.0.4 at commit 28dcce2, dated 5 October 2026, the revision documented by the supplied Pi Technical Manual. The examples below are authored teaching fixtures checked against that implementation. They are not the manual's captured run, and later versions can change the behavior.

PartResponsibility in this example
pi-aiAdapt a provider's streaming response into text, tool-call blocks, and a final assistant message
pi-agent-coreCarry messages through a loop, run registered tools, and feed results into another request
AgentSessionPersist completed messages, prepare context, and handle continuation around the loop
Read toolObtain file content using ordinary executable code
Session managerAppend records and build the selected conversation's model context

Start with a small question: “What does status.txt say?” A read is needed before the model can answer from that file. That observation makes the round trip easy to follow.

A streamed call is still a proposal

During generation, the interface can show fragments of a tool call. Pi forwards those provider events as message updates. Seeing a closing brace is not the execution boundary: the final assistant message also carries the reason generation ended.

Move through the replay and open its inspector to compare the partial buffer, completed records, and last model request. Then change the first response ending to Output limit reached. The same complete-looking arguments produce an error without a read.

Explore the mechanism

Follow a read around Pi’s loop

Task: What does status.txt say? The fixed file fixture contains status: queued. Compare a normal tool-use response with an output-limit stop.

→↓←↑ContextModelSessionTool resultCompleted results feed the next request

message_end · user

The completed prompt is stored. Header and configuration records are omitted from this teaching view.

Model requests
0
Read dispatches
0
Stored messages
2
Lifecycle
In progress
Inspect the partial buffer, stored messages, and last request

Partial buffer: No streamed buffer yet

Completed message records

  1. m1 · system

    A read tool is available in this fixture.

  2. m2 · user

    What does status.txt say?

Last model request

No request yet.

Provider deltas become message_update events. Finalized messages become message_end records. Tool lifecycle events alone do not establish that an implementation ran.

An authored replay of selected boundaries in Pi 1.0.4 at 28dcce2, not a captured event stream. Messages, IDs, fragments, and replies are teaching fixtures. No model, Pi process, or filesystem tool runs. The browser computes snapshots locally; header/configuration records and many lifecycle events are omitted. There is no automatic playback.

For a normal finalized call, the loop resolves the tool, prepares and validates arguments, invokes its implementation, and returns a correlated result. That result joins the conversation before a fresh model request interprets it. One user question can therefore require two model calls.

At this revision, an assistant response stopped by the output limit rejects all its tool calls because their arguments may be truncated. The error can tell a later response to try again; our authored second response simply reports that the read did not run. A tool lifecycle event is also insufficient evidence of dispatch: the failure path emits such events while returning the error.

The loop can end before the session settles

agent_end closes one agent-loop run. AgentSession can still schedule a retry, recover through compaction, drain queued work, or honor an extension's continuation request. agent_settled marks completion of that surrounding driver. The replay has no retry or extension continuation, so it shows these as two separate final positions.

Pi also distinguishes two ways of submitting text while work is running:

QueueWhen the loop takes the messageEffect on current tools
SteeringAt its steering checkpoints, including after the current turnDoes not skip the current assistant message's tool calls
Follow-upWhen the inner loop would otherwise stopDoes not interrupt the current tools

These are scheduling choices. A new instruction does not retroactively undo an executed operation.

The file is a tree; context is one path

A Pi session's JSONL records carry IDs and parent IDs. Appending a new message under an earlier point creates another branch without deleting its sibling. The current leaf identifies which path the session manager projects into context.

In this fixture, A explains the observed status and B suggests another check. Both share one earlier read. Select each branch, inspect its context, then compact it. Compaction appends a summary and prompt checkpoint; the selected recent pair remains verbatim. Switching to an uncompacted branch restores that branch's full path.

Explore the mechanism

Keep the file, change the model’s view

Both branches share one read of status.txt. A explains the status; B suggests a check. Selecting a branch changes the leaf used to build context.

s0 · system
parent: root
u0 · user
parent: s0
a0 · assistant
parent: u0
t0 · toolResult
parent: a0
t0 branches into two user messages ↙ ↘
ua · user
parent: t0
ub · user
parent: t0
a1 · assistant
parent: ua
b1 · assistant
parent: ub
No compaction A
No compaction B
Stored entries
8
Context messages
6
Current leaf
a1
Fixture deployment
Not deployed

Context follows only the selected parent path. The sibling branch remains in the file but is absent from this request.

Next model context

  1. s0 · system

    Inspect the fixture; do not deploy.

  2. u0 · user

    Read status.txt. The host is staging-7.

  3. a0 · assistant

    read status.txt, call_1

  4. t0 · toolResult

    call_1: status: queued

  5. ua · user

    A: explain the status.

  6. a1 · assistant

    A: queued means waiting; no deployment occurred.

Persistent history

8 entries remain in append order. Selecting an earlier path does not undo an external effect.

Inspect simplified JSONL
{"id":"s0","parentId":null,"type":"message","role":"system","content":"Inspect the fixture; do not deploy."}
{"id":"u0","parentId":"s0","type":"message","role":"user","content":"Read status.txt. The host is staging-7."}
{"id":"a0","parentId":"u0","type":"message","role":"assistant","content":"read status.txt, call_1"}
{"id":"t0","parentId":"a0","type":"message","role":"toolResult","content":"call_1: status: queued"}
{"id":"ua","parentId":"t0","type":"message","role":"user","content":"A: explain the status."}
{"id":"a1","parentId":"ua","type":"message","role":"assistant","content":"A: queued means waiting; no deployment occurred."}
{"id":"ub","parentId":"t0","type":"message","role":"user","content":"B: suggest the next check."}
{"id":"b1","parentId":"ub","type":"message","role":"assistant","content":"B: inspect the queue worker; no deployment occurred."}

A locally computed projection of an authored parent-linked fixture, following the pinned Pi session semantics. IDs and records are simplified, with timestamps, usage, header and configuration omitted; this is not an importable Pi session. The summary is authored, not model-generated. One compaction per branch is allowed here; real Pi can compact repeatedly. No filesystem or deployment changes occur.

Compaction preserves raw history on disk while shortening the view supplied to the model. That distinction is useful, but it does not make summaries lossless. Our authored summary omits staging-7, which appeared in the earlier user message. An application that needs that host must keep it in an authoritative record or deliberately retrieve it.

Selecting another conversation path also does not restore files, revoke a deployment, or rewind a database. The session remembers an observation; external systems own their current state.

Inspect the compaction boundary

The pinned session manager finds the latest compaction on the selected parent path. It projects the saved system checkpoint and summary, the retained range starting at firstKeptEntryId with earlier raw system messages excluded, and entries appended after compaction. Pi converts the summary into a user message for the provider.

Our small fixture permits one compaction per branch and retains its two recent messages. It omits token estimation, context edits, metadata, and repeated compactions. Real Pi generates a checkpoint through a model request and can keep compacting as the conversation grows. Moving a leaf is an in-memory selection; without an appended record, reopening picks the file's last written entry.

Extension seams do not create isolation

Extensions can attach code at input, tool, context, and settling boundaries. Where that code runs determines what it can influence: a pre-tool decision can stop a dispatch; a post-tool handler observes an effect that already occurred.

Pi's pinned security guide distinguishes project trust from execution isolation. Trust gates loading of project resources at startup. Enabled tools and extensions still use the permissions of the process's operating-system account unless an actual isolation boundary constrains them. The working directory selects resources and default paths; it does not itself confine access.

The useful mental model is now concrete: a stream becomes a completed message, executable code creates an observation, the observation enters another request, and persistent history supplies only a selected view of the past. Those responsibilities belong to different parts of Pi even when the terminal presents one continuous conversation.

Sources and further reading

  1. 01
    Pi 1.0.4: agent loop and tool-call executionEarendil Works / Pi maintainers · documentation · published Oct 5, 2026 · source checked Oct 6, 2026

    Pinned implementation of stream finalization, tool dispatch, truncation rejection, steering and follow-up polling.

  2. 02
    Pi 1.0.4: AgentSession persistence and settlingEarendil Works / Pi maintainers · documentation · published Oct 5, 2026 · source checked Oct 6, 2026

    Pinned session runtime shows completed-message persistence, retry and compaction continuation, and the settled boundary.

  3. 03
    Pi 1.0.4: session tree and context projectionEarendil Works / Pi maintainers · documentation · published Oct 5, 2026 · source checked Oct 6, 2026

    Parent-linked append records and compaction-aware projection distinguish stored history from the active model context.

  4. 04
    Pi 1.0.4: context compactionEarendil Works / Pi maintainers · documentation · published Oct 5, 2026 · source checked Oct 6, 2026

    Implementation of context thresholds, retained ranges, and model-generated summaries that preserve raw session history.

  5. 05
    Pi 1.0.4: project trust and execution boundariesEarendil Works / Pi maintainers · documentation · published Oct 5, 2026 · source checked Oct 6, 2026

    Official pinned guide distinguishes project-resource startup trust from operating-system permissions and actual isolation.