Schedules can now run every day or every week at a local time, in the timezone you choose. Worktree cleanup can finally delete the worktrees in a plan you reviewed, not just list them. Most of this release makes background results harder to lose: headless sessions, pi-web, timed-out children, and outputSchema children all hand back what they produced. Two Pi sessions working side by side also stop overwriting each other's settings and schedules.
Highlights
- Daily and weekly schedules at a local time, in a timezone you choose, on the weekdays you choose.
worktree.cleanupdeletes the worktrees in a plan you reviewed. It checks everything again first, never deletes branches, and always keeps worktrees from older versions.- Background results are no longer lost in
pi -p, SDK hosts, or when pi-web replaces a session it thinks is idle. - A child that times out or crashes returns the text it had written so far, and a child with
outputSchemasaves its structured result to the output file. - Two sessions running at once no longer overwrite each other's settings, schedules, or schedule history.
Changelog
Added
- Schedules accept
every: "day"orevery: "week"with a localHH:mmtime and an IANA timezone, plus the weekdays to run on for weekly schedules. If a daylight-saving change skips the time, that run is skipped. If the time happens twice, it runs once. After a restart, the next run is calculated again from the local date. Overlap, catch-up, quiet, and mission options work the same as on other schedules. Thanks to @quifox for #815.
Changed
worktree.cleanupcan now apply a plan you saved and reviewed, once you authorize discarding those worktrees. Before it deletes anything, it checks Git state and ownership again. It keeps any worktree whose path now points outside the directory it was created in, any worktree that is locked or has been resumed, and any ignored files. It never deletes local branches, and each plan can be applied only once. Worktrees created before this version are always kept. Thanks to @quifox for #1622.
Fixed
- In a headless session (
pi -p, or an SDK host without a UI), a background run's completion message could be lost. The turn could end before the message was sent, and the host then closed the session. Finished results are now handed to Pi before the turn ends, and Pi runs the completion turn next. Thanks to @felores for #2644. - pi-web could see a session as idle while a child's result was still on its way to the parent, so it could replace the session and lose the result. The session now counts as busy until Pi starts processing the result. Thanks to @brettinternet for #2674.
- A child with
outputSchemaand anoutputpath wrote its final text to that file whenever the text was not empty. A short closing line such as "Enough; writing up." could replace the structured result. If the child returns a structured result and does not write the file itself, the file now holds that result as indented JSON. The child's returned output and output artifact show the same JSON. Thanks to @tara-marchand for #2672. - A child that timed out or crashed partway through a reply lost the text it was still writing. The parent now gets that text, labeled
Partial output before timeout:orPartial output before child error:, and the result hasoutputPartial: true. The run still fails. The partial text is never saved as the output file and never counts as acceptance evidence. Runs that are stopped, interrupted, aborted, or exit with an ordinary error are unchanged. Thanks to @yanqianglu for #2653 (#2549). - An async workflow could keep its
maxActiveAsyncRunsPerSessionslot forever after it ended. This happened when one of its async children failed and there was no record that the child's process had exited, for example after a reload. The newcapacity.abandonedSlotReleaseAfterMssetting frees the slot once the workflow has been over for longer than that time and each such child has failed, has no running runner, and has been inactive for a while. Set it tofalseto keep the slot. Thanks to @jihunkim0 for #2658. - Two Pi sessions that changed extension settings at the same time could lose one of the changes. Agent override saves,
/subagents-load-profile, and Watchdog setting changes now locksettings.json.write-lockbeside the settings file while they read and save, so the second session waits for the first. If the settings directory is read-only, the lock cannot be created and the save fails with an error. Thanks to @quifox for #2676. - Two sessions recording runs for the same schedule could each rewrite its run history from an old copy. A run could disappear from the history and its completion was never recorded, so the schedule stayed marked as running. History updates now use a short lock. Completion, restore, and delete now read the record of the run that is actually active. Thanks to @quifox for #2677.
- A session that lost the race to start a scheduled run could save its outdated copy of the schedule over the copy used by the session running it. That no longer happens. The session that lost still protects the active run from being deleted, and only postpones its own timer while the other session starts the run. Thanks to @quifox for #2675.
- If a session claimed a scheduled run but could not write the run id into the claim, for example because the disk was full, the empty claim file stayed behind. Every later run of that schedule was then skipped. The failed claim is now removed unless another session has already replaced it, and the original error is reported. Thanks to @quifox for #2678.
- On Windows, some file checks lost precision on large file ids, so two different files or folders could look like the same one. A schedule claim that failed to write could then delete another session's lock, and the agent-memory and worktree-cleanup checks could treat two directories as one. File ids are now compared exactly. (#2679, #2685)
- An agent that named a built-in MCP server with
_in place of-, such asmcp:codebase_memoryfor the servercodebase-memory, got none of the server's tools. The launch failed 10 seconds later saying the tools did not register. Both spellings now work, the same way Pi's MCP treats-and_as the same server. Thanks to @trellis-zz for #2668. workflow: truefailed with "found 0" when the model marked its script as plain```jsinstead of```js workflow. If a reply has exactly one plainjsblock and nojs workflowblock, that block now runs. When no block is found, the error shows the exact opening line it looked for and suggests passing a script file path instead. Thanks to @thetechpadawan for #2659.workflow: truealso said "found 0" when the model's reply reached the session with no text at all, which made it look as if the model had left out the script. Some providers drop the reply's visible text before Pi saves it. The error now says the reply text never arrived and suggests passing a script file path. (#2669)- Creating an agent with a description longer than one line, or updating only the prompt of such an agent, saved only the first line of the description. The description is now saved as a YAML block, so every line is kept. Thanks to @March-7 for #2681.
- When jobs leave a crowded async widget, it now shrinks to fit the jobs that remain instead of keeping blank rows. While the same jobs are listed, its height still stays fixed. Thanks to @tara-marchand for #2651.
- The main watchdog no longer flags work you asked for mid-run as scope drift. Messages you send while the agent is running (steer or follow-up) now count as part of the task. Its review and
watchdog_diffnow also say that the list of changed paths covers the whole working tree. That list can include edits made before the session or by another session in the same checkout. Thanks to @tara-marchand for #2652.