From 8365e4e5b42a2233611b75f36ec5a8e72cc054bd Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Thu, 22 Oct 2026 11:38:44 -0500 Subject: [PATCH] gizmoduck: rows 270 and 271 packets: the 264c count and 264d clear contracts pinned for row 272 (Huey), and the Pi 0.85.1 compaction source contract --- agents/gizmoduck/work/queue-270/BUILD.md | 49 ++++++++++++++++ agents/gizmoduck/work/queue-271/BUILD.md | 72 ++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 agents/gizmoduck/work/queue-270/BUILD.md create mode 100644 agents/gizmoduck/work/queue-271/BUILD.md diff --git a/agents/gizmoduck/work/queue-270/BUILD.md b/agents/gizmoduck/work/queue-270/BUILD.md new file mode 100644 index 000000000..2ce11d477 --- /dev/null +++ b/agents/gizmoduck/work/queue-270/BUILD.md @@ -0,0 +1,49 @@ +# Row 270 build notes (Gizmoduck) + +264c, T1 item 7: the compaction count, exact for Pi. Gitea #1775. Brief: +docs/plans/2026-10-21_t1-sessions.md section 4 (D6), with RULINGS Q6 (Pi exact, Claude `unknown` with +the Vikunja 466 reason). Reviewer Duckworth, gate owner Sage. Branch gizmoduck/row270 from +gizmoduck/row269 2d0d407ba (sage-2797, Q220). Row 269 lands first, and this branch rebases with it. + +## Contract for 264e (pinned for Huey, row 272) + +The surface reads the count from `session.list`. Each row gains one key, `compactions`: + +```json +{ "id": "...", "at": "...", "name": "...", "predecessor": null, "default": true, + "compactions": { "count": 3, "known": true, "reason": null, "at": "2026-10-22T16:40:00.000Z" } } +``` + +- `known: true`: `count` is the exact number of completed compactions over the session's lifetime, + an integer of 0 or more. `reason` is null. +- `known: false`: `count` is a lower bound (an integer) or null when there is no bound. Show + `unknown` with the reason, never a bare 0. `reason` is one of: + - `harness-unsupported`: Claude Code, until Vikunja 466 pins its format. + - `not-collected`: no collection has run for this session yet. + - `store-unreadable`: a store file couldn't be read, or isn't a Pi session. + - `store-incomplete`: a stream file got shorter, was replaced, or has unreadable lines, so + entries may be missing. +- `at`: ISO time of the last collection, or null if none has run. +- A clear successor (264d) starts at `known: true, count: 0` once its first collection runs. The + retired session keeps its row and its count. +- On a base without 264c, the key is absent. The surface shows the count as unavailable, which is + what Huey's plan does (Sage Q1). + +No new verb and no new input key. Access is `session.list`'s: the thread owner, or the tester. + +## Pi source contract (pinned offline at 0.85.1) + +`@earendil-works/pi-coding-agent` 0.85.1 (package.json pin), read in node_modules: + +- One line per completed compaction: `{"type":"compaction","id":...,"parentId":...,"timestamp":..., + "summary":...,"firstKeptEntryId":...,"tokensBefore":...}` from `appendCompaction` + (dist/core/session-manager.js:818-833). The two callers write it after the summary exists + (dist/core/agent-session.js:1539, :1826). A start or an aborted compaction writes no entry. +- What doesn't count: a `message` entry with role `compactionSummary` (a rendered summary, + docs/session-format.md:154), any message inside a compaction's `retainedTail`, and + `branch_summary` entries. +- Identity: the stream is the `*.jsonl` file name under the session's `session/` directory, and the + event is the entry `id`. Pi's ids are unique within a file (`generateId(this.byId)`). An entry + id repeated in one file counts once. +- Partial trailing line: wait. Only lines that end with a newline are read, as + packages/conversation/src/pi.mjs:35-38 does. diff --git a/agents/gizmoduck/work/queue-271/BUILD.md b/agents/gizmoduck/work/queue-271/BUILD.md new file mode 100644 index 000000000..c7090f58b --- /dev/null +++ b/agents/gizmoduck/work/queue-271/BUILD.md @@ -0,0 +1,72 @@ +# Row 271 build notes (Gizmoduck) + +264d, T1 item 7: clear context through row 194's lifecycle stop. Gitea #1776. Brief: +docs/plans/2026-10-21_t1-sessions.md section 5 (D7, D8), with RULINGS Q4 (rebase onto 194's +landing, no 194 code copied). Reviewer Duckworth, gate owner Sage. Built after row 270 (sage-2797). + +## Contract for 264e (pinned for Huey, row 272) + +Proposed, pending Duckworth's review. Any change goes to Huey before it's pushed. + +### session.clear + +Input, every key required, with no other key (`keys()` as `session.create` does): + +```json +{ "thread": "", "session": "", "revision": 0, "confirm": true, "op": "" } +``` + +- `revision`: the integer from the session's `session.list` row (below). It names the state the + human confirmed. +- `confirm`: the literal `true`. Anything else refuses `confirm-required`. +- `op`: the caller's operation key, an identifier, unique per thread. It is also the idempotency + key. + +Answer, the operation envelope: + +```json +{ "op": "...", "thread": "...", "session": "", "successor": "", + "state": "clearing", "start": null, "refusal": null, "at": "..." } +``` + +- `successor` is bound when the request is accepted, before any asynchronous work. +- `state`: + - `clearing`: accepted. Sends and launches to the old session are fenced, and the drain or stop + is in progress. + - `uncertain`: stop proof isn't available. Capacity stays reserved and nothing is forced. It + resumes when proof arrives. + - `retired`: the old session is retired and the successor exists, in one bus transaction. + - `failed`: no retirement happened, and `refusal` names why. The old session stays as it was. +- `start` is null until `state` is `retired`. After that it is `held` (waiting for capacity), + `started`, or `refused` with `refusal` set. +- `at`: the time of the latest state change. + +Repeats and conflicts: + +- The same `op` with the same payload returns the current envelope and starts nothing new. +- The same `op` with any other payload refuses `session-op-conflict`. +- A new `op` on a session with an open clear refuses `session-clearing`. +- A `revision` that isn't current refuses `session-revision-stale`. +- Other refusals: `session-not-found`, `session-retired`, `processing-halted` (D8: a halt refuses + and changes nothing), and the owner refusals `session.create` already gives. + +### session.operation + +Input `{ "thread": "...", "op": "..." }`. Answer: the same envelope. Unknown op refuses +`operation-not-found`. A CLI that waits 30 s and then reports pending uses this to poll. A retry +queries the op here and never resends the clear. + +### session.list additions + +Each row gains: + +- `state`: `active`, `clearing`, `uncertain` or `retired`. +- `revision`: an integer. 0 at creation, plus 1 for each lifecycle event on that session. +- `successor`: the successor's id once retired, otherwise null. + +A successor row has `predecessor` set (264a's column). When the cleared session was the thread's +default, `default` moves to the successor at retirement. Clearing any other session leaves the +default where it is. + +On a base without 264d, `session.clear` refuses `unknown-verb`, and the rows have no `state`, +`revision` or `successor`.