Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
1ef548a
feat(bench): a typical home with extensions, long-block answers, and …
opensec-intelligence Nov 10, 2026
642be38
perf(coding-agent): read prompt templates after the first frame, and …
opensec-intelligence Nov 10, 2026
0c28c2d
perf(ai,coding-agent): build the model list once at start
opensec-intelligence Nov 10, 2026
fe4064e
perf(coding-agent,tui): start the git watcher after the first frame, …
opensec-intelligence Nov 10, 2026
e355067
perf(tui): lay out again only the last block of a streaming Markdown …
opensec-intelligence Nov 10, 2026
b397eff
perf(coding-agent): check settings, models.json and tool schemas with…
opensec-intelligence Nov 10, 2026
50d2b08
perf: load marked, chalk, node:crypto, semver and hosted-git-info whe…
opensec-intelligence Nov 10, 2026
13d1fc7
perf(coding-agent,tui): a resumed fullscreen transcript draws its end…
opensec-intelligence Nov 10, 2026
f73d7e4
perf(ai): Codex requests serialize the conversation once
opensec-intelligence Nov 10, 2026
1d2a4f4
perf(coding-agent,tui): highlight a growing code block only where it …
opensec-intelligence Nov 10, 2026
6c1ad16
feat(coding-agent): remote control off by default; the mobile UI skil…
opensec-intelligence Nov 10, 2026
dd0bc36
chore(release): Pi-Bolt 1.2.0
opensec-intelligence Nov 10, 2026
0b5ff09
fix(coding-agent): start the schema generator's formatter through Nod…
opensec-intelligence Nov 10, 2026
a1b5f95
fix(coding-agent): a /remote-control start under way opens nothing on…
opensec-intelligence Nov 10, 2026
8a70a2e
fix(coding-agent): the schema generator writes import paths with / on…
opensec-intelligence Nov 10, 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: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.1.7
1.2.0
3 changes: 2 additions & 1 deletion android/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Pi-Bolt for Android

A remote control for Pi-Bolt: pair the phone with `/remote-control` on a computer, then watch and drive the session
(prompts, steering, follow-ups, abort, model and thinking level, extension dialogs) from the phone. Protocol:
(prompts, steering, follow-ups, abort, model and thinking level, extension dialogs) from the phone. Remote control is off
on the computer until you run `pi-bolt remote-control enable` there. Protocol:
[docs/remote-control.md](../packages/coding-agent/docs/remote-control.md).

- `protocol/`: plain Kotlin/JVM. Noise IK (X25519 via BouncyCastle, AES-256-GCM, SHA-256), pairing codes, transport
Expand Down
7 changes: 4 additions & 3 deletions bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ throwaway Pi home, so runs are isolated from your Pi configuration and from each

| Tool | What it does |
|---|---|
| `benchmark.py` | Startup (`--version`), headless (`-p`) and interactive (TUI on a pseudo-terminal) scenarios. Fresh processes, interleaved round-robin. Reports wall, CPU and peak memory. |
| `benchmark.py` | Startup (`--version`), headless (`-p`) and interactive (TUI on a pseudo-terminal) scenarios. Fresh processes, interleaved round-robin. Reports wall, CPU and peak memory. `--home realistic` starts each run with a home and project as they are after daily use (43 prompt templates, 8 skills, a model store of 118 models, AGENTS.md files, a `.git` folder) instead of a blank one. `--home-extensions DIR[,DIR]` adds Pi package folders to that home (local-path entries of its `packages` setting), so every run loads those extensions; the warmup rounds fill the home's extension cache. |
| `long_session.py` | One interactive process, many prompts (default 40; 75 makes a conversation of about 4.2M tokens). Reports time and CPU per prompt, memory and request size as the session grows. |
| `early_tools.py` | A model that streams read-only tool calls (read, grep, find, ls) with more text after them, at a model's pace, on OpenAI chat completions or Anthropic messages: the time from the end of each message to the request with the tool results, and the whole prompt (`pi -p`). |
| `large_write.py` | A file written through a tool call whose arguments stream in 16 characters at a time: wall and CPU of `pi -p`, for files of 50 and 200 KB. |
| `long_answer.py` | The CPU it takes to stream Markdown answers of 5,000 to 60,000 characters in the TUI at the pace of a fast model: what a chunk costs must not grow with the length of the answer. |
| `long_answer.py` | The CPU it takes to stream Markdown answers of 5,000 to 60,000 characters in the TUI at the pace of a fast model: what a chunk costs must not grow with the length of the answer. `--file` streams Markdown files instead, such as those in `answers/`. Reports frames and, on macOS, instructions per frame. |
| `long_transcript.py` | Frame cost on a long transcript: Pi continues (`-c`) a session of 0 to 4,000 entries (thinking, Markdown, bash calls and their output, about 2.6 KB an entry; 2,292 is a session that ran for hours) and streams a Markdown answer, fullscreen and regular (`--mode`). Reports CPU, frames and, on macOS, instructions per frame: what a frame costs must not grow with the length of the transcript. `--keys` types while the answer streams and reports the echo latency. |
| `tmux_check.py` | Pi in a real tmux pane against a model streaming at human pace: time to interactive, keystroke latency, paste, streaming CPU and frames, resize, Escape to abort, idle CPU, memory. On Windows it runs `conpty_check.py`. |
| `conpty_check.py` | `tmux_check.py` for Windows, which has no tmux: the same steps and JSONL fields with Pi in a ConPTY (the pseudo-console behind Windows Terminal), plus the p99 frame time while streaming and peak memory. The bytes and frame times are what the console sends on: ConPTY keeps its own screen and sends the changes at most once a console frame. |
Expand All @@ -29,8 +29,9 @@ throwaway Pi home, so runs are isolated from your Pi configuration and from each
| `run-suite.ps1` | The same on Windows: Pi-Bolt, Pi on stock Bun, Node 22 and Node 24; `conpty_check.py` for `tmux_check.py`, and `pauses.py` (frames and GC pauses), `long_answer.py` and `large_write.py` too. Writes `environment.txt` (CPU, Windows build, Defender, power) and `summary.md`. |
| `report.py` | Draws the charts (light and dark SVG) and prints the tables, from a results folder. |
| `fake_model.py`, `fake_model_tools.py`, `fake_model_stress.py` | The scripted OpenAI-compatible model servers the tools start. |
| `harness.py` | Shared pieces: build parsing, model server, Pi home, pseudo-terminal, CPU and memory readings. |
| `harness.py` | Shared pieces: build parsing, model server, Pi home (blank, or `realistic_home()`), pseudo-terminal, CPU and memory readings. |
| `fixtures/` | Four Pi source files (MIT, from Pi) that the scripted model asks Pi to read. |
| `answers/` | Synthetic Markdown answers for `long_answer.py --file`: prose, TypeScript blocks of 150 and 400 lines, a Python block, a 300-item list, long paragraphs, a 150-row table. |
| `floor/floor.c` | The process floor (`benchmark.py --floor`): a minimal native program that takes Pi's arguments and answers each scenario as Pi does. |

`results/` holds the published runs: the raw JSONL of each tool, with a note on what was compared.
Expand Down
156 changes: 156 additions & 0 deletions bench/answers/code150.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
Here is the module.

```ts
export async function handler0(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [1]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 3: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 4 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler8(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [9]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 11: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 12 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler16(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [17]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 19: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 20 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler24(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [25]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 27: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 28 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler32(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [33]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 35: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 36 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler40(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [41]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 43: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 44 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler48(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [49]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 51: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 52 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler56(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [57]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 59: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 60 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler64(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [65]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 67: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 68 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler72(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [73]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 75: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 76 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler80(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [81]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 83: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 84 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler88(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [89]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 91: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 92 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler96(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [97]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 99: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 100 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler104(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [105]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 107: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 108 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler112(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [113]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 115: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 116 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler120(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [121]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 123: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 124 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler128(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [129]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 131: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 132 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler136(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [137]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 139: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 140 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
return Response.json({ view, count: view.length });
}
export async function handler144(req: Request, ctx: Context): Promise<Response> {
const items = await ctx.db.query<Item>("select * from items where id > $1", [145]);
if (items.length === 0) return new Response("not found", { status: 404 });
// comment number 147: map each item to its view model
const view = items.map((item) => ({ id: item.id, name: `item-${item.name}`, total: item.price * 148 }));
for (const v of view) ctx.log.info({ id: v.id }, "rendered");
```

That is all.
Loading
Loading