pi-tuck 1.0.11 → 1.0.12

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 CHANGED
@@ -3,6 +3,33 @@
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.12] - 2026-11-06
7
+
8
+ ### Fixed
9
+
10
+ - **A prompt could be lost, with no notice (1.0.10 and 1.0.11).** When a new prompt arrived over the cut point and pi-tuck's cut kept nothing of the
11
+ conversation before it, which happens when everything since the last cut is one long turn, the prompt was dropped from the request and from the
12
+ saved session. The model never saw it and usually went on with the previous task, so work you asked for could be lost, and could look answered.
13
+ The next prompt ran normally. A fresh one-prompt `pi -p` run could not hit it; a run that resumed a long session could. If you used 1.0.10 or 1.0.11
14
+ and a reply ignored what you had just asked, this is the likely cause. Now the prompt always reaches the model and stays in the session.
15
+
16
+ ### Added
17
+
18
+ - The settings stress test now fails any prompt missing from a request it opened, or from the session after it: 13 such findings on 1.0.11, none here.
19
+
20
+ ### Known issues
21
+
22
+ - Correction to the 1.0.11 notes: their count of 4 failing stress cells of 272 understated cut rhythm. With setups that leave a real summary and tail
23
+ after a cut, 1.0.11 cuts too often at small windows, freeing little each time: about 99 cuts in 200 tool rounds at an 81,920 window with a 24k setup.
24
+ It costs time and extra work. An automatic cut point that fixes it is being tested and is not in this release.
25
+ - The known issues listed under 1.0.11 still apply.
26
+
27
+ ### Release gates
28
+
29
+ - **Shipped without a live gate, as a defect that loses work.** Tests and CI passed, and the stress test passed on Pi 1.2.0 and the newest Pi.
30
+ - Replay differs from 1.0.11, as it has to: 1,124 of 13,946 requests on 29 recordings, each change starting at the first request of a run, with 0 dead
31
+ tasks, 0 refused requests and the same 9 clips.
32
+
6
33
  ## [1.0.11] - 2026-11-06
7
34
 
8
35
  ### Fixed
