Repository navigation
This release adds support for Pi 1.5, which makes PiHarness much faster and lighter on memory, and lets you continue a run that ended without an answer.
Highlights
- Pi 1.5 support:
agents/harness/pi,agents/models/pi-aiand the Pi tools now run on Pi 1.5. - Far fewer storage writes while streaming: Pi stores streamed answers and tool output in batches, at the latest every second, instead of on every update. Over SQLite this cuts storage transactions about 27x for long answers and 5.7x for turns with parallel tools, and CPU by about a quarter. A crash can now lose up to about a second of streamed output.
- Faster with many sessions: Pi's scheduler no longer rechecks every conversation on each commit, so on SQLite one Pi harness with 2,048 sessions went from 88 to 459 agent runs per second. Commits that arrive together now share one storage call, and a normal run makes no storage reads.
- Large answers no longer blow up memory: streaming a 500 KB tool call used to take about 20 s of CPU and a 100 MB heap, and a 140 KB answer followed by a 140 KB tool call held over 100 MB. Pi now appends new text instead of copying it on every update.
- Conversation view:
session.watch()replacessession.events(). It returns the newest transcript entries and the session's live state, such as the answer being streamed, running tools, and why the last run ended without an answer.session.entries()pages older entries. See Work with sessions. - Continue a run:
session.continue()starts a run on the transcript as it is, without a new message, for example after the model errored or the run hit its turn limit. - Turn limit:
defaults.maxTurnscaps the tool rounds a run can take in new sessions. A run that reaches it ends without an answer, with reasonturn_limit. - Crash-looping tasks end instead of parking: a tool call cut off by Pi's restart limit ends with an error result the model sees, and a model request ends its run without an answer, with reason
restart_limit. See How recovery works. - Other changes:
submit()takeswhenIdle: "reject", so an idle session refuses input instead of starting a run. Streamed tool-call arguments on the chat-completions wire that aren't valid JSON now fail the tool call with an error the model can fix. In Pi's code mode,browserToolgives scripts its run as an object, with screenshots as image blocks forimage().browserToolstops when the harness aborts its call, andBrowserSessionlets code outsidebrowserToolrun the samebrowserobject.useAgentChat().stop()now cancels server-started turns streamed without a resume handshake.
Upgrading
You only need to make changes if you use the Pi harness, agents/models/pi-ai or the Pi tools.
-
Update Pi to
~1.5.0(@earendil-works/pi-durable,pi-ai,chord) and install@earendil-works/pi-schemaalongside them. Once a conversation runs on Pi 1.5, Pi 1.4 cannot read it, so a deployment cannot roll back past this release. Roll forward to fix a bad deploy instead. -
Check pi-durable's 1.5.0 changelog for its breaking changes, such as the removal of
watchEvents(), tagged watch calls and ends, andConversationView.entriesbecoming a window over the transcript. -
Replace
session.events()withsession.watch()and render the view each time it changes. To show a transcript longer than the window, merge each value into Pi'sConversationTranscript, which pages older entries withsession.entries(). The view's entries now include those before a reset or compaction: Pi adds the reset or summary in place instead of cutting the transcript there. An idlepi.livenow keepsrunFailureafter a failed run, so checkrun === undefinedinstead of comparing it with{}.- const stream = await session.events(); - initialize(stream.snapshot); - stream.start(async (events) => events.forEach(apply)); + const watch = await session.watch(); + render(watch.value); + watch.start(async (call) => { + if (call.kind === "value") render(call.value); + });
-
Build your Pi tool schemas with
@earendil-works/pi-schema, as pi-ai no longer exports TypeBox'sType. UnlikeType.Object,obj()rejects properties it doesn't list; pass{ open: true }to allow them. To keep TypeBox instead, importTypefromtypebox.- import { Type } from "@earendil-works/pi-ai"; - const WordCount = Type.Object({ text: Type.String() }); + import { obj, str } from "@earendil-works/pi-schema"; + const WordCount = obj({ text: str() });
-
Remove calls to
retry()and handling of theparkedstatus. To run work again after a run ended without an answer, callsession.continue()or submit it again. -
Update your
onReportcallback if you pass one toHarness.open. It now takes one report tagged bykind, with the error on it.session_failedis nowstorage_failed, and a task the restart limit ends is reported astask_interruptedinstead oftask_parked.
Minor Changes
-
#2796
edf1b52Thanks @ben-reitz! - Support Pi 1.5 (~1.5.0; Pi 1.4 is no longer supported):session.watch()replacessession.events()and returns Pi's conversation view,session.entries()pages the transcript,retry()and theparkedpending status are removed as Pi now ends crash-looping tasks,submit()takeswhenIdle: "reject", and the Pi tools build their schemas with@earendil-works/pi-schema, a new optional peer, as pi-ai no longer exports TypeBox'sType. See pi-durable 1.5.0's changelog. -
#2797
77138faThanks @ben-reitz! - Two new controls for the Pi harness, from Pi 1.5:continue()starts a run without sending a new message. Use it to try again after a run ended without an answer, for example because the model errored or the run hit its turn limit.defaults.maxTurnslimits how many tool rounds a run can take. A run that reaches it ends without an answer, with reasonturn_limit.
See Pi harness.
Patch Changes
-
#2795
3e21ee4Thanks @ben-reitz! - AddBrowser.functionDocs(),BrowserSession.functions, reportnotices, opt-in scope locks (browser.session({ lock: true }),session.lock()) andconversationScope()toagents/browser, so code outsidebrowserToolcan run the samebrowserobject. See Browse the Web. -
#2795
3e21ee4Thanks @ben-reitz! -browserToolstops when the harness aborts its call, for pi, the AI SDK and TanStack AI. See Browse the Web. -
#2798
62ccd87Thanks @ben-reitz! - Pi models on the chat-completions wire (Workers AI, and OpenAI-compatible models through AI Gateway) now parse streamed tool-call arguments with pi-ai's own parser. When a model sends arguments that aren't valid JSON, the tool call now fails with an error the model can see and fix, instead of running with whatever arguments could be salvaged.See Models for pi-ai.
-
#2801
748e693Thanks @ben-reitz! - The pibrowserToolgives code mode scripts the run as an object, with screenshots as image blocks forimage(). See Browse the Web. -
#2805
baf26bcThanks @ben-reitz! -useAgentChat().stop()now cancels server-started turns streamed without a resume handshake. See Request cancellation.