Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
05c77e8
feat(pi): add the Pi agent type on Pi durable
salman1993 Oct 20, 2026
20964bd
feat(pi): follow the current agent-type API
salman1993 Oct 20, 2026
5bb2898
feat(pi): run on Pi durable 1.3.0
salman1993 Oct 20, 2026
753e750
feat(pi): load Buzz tools on search with Pi's tool_search
salman1993 Oct 20, 2026
62fec43
fix(pi): continue a run after its storage fails, and list sessions sa…
salman1993 Oct 20, 2026
0f82a1c
Merge remote-tracking branch 'origin/main' into sm/pi-harness
salman1993 Oct 20, 2026
63be0e3
feat(pi): run Pi agents on Windows
salman1993 Oct 20, 2026
3ba2fb5
fix(pi): keep a removed agent's queued mention from running, and surv…
salman1993 Oct 20, 2026
3e77104
feat(pi): show Pi agents' usage on the Usage page (#1442)
salman1993 Oct 20, 2026
e0a814e
fix(pi): address review of the Pi agent type
salman1993 Oct 21, 2026
47223e2
fix(pi): route adopted mentions to their thread, show pi sign-ins, ru…
salman1993 Oct 21, 2026
30e5267
Merge remote-tracking branch 'origin/main' into sm/pi-harness
salman1993 Oct 21, 2026
1a708fb
fix(pi): use no $NAME keys, and find the transcript button by its new…
salman1993 Oct 21, 2026
ab4f8be
fix(pi): read auth.json under its lock, count left runs' sends before…
salman1993 Oct 21, 2026
5aeeede
fix(pi): stop an agent's storage once another host takes its lock
salman1993 Oct 21, 2026
1847369
fix(pi): end watch and timer runs an earlier app left, rather than co…
salman1993 Oct 21, 2026
6e271c3
Merge remote-tracking branch 'origin/main' into sm/pi-harness
salman1993 Oct 21, 2026
26abc3a
fix(pi): end left one-off runs without continuing Pi first
salman1993 Oct 21, 2026
7bb1878
Merge remote-tracking branch 'origin/main' into sm/pi-harness
salman1993 Oct 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/plugin-manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ pub fn bundled_manifests() -> Vec<Manifest> {
.expect("codex manifest"),
serde_json::from_str(include_str!("../../../src/bundled/goose/manifest.json"))
.expect("goose manifest"),
serde_json::from_str(include_str!("../../../src/bundled/pi/manifest.json"))
.expect("pi manifest"),
serde_json::from_str(include_str!("../../../src/bundled/workflows/manifest.json"))
.expect("workflows manifest"),
serde_json::from_str(include_str!("../../../src/bundled/feedback/manifest.json"))
Expand Down
2 changes: 1 addition & 1 deletion crates/plugin-manager/tests/management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1134,7 +1134,7 @@ fn bundled_defaults_preserve_saved_choices_and_only_channels_is_required() {
let id = plugin.manifest.id.as_str();
let default = !matches!(
id,
"buzz.bestie" | "buzz.todos" | "buzz.usage" | "buzz.relay-notes"
"buzz.bestie" | "buzz.pi" | "buzz.todos" | "buzz.usage" | "buzz.relay-notes"
);
assert_eq!(plugin.enabled, default, "{id}");
if id == "buzz.channels" {
Expand Down
50 changes: 0 additions & 50 deletions docs/view-pi-session.md

This file was deleted.

4 changes: 4 additions & 0 deletions scripts/design-system/check-icons.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const nonIconDependencies = new Set([
"@buzz/author",
"@deepseek-ai/cordis",
"@dnd-kit/core", // Pointer drag and drop for sidebar channel moves.
"@earendil-works/chord", // The Pi host's packages, which pi.sh installs.
"@earendil-works/pi-ai",
"@earendil-works/pi-durable",
"@emoji-mart/data",
"@fontsource-variable/inter",
"@fontsource/jetbrains-mono",
Expand Down Expand Up @@ -57,6 +60,7 @@ const nonIconDependencies = new Set([
"nostr-tools",
"pele",
"postcss",
"proper-lockfile", // The Pi host's lock on each agent's storage.
"prosemirror-commands",
"prosemirror-history",
"prosemirror-model",
Expand Down
1 change: 1 addition & 0 deletions src/app/pages.integration.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ test("the app runtime exposes ready bundled pages and removes them on disable",
plugin.enabledByDefault,
![
"buzz.bestie",
"buzz.pi",
"buzz.todos",
"buzz.usage",
"buzz.relay-notes",
Expand Down
2 changes: 1 addition & 1 deletion src/bundled/claude-code/transcript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const KINDS: Record<string, string> = {
};

/** A Buzz prompt, or a steer wrapping one, as the turn's opening item. */
function prompt(id: string, at: number, raw: string): TranscriptItem {
export function prompt(id: string, at: number, raw: string): TranscriptItem {
let sections = promptSections(raw);
const steer = sections.find((section) => section.tag === STEER);
if (steer) sections = promptSections(steer.body);
Expand Down
7 changes: 7 additions & 0 deletions src/bundled/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import codexManifest from "./codex/manifest.json";
import * as codex from "./codex";
import gooseManifest from "./goose/manifest.json";
import * as goose from "./goose";
import piManifest from "./pi/manifest.json";
import * as pi from "./pi";
import channelsManifest from "./channels/manifest.json";
import githubManifest from "./github/manifest.json";
import * as channels from "./channels";
Expand Down Expand Up @@ -199,6 +201,11 @@ export const bundledPlugins: readonly BundledPlugin[] = [
module: goose,
enabledByDefault: true,
},
{
manifest: { ...piManifest, apiVersion: 1 },
module: pi,
enabledByDefault: false,
},
{
manifest: { ...workflowsManifest, apiVersion: 1 },
module: workflows,
Expand Down
167 changes: 167 additions & 0 deletions src/bundled/pi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Pi

Create agents that run Pi on your computer, with any provider Pi supports, and answer you in Buzz. Each conversation continues its own Pi conversation.

## Getting started

1. Turn on **Pi** in **Settings** → **Plugins**. Pi needs Node.js 22.19 or later on this computer.
2. Choose **New agent** → **Create a new agent**, pick **Pi** as the **Harness**, and choose its provider and model.
3. Mention your agent in a conversation to give it work.

---

# Pi for Agents

A desktop plugin that ships with Buzz on macOS, Linux and Windows, off until
turned on in **Settings → Plugins**. Its agents run on
[Pi durable](https://earendil.com/posts/pi-1-0/) (`@earendil-works/pi-durable`),
not the Pi coding agent, with any provider Pi supports.
Agents owns the identity, admission, configuration, and signed Buzz actions. This
plugin owns Pi work after delivery handover. One Pi host process ([`host.mjs`](host.mjs))
runs every Pi agent here. Each agent has its own SQLite storage, and each Buzz
conversation the agent is in is one durable Pi conversation.
Comment thread
salman1993 marked this conversation as resolved.

## Try it

From the feature worktree:

```sh
source bin/activate-hermit
BUZZODZ_PROFILE=pi-agents BUZZ_DEV_VIEWER= bin/just desktop
```

Pi needs Node.js 22.19 or later on this computer (`node:sqlite`). On macOS and
Linux the app's PATH is searched first, then an interactive login shell's, so
Homebrew, nvm, fnm and similar installs work. On Windows, `node` must be on the PATH Buzz
starts with.

1. Turn on **Pi** in **Settings → Plugins**. Then open **Agents → New agent**
and choose **Pi** as the **Harness**. Its **Model** starts on **Pi default**:
the `defaultProvider` and `defaultModel` in `~/.pi/agent/settings.json`, as
the `pi` command sets them. The first time Buzz asks Pi anything, it
installs Pi's packages (a few seconds).
2. Click the model (marked with Pi, and with the provider when Buzz has its
mark) and choose a provider. A provider Pi has no key for shows an
**API key** field; **Save to Pi** stores it in
`~/.pi/agent/auth.json`, shared with the `pi` command. A provider that signs
in some other way (OAuth, cloud credentials) says to run `pi`, then `/login`.
Choose a model, click **Test connection**, then **Use**. Name the agent and
click **Create agent**. Its library card shows the same control.
3. Open the agent. Its **Identity** tab has the **Provider**, **Model**,
**Thinking** level (Pi's default, or one the model has) and
**Instructions**. Its **Settings** tab has the provider's key with **Test
connection**, the **Workspace** (default `~/.buzz`), and thread or channel
**Conversations**.
4. In a channel, send: `@Pi create hello.txt containing hello in the workspace,
read it back, and report what you found` (use the agent's actual name).
Expect steps under your message, one reply in the thread, and the file in the
workspace. Then reply in the same thread: `@Pi what did you write?`. It
remembers, because the thread is the same Pi conversation.
5. While it works, send another mention in the thread. It steers the running
turn: Pi reads it at its next step. The activity moves to the newer message.
6. Quit Buzz during a long turn, start it again, and mention the agent in that
thread. Pi continues the interrupted run from its storage, and the new
mention steers it.
7. Choose **View activity** on one of its messages in that thread, or open the
agent's **Activity** tab from there. **This thread** is its Pi conversation,
with the agent's other recent ones below. **View transcript** shows each
turn's prompt, thinking, tool calls with their output, and what it sent,
read from Pi's storage.

The composer's model picker offers the agent's provider's models, and each
model's thinking levels, for one turn. Without a pick, a turn runs the
agent's saved model and Thinking level.

## Checks

```sh
bin/pnpm exec vitest run src/bundled/pi
PI_DEPS="$HOME/Library/Application Support/Buzz/pi/deps/<sum>" bin/pnpm exec vitest run src/bundled/pi/host.live.test.ts
```

The unit tests cover the stdio protocol (`pi.ts`), the runtime against a fake
host, the install script in a throwaway home, and the New agent and Settings
fields. The opt-in live test runs the real `host.mjs` on Pi durable, using Pi's
faux provider as the model. It checks that a final `send` ends the run, that the
model reads what a Buzz tool answered, that a plain answer comes back as text,
that a request steers a working run, that a run cut off by a stop continues in
the next host, that aborting a run leaves it unanswered, and that a deferred
Buzz tool is offered once `tool_search` finds it. `PI_DEPS` is any folder
the launcher installed Pi's packages in.

## Buzz's copy of Pi's packages

[`launch.mjs`](launch.mjs) runs the host on this computer's Node. On macOS and
Linux, [`pi.sh`](pi.sh) finds that Node and starts the launcher on it; Windows,
which has no bash, starts `node` itself. The launcher's first line on stdin
carries the host source and the packages to install, since together they pass
Windows' 32 KB command line. On first use it runs `npm ci` with
[`deps/package-lock.json`](deps/package-lock.json), which pins every package and
its integrity hash. Install scripts are off, and npm uses the registry it is
configured for. Each lock gets its own folder under
`~/Library/Application Support/Buzz/pi/deps/`
(`${XDG_DATA_HOME:-~/.local/share}/buzz/pi/deps/` on Linux,
`%LOCALAPPDATA%\Buzz\pi\deps\` on Windows). The folder appears
whole or not at all, and is shared by every Buzz build. An install removes the
folders no build has started for 30 days. Agents' storage is in `agents/<pubkey>.sqlite`
beside it. A lock on each agent's storage keeps a second Buzz build from opening
it; that build's turns fail and say so. It also keeps Pi's `auth.json` writes
from colliding with the `pi` command's.

To update Pi, change the versions in [`deps/package.json`](deps/package.json),
then regenerate the lock without registry URLs:

```sh
cd src/bundled/pi/deps
npm install --package-lock-only --ignore-scripts --omit-lockfile-registry-resolved
```

## Limits and boundaries

- Only the owner's events, or ones the owner delegated, start work. Watches,
timers and catch-ups run as standalone turns, each a one-off Pi
conversation, in the lanes and with the 5-minute deadline Buzz's agent
runner gives every harness.
- Pi has the coding tools `read`, `write`, `edit` and `bash`, in the workspace,
with no sandbox or approvals, like Claude's `bypassPermissions`. Buzz's tools
are offered as `buzz_<name>` (`buzz_send`, `buzz_read`, …), since `read` and
`edit` would collide. They run inside Buzz through the agent's native-held
handle, so the host holds no Buzz key. A final `buzz_send` ends the run.
The Buzz tools Claude and Codex load on search (the attention tools, and
those plugins add) are deferred: Pi's `tool_search` loads them when the
model looks for them, and they stay loaded in that conversation.
- **Stop** on a turn's activity aborts the Pi run it is in, and posts
nothing.
- A turn that answered in text and never called `buzz_send` has its answer
posted in the thread, as goose does. A turn that fails posts `⚠️ I couldn't
finish that: …` and tags the owner.
- A Buzz tool call that a stop cuts off runs again when the run continues, so a
`send` may be posted twice.
- Pi's storage keeps every conversation, including each one-off watch or timer
turn; nothing prunes it. The Activity tab lists thread and channel
conversations only, and rereads an open transcript when its turn ends, not
as it streams.
- A host that stops while a turn waits is started again once and waits for it
there. A run resumed that way shows no steps.
- Runs left unfinished when the app quit continue at the first turn after it
starts again, in whatever thread. A mention's run shows as working and its
answer or failure is posted where it would have been, but it shows no steps
and Stop does not reach it. A watch's or timer's run is ended instead, as
quitting ends other harnesses' turns: continued, it would have no deadline.
- Pi durable records some requests that have no answer, such as compaction's,
only in its totals. Usage counts each model's share of those as one more
request, at the newest compaction.
- A provider that needs more than a key, such as Bedrock or Vertex, is set up
with the `pi` command. Signing in from Buzz is not supported.
- A `!command` or `$NAME` key in `auth.json`, which the `pi` command runs or
reads from its environment to get the key, is not used: one host serves
every agent, and a command would hold them all, and the desktop app often
lacks the shell's environment. Its turns fail and say so; enter the key in
Buzz instead.
- Only Pi's built-in providers run. One that a Pi package adds (`packages` in
Pi's settings), such as databricks, is not loaded: the provider field, the
New agent Model field and a turn's error say so, and Use and Save wait for another.
- On Windows, Pi's `bash` tool needs a bash, such as Git for Windows'; the
other tools work without one.
- Windows, Linux, packaged-app behavior, and human acceptance need separate
validation.
Loading
Loading