@@ -12,7 +39,7 @@ All notable changes to pi-tuck. The format follows [Keep a Changelog](https://ke
12
39
  - A fresh tool result is clipped in more of the cases where the request would otherwise be refused. The clip's budget is now counted in the
13
40
  same units as the request and allows for what the smallest cut keeps, so a round that fits only when clipped no longer ends the run. This
14
41
  is most of the refusals the settings stress test found.
15
- - One wrong usage report no longer refuses later prompts: a reply that reports more than the model's window is not learned from.
42
+ - A reply that reports more than the model's window no longer raises pi-tuck's estimate for later requests.
16
43
  - When a provider refuses a request as over its window, pi-tuck now says so in a notice, where the turn could read as a hang.
17
44
  - Every refusal says why.
18
45
 
@@ -30,8 +57,8 @@ All notable changes to pi-tuck. The format follows [Keep a Changelog](https://ke
30
57
  - With a setup near the band, cuts can come too often: with Pi's defaults, a 40k setup at a 65,536 window made 25 cuts in 60 tool rounds, each
31
58
  freeing little. It costs extra work, not a task. One sampled cell at a 167,739 window also trips this count. The fix is planned for the next
32
59
  patch.
33
- - A wrong usage report at or under the window can still raise the estimate for a while: in the stress cell for it, a 6,000-token prompt is
34
- refused in a 28,672-token room.
60
+ - A reply that reports more than the window reads to Pi as a silent overflow, so Pi asks for a recovery cut. When nothing can be cut, pi-tuck
61
+ refuses the next prompt: in the stress cell for it, a 6,000-token prompt in a 28,672-token room. The prompt after that runs.
35
62
  - If a provider keeps rejecting requests in a wording Pi does not class as a context overflow, or with a bare HTTP 413, pi-tuck does not recover
36
63
  on its own: the provider's error reaches you, the session stays intact, and every prompt can fail until the request shrinks. Try `/compact`.
37
64
 
@@ -1 +1 @@
1
- export declare const BUILD_ID = "828ac4821689607af006d54a41fd5960bb910bc65aa43adbd81c2f6aac25d180";
1
+ export declare const BUILD_ID = "f637995e7992c76737b37ae27dee5bb6bf47e959fd0f9677c3ae4d0c7530442c";
@@ -1 +1 @@
1
- export const BUILD_ID = "828ac4821689607af006d54a41fd5960bb910bc65aa43adbd81c2f6aac25d180";
1
+ export const BUILD_ID = "f637995e7992c76737b37ae27dee5bb6bf47e959fd0f9677c3ae4d0c7530442c";
@@ -324,6 +324,10 @@ export async function installCompaction(pi, options = {}) {
324
324
  // A missing model is configuration, not capacity.
325
325
  const missingModel = () => new Error("No context model for the request budget");
326
326
  let promptCut;
327
+ // Where the branch ended when the prompt's cut was planned: the prompt is the entry Pi appends there. A cut that keeps nothing of the conversation
328
+ // before it names no entry to keep from, and read as it stands that drops the prompt too, so the model never sees it.
329
+ let promptCutAt = 0;
330
+ const keptFrom = (cut, ctx) => cut.firstKeptEntryId || ctx.sessionManager.getBranch()[promptCutAt]?.id || "";
327
331
  let failure;
328
332
  let pendingBoundaryCommit = false;
329
333
  // Fresh tool results the room cannot hold, by call id: each request of the run carries the clip in their place; the session keeps them
@@ -523,7 +527,7 @@ export async function installCompaction(pi, options = {}) {
523
527
  if (!header)
524
528
  throw new Error("Missing native session header for the prompt's cut");
525
529
  const preview = SessionManager.inMemory(ctx.cwd, undefined, [header, ...ctx.sessionManager.getBranch()]);
526
- preview.appendCompaction(cut.summary, cut.firstKeptEntryId, 0, cut.details, true);
530
+ preview.appendCompaction(cut.summary, keptFrom(cut, ctx), 0, cut.details, true);
527
531
  return preview;
528
532
  };
529
533
  const withCut = (ctx, cut) => cutSession(ctx, cut).buildSessionProjection().messages;
@@ -533,6 +537,7 @@ export async function installCompaction(pi, options = {}) {
533
537
  // Pi's defaults on a 32k window). Each request of the run carries the cut, and the run's first boundary saves it.
534
538
  const preflight = async (ctx, incomingPart, observedPrompt, tools) => {
535
539
  promptCut = undefined;
540
+ promptCutAt = ctx.sessionManager.getBranch().length;
536
541
  if (!active())
537
542
  return;
538
543
  access();
@@ -691,7 +696,8 @@ export async function installCompaction(pi, options = {}) {
691
696
  const preview = cutSession(ctx, entry), view = Object.create(ctx, { sessionManager: { value: preview } });
692
697
  const more = decide && !event.entries.length
693
698
  ? boundaryCut({ ...event, context: { ...event.context, contextMessages: preview.buildSessionProjection().messages } }, view, reason) : undefined;
694
- const { tokensBefore: _, ...saved } = entry;
699
+ const { tokensBefore: _, ...cut } = entry;
700
+ const saved = { ...cut, firstKeptEntryId: keptFrom(entry, ctx) };
695
701
  // As for a cut Pi makes (session_compact): the history the cut leaves behind is indexed, once Pi has committed the entries.
696
702
  setImmediate(() => { semantic.indexAtCut(semanticProvider(ctx)); });
697
703
  return { entries: [...event.entries, saved, ...(more?.entries ?? [])] };
@@ -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 = "828ac4821689607af006d54a41fd5960bb910bc65aa43adbd81c2f6aac25d180";
3
+ export declare const DIAGNOSTIC_BUILD = "f637995e7992c76737b37ae27dee5bb6bf47e959fd0f9677c3ae4d0c7530442c";
4
4
  export interface Receipt {
5
5
  id: string;
6
6
  build: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-tuck",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "Deterministic compaction for Pi with no model call, plus exact recall of earlier history. Search by meaning on Apple silicon.",
5
5
  "keywords": [
6
6
  "pi-package",
package/src/build-id.ts CHANGED
@@ -1 +1 @@
1
- export const BUILD_ID = "828ac4821689607af006d54a41fd5960bb910bc65aa43adbd81c2f6aac25d180";
1
+ export const BUILD_ID = "f637995e7992c76737b37ae27dee5bb6bf47e959fd0f9677c3ae4d0c7530442c";
package/src/compaction.ts CHANGED
@@ -304,6 +304,10 @@ export async function installCompaction(pi: ExtensionAPI, options: CompactionOpt
304
304
  // before a boundary drops it, and the next prompt decides afresh.
305
305
  type CutEntry = { type: "compaction"; summary: string; firstKeptEntryId: string; tokensBefore: number; details?: unknown };
306
306
  let promptCut: CutEntry | undefined;
307
+ // Where the branch ended when the prompt's cut was planned: the prompt is the entry Pi appends there. A cut that keeps nothing of the conversation
308
+ // before it names no entry to keep from, and read as it stands that drops the prompt too, so the model never sees it.
309
+ let promptCutAt = 0;
310
+ const keptFrom = (cut: CutEntry, ctx: ExtensionContext) => cut.firstKeptEntryId || ctx.sessionManager.getBranch()[promptCutAt]?.id || "";
307
311
  let failure: Error | undefined;
308
312
  let pendingBoundaryCommit = false;
309
313
  // Fresh tool results the room cannot hold, by call id: each request of the run carries the clip in their place; the session keeps them
@@ -460,7 +464,7 @@ export async function installCompaction(pi: ExtensionAPI, options: CompactionOpt
460
464
  const header = ctx.sessionManager.getHeader();
461
465
  if (!header) throw new Error("Missing native session header for the prompt's cut");
462
466
  const preview = SessionManager.inMemory(ctx.cwd, undefined, [header, ...ctx.sessionManager.getBranch()]);
463
- preview.appendCompaction(cut.summary, cut.firstKeptEntryId, 0, cut.details, true);
467
+ preview.appendCompaction(cut.summary, keptFrom(cut, ctx), 0, cut.details, true);
464
468
  return preview;
465
469
  };
466
470
  const withCut = (ctx: ExtensionContext, cut: CutEntry) => cutSession(ctx, cut).buildSessionProjection().messages;
@@ -470,6 +474,7 @@ export async function installCompaction(pi: ExtensionAPI, options: CompactionOpt
470
474
  // Pi's defaults on a 32k window). Each request of the run carries the cut, and the run's first boundary saves it.
471
475
  const preflight = async (ctx: ExtensionContext, incomingPart: number, observedPrompt: string, tools: readonly string[]) => {
472
476
  promptCut = undefined;
477
+ promptCutAt = ctx.sessionManager.getBranch().length;
473
478
  if (!active()) return;
474
479
  access();
475
480
  if (!settingsFor().getCompactionSettings(ctx.model).enabled) return;
@@ -611,7 +616,8 @@ export async function installCompaction(pi: ExtensionAPI, options: CompactionOpt
611
616
  const preview = cutSession(ctx, entry), view: ExtensionContext = Object.create(ctx, { sessionManager: { value: preview } });
612
617
  const more = decide && !event.entries.length
613
618
  ? boundaryCut({ ...event, context: { ...event.context, contextMessages: preview.buildSessionProjection().messages } }, view, reason) : undefined;
614
- const { tokensBefore: _, ...saved } = entry;
619
+ const { tokensBefore: _, ...cut } = entry;
620
+ const saved = { ...cut, firstKeptEntryId: keptFrom(entry, ctx) };
615
621
  // As for a cut Pi makes (session_compact): the history the cut leaves behind is indexed, once Pi has committed the entries.
616
622
  setImmediate(() => { semantic.indexAtCut(semanticProvider(ctx)); });
617
623
  return { entries: [...event.entries as CutEntry[], saved, ...(more?.entries ?? [])] };