pi-tuck 1.0.23 → 1.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +5 -3
- package/dist/src/build-id.d.ts +1 -1
- package/dist/src/build-id.js +1 -1
- package/dist/src/compaction.js +6 -2
- package/dist/src/diagnostics.d.ts +1 -1
- package/package.json +1 -1
- package/src/build-id.ts +1 -1
- package/src/compaction.ts +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
All notable changes to pi-tuck. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
|
|
4
4
|
[Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [1.0.24] - 2026-11-10
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **A prompt's cut could reach a run it was not planned for.** pi-tuck plans a prompt's cut when the prompt arrives. When another extension
|
|
11
|
+
handled that prompt, no run started, and the plan waited for the next run:
|
|
12
|
+
- **A run started by a custom message** carried a cut sized for the prompt that never ran: deeper than needed.
|
|
13
|
+
- **After a move in the session tree (`/tree`),** the plan was applied on the new branch. Its kept entry was not on that branch, so the request
|
|
14
|
+
lost the custom message that started the run, even on a branch that needed no cut.
|
|
15
|
+
|
|
16
|
+
Now a run started by a custom message plans afresh, and a move in the tree drops any waiting plan.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Lifecycle tests: a handled prompt, then a custom-message run, and the same with a move in the tree first.
|
|
21
|
+
|
|
22
|
+
### Release gates
|
|
23
|
+
|
|
24
|
+
- **Shipped without a live gate, because its replay matches 1.0.23:** 13,946 requests on 29 recordings, read per request, 0 differ beyond the
|
|
25
|
+
base's own run-to-run noise; clips 17 → 17; 0 lost and 0 dead tasks.
|
|
26
|
+
- **Stress, 310 cells:** findings the same as 1.0.23, cell for cell. Tests (715) and CI.
|
|
27
|
+
|
|
6
28
|
## [1.0.23] - 2026-11-10
|
|
7
29
|
|
|
8
30
|
### Fixed
|
package/README.md
CHANGED
|
@@ -64,8 +64,8 @@ Then start a new Pi session. That is all. pi-tuck takes over Pi's compaction, bo
|
|
|
64
64
|
|
|
65
65
|
- Needs Pi 1.2.0 or later (tested on 1.2.0 and 1.4.0), on macOS or Linux. On an older Pi, pi-tuck stays off, Pi's own compaction runs, and pi-tuck says so once. Full features on macOS with Apple silicon; elsewhere, compaction and word recall.
|
|
66
66
|
- On macOS with Apple silicon, the first session also downloads a local search model in the background (about 390 MB, once), and says once when it is set up. Until it is ready, search works by keyword. See [Semantic search](#semantic-search).
|
|
67
|
-
- Prefer GitHub? `pi install git:github.com/galyavin/pi-tuck@v1.0.
|
|
68
|
-
- To pin a version, add it: `pi install npm:pi-tuck@1.0.
|
|
67
|
+
- Prefer GitHub? `pi install git:github.com/galyavin/pi-tuck@v1.0.23`.
|
|
68
|
+
- To pin a version, add it: `pi install npm:pi-tuck@1.0.23`.
|
|
69
69
|
|
|
70
70
|
## Uninstall
|
|
71
71
|
|
|
@@ -131,7 +131,9 @@ Known limits today:
|
|
|
131
131
|
- A host that patches a large section into the system prompt could get a refusal that a cut would have fit (1.0.0 to 1.0.16; fixed in 1.0.17). pi-tuck plans a prompt's cut when the prompt arrives, before other extensions change the system prompt. A host engine that adds a section of thousands of tokens for one turn could push that turn's first request over the room, and the final guard refused it. Now the guard plans the cut against the system prompt as finally sent, and refuses only if that cut does not fit either ([`CHANGELOG.md`](CHANGELOG.md) 1.0.17).
|
|
132
132
|
- Semantic recall never started in `pi -p` (1.0.0 to 1.0.20; fixed in 1.0.21). pi-tuck skipped semantic recall whenever Pi ran with no UI, to keep a one-shot run from starting the first-time download, and that also skipped a runtime that was already installed. Jobs run with `pi -p` searched by keyword only, while `tuck_search` said semantic recall was being set up. RPC mode was not affected ([`CHANGELOG.md`](CHANGELOG.md) 1.0.21).
|
|
133
133
|
- pi-tuck refused requests that fit the window, and could strand a session (1.0.0 to 1.0.21; fixed in 1.0.22). It refused at the room, the window less the full reply reserve, where native Pi still sends. With Pi's defaults and a large setup, a session could be refused at every prompt. The final refusal now sits at the window less a reply floor ([`CHANGELOG.md`](CHANGELOG.md) 1.0.22).
|
|
134
|
+
- Near the 1.0.22 refusal line, replies were cut to a few hundred tokens (1.0.22; fixed in 1.0.23). 1.0.22 left only its reply floor and did not count Pi's 4,096-token margin on the reply cap ([`CHANGELOG.md`](CHANGELOG.md) 1.0.23).
|
|
134
135
|
- Cost of the 1.0.22 change: between the room and the window, the reply reserve no longer cushions a wrong count. At a cold start an undercount can reach the provider. A provider that errors is caught and learned from; a server that truncates silently truncates, as it does for native Pi. With a setup above the room, cuts are forced and frequent, each freeing little, which beats a stranded session ([`CHANGELOG.md`](CHANGELOG.md) 1.0.22).
|
|
136
|
+
- Four `tuck_search` faults, fixed in 1.0.23: it said semantic recall was still indexing when the history was fully indexed; in the turn after a prompt's cut it ranked nothing ("nothing is out of view"); a keyword-search timeout on a very long history failed the whole search, where now the semantic results still answer; and the timeout message named a pattern instead of the history's size ([`CHANGELOG.md`](CHANGELOG.md) 1.0.23).
|
|
135
137
|
- pi-tuck cut earlier than native Pi when a model's reply cap is above Pi's reserve (1.0.0 to 1.0.19; fixed in 1.0.20). It reserved the larger of Pi's `compaction.reserveTokens` and the model's `maxTokens`: with `maxTokens` 32,768 and a reserve of 16,384 on an 81,920 window, its room fell to 49,152 tokens, where native Pi compacts at 65,536 ([`CHANGELOG.md`](CHANGELOG.md) 1.0.20).
|
|
136
138
|
- A summary could drop the task a session was working on (1.0.0 to 1.0.17; fixed in 1.0.18). Until 1.0.15 a summary quoted only the session's first user turn, up to 12,000 characters, and the newest one, so in a long session the current task, an older turn followed by short steps, was clipped or dropped while a finished first task stayed whole, and agents acted on a days-old task. 1.0.16 added more quotes. From 1.0.13 to 1.0.17, separate rules decided what a summary quoted and what the kept tail held, and the prompt that set the current task, or the first prompt, could lose its opening after a cut: the stress test found it 40 times in 32 cells on 1.0.16 and 1.0.17. Since 1.0.18 one ranked list fills a cut's room, and the stress test finds no lost opening in 287 cells. Short sessions, and the SWE-bench chains behind our benchmarks, keep each task's prompt in the kept tail and never hit it. Upgrade to 1.0.18 ([`CHANGELOG.md`](CHANGELOG.md) 1.0.18).
|
|
137
139
|
- pi-tuck 1.0.16 cost more in live chains than its notes first said. Against 1.0.13 at an 81,920 window, it took 1.48 times the turns on the tasks both resolved (95% interval 1.34 to 1.64), 74% more cuts and 1.66 times the tokens, with 56 of 60 tasks resolved against 55. Replay of single-task sessions cannot show this cost. 1.0.18 replaced its quote rules; in the 1.0.18 gate it took 22.27 turns per task against 29.35 for 1.0.16 and 20.22 for the 1.0.13 arm (different pairs of runs; reported, not claimed) ([`benchmark/RESULTS-V1016.md`](benchmark/RESULTS-V1016.md), [`CHANGELOG.md`](CHANGELOG.md) 1.0.18).
|
|
@@ -190,7 +192,7 @@ A bad file is reported at session start, and pi-tuck uses the defaults.
|
|
|
190
192
|
|
|
191
193
|
pi-tuck keeps room for the model's reply. Since 1.0.20 that room is Pi's `compaction.reserveTokens` (default 16,384), the same as native Pi. It rises to the model's `maxTokens` for the rest of the branch only after a reply on that branch did not fit: a reply that stopped for length, or a request refused as a context overflow. A transient error such as a 503 keeps the native room. This is read from the current branch at each decision, so resume, fork and `/tree` follow the branch, and a new session starts at `reserveTokens` again. A host that passes its own reserve keeps its own. Before 1.0.20, pi-tuck always reserved the larger of the two, so with a large `maxTokens` it cut earlier than native Pi.
|
|
192
194
|
|
|
193
|
-
Since 1.0.22, pi-tuck refuses a request only
|
|
195
|
+
Since 1.0.22, pi-tuck refuses a request only past a line near the window. Pi sets each request's reply cap to the window less its own estimate of the request less a 4,096-token safety margin, and since 1.0.23 pi-tuck reads that margin from Pi. A request above the room goes out only if Pi's own cap still leaves the reply floor. The floor is the larger of the biggest reply seen on the branch and 3,584 tokens, capped at the model's own reply cap. After a reply that stopped for length or was refused as a context overflow, the floor is the reserve. The line never sits below the room, where 1.0.21 also sends. Before it refuses, pi-tuck cuts to the band, cuts to the room, makes a smaller summary and clips a fresh tool result, in that order. A run's request always carries the whole prompt that started the run: if it cannot fit after every reduction, pi-tuck stops the run with a notice saying to shorten the prompt or use a larger window. Each refusal, and each request sent above the room, leaves one `pi-tuck-capacity` entry in the session; it is never sent to the model, and never appears in summaries or recall.
|
|
194
196
|
|
|
195
197
|
pi-tuck estimates the request's size, and it scales that estimate by the worst gap it has seen between its count and the provider's. In a small window, an upstream that spends far more context on tool schemas (some OpenRouter upstreams do) can leave too little room. If a small window refuses, use a larger window, or pin one upstream as below.
|
|
196
198
|
|
package/dist/src/build-id.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const BUILD_ID = "
|
|
1
|
+
export declare const BUILD_ID = "c3eefa3bb76e52635b1e8394a025d3ffd6a056fc5636ed9b35f89dfddc1a322a";
|
package/dist/src/build-id.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const BUILD_ID = "
|
|
1
|
+
export const BUILD_ID = "c3eefa3bb76e52635b1e8394a025d3ffd6a056fc5636ed9b35f89dfddc1a322a";
|
package/dist/src/compaction.js
CHANGED
|
@@ -602,7 +602,9 @@ export async function installCompaction(pi, options = {}) {
|
|
|
602
602
|
const syncHistoryTools = (ctx) => { if (active())
|
|
603
603
|
applyHistoryTools(pi, historyToolsEnabled(ctx)); };
|
|
604
604
|
pi.on("session_start", (_event, ctx) => syncHistoryTools(ctx));
|
|
605
|
-
|
|
605
|
+
// A plan decided on `input` is for the branch it was decided on: moving in the tree leaves it planned for another branch, whose kept entry
|
|
606
|
+
// may not be on this one.
|
|
607
|
+
pi.on("session_tree", (_event, ctx) => { promptCut = undefined; phase = "idle"; syncHistoryTools(ctx); });
|
|
606
608
|
pi.on("session_compact", (_event, ctx) => { if (!admittedRun)
|
|
607
609
|
syncHistoryTools(ctx); });
|
|
608
610
|
pi.on("turn_start", (_event, ctx) => syncHistoryTools(ctx));
|
|
@@ -681,7 +683,9 @@ export async function installCompaction(pi, options = {}) {
|
|
|
681
683
|
promptCut = { type: "compaction", summary: compaction.summary, firstKeptEntryId: compaction.firstKeptEntryId, tokensBefore: before, details: compaction.details };
|
|
682
684
|
};
|
|
683
685
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
684
|
-
|
|
686
|
+
// A decided plan is for the prompt `input` saw. A run that a custom message starts is not that run (another extension handled the
|
|
687
|
+
// input, so no run started for it): plan afresh.
|
|
688
|
+
const prompted = phase === "decided" && event.messageType !== "custom";
|
|
685
689
|
phase = "starting";
|
|
686
690
|
runPrompt = event.messageType === "custom" ? undefined : event.prompt;
|
|
687
691
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type RecallPolicy } from './recall.js';
|
|
2
2
|
import type { SessionEntry } from '@earendil-works/pi-coding-agent';
|
|
3
|
-
export declare const DIAGNOSTIC_BUILD = "
|
|
3
|
+
export declare const DIAGNOSTIC_BUILD = "c3eefa3bb76e52635b1e8394a025d3ffd6a056fc5636ed9b35f89dfddc1a322a";
|
|
4
4
|
export interface Receipt {
|
|
5
5
|
id: string;
|
|
6
6
|
build: string;
|
package/package.json
CHANGED
package/src/build-id.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const BUILD_ID = "
|
|
1
|
+
export const BUILD_ID = "c3eefa3bb76e52635b1e8394a025d3ffd6a056fc5636ed9b35f89dfddc1a322a";
|
package/src/compaction.ts
CHANGED
|
@@ -527,7 +527,9 @@ export async function installCompaction(pi: ExtensionAPI, options: CompactionOpt
|
|
|
527
527
|
const historyToolsEnabled = (ctx: ExtensionContext) => Boolean(promptCut) || hasTuckCut(ctx.sessionManager.getBranch());
|
|
528
528
|
const syncHistoryTools = (ctx: ExtensionContext) => { if (active()) applyHistoryTools(pi, historyToolsEnabled(ctx)); };
|
|
529
529
|
pi.on("session_start", (_event, ctx) => syncHistoryTools(ctx));
|
|
530
|
-
|
|
530
|
+
// A plan decided on `input` is for the branch it was decided on: moving in the tree leaves it planned for another branch, whose kept entry
|
|
531
|
+
// may not be on this one.
|
|
532
|
+
pi.on("session_tree", (_event, ctx) => { promptCut = undefined; phase = "idle"; syncHistoryTools(ctx); });
|
|
531
533
|
pi.on("session_compact", (_event, ctx) => { if (!admittedRun) syncHistoryTools(ctx); });
|
|
532
534
|
pi.on("turn_start", (_event, ctx) => syncHistoryTools(ctx));
|
|
533
535
|
pi.on("agent_settled", (_event, ctx) => syncHistoryTools(ctx));
|
|
@@ -596,7 +598,9 @@ export async function installCompaction(pi: ExtensionAPI, options: CompactionOpt
|
|
|
596
598
|
promptCut = { type: "compaction", summary: compaction.summary, firstKeptEntryId: compaction.firstKeptEntryId, tokensBefore: before, details: compaction.details };
|
|
597
599
|
};
|
|
598
600
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
599
|
-
|
|
601
|
+
// A decided plan is for the prompt `input` saw. A run that a custom message starts is not that run (another extension handled the
|
|
602
|
+
// input, so no run started for it): plan afresh.
|
|
603
|
+
const prompted = phase === "decided" && event.messageType !== "custom";
|
|
600
604
|
phase = "starting";
|
|
601
605
|
runPrompt = event.messageType === "custom" ? undefined : event.prompt;
|
|
602
606
|
try {
|