pi-posthorse 0.5.0 → 0.7.0

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.
Files changed (4) hide show
  1. package/README.md +41 -47
  2. package/index.ts +726 -317
  3. package/package.json +12 -9
  4. package/ui.ts +17 -7
package/README.md CHANGED
@@ -2,61 +2,61 @@
2
2
 
3
3
  **Fresh context. Same journey.** (POST-horse)
4
4
 
5
- Native, no-summary context windows for the [`fitchmultz/pi`](https://github.com/fitchmultz/pi) fork of [Pi](https://github.com/earendil-works/pi). A post-horse was swapped in at relay stations so the courier and the message could continue on fresh legs. Posthorse does the same for a model: fresh context, same work, complete recoverable transcript.
5
+ No-summary context rollover for [Pi](https://github.com/earendil-works/pi), on official Pi and on the [`fitchmultz/pi`](https://github.com/fitchmultz/pi) fork. A post-horse was swapped in at relay stations so the courier and the message could continue on fresh legs. Posthorse does the same for a model: fresh context, same work, complete recoverable transcript.
6
6
 
7
7
  ![Posthorse flow](https://raw.githubusercontent.com/fitchmultz/pi-posthorse/main/diagram.png)
8
8
 
9
9
  Pi owns the persisted boundary. Posthorse owns the policy: stable window guidance, one best-effort checkpoint reminder, `new_context`, `get_context_remaining`, durable `notes`, and window-aware `history`. A rollover removes the old window from active model context while the JSONL transcript stays append-only and complete.
10
10
 
11
- An isolated [Codex prototype](adapters/codex-posthorse/README.md) tests local notes and recovery with Codex's native context reset. It is not ready for everyday use and is not included in the Pi package.
12
-
13
11
  ## Requirements
14
12
 
15
- - Node `>=22.19.0`.
16
- - The `fitchmultz/pi` fork. The native qualification target is `8fb7886130ff1fedc415bdd6fea03aef8a8957d8` (fork package version `0.87.0`). Posthorse needs the fork's native `context_window` entries, its `session_before_auto_compact` hook, `ctx.getCompactionSettings()`, and the coding-agent SDK's `publishLocalFile` export.
17
- - Official, unpatched Pi is unsupported. Posthorse reports a clear extension error at session start and cannot operate; Pi itself keeps running.
13
+ - Node `>=24.12.0`.
14
+ - Official Pi (`@earendil-works/pi-coding-agent`) or the `fitchmultz/pi` fork. Posthorse detects the host when it loads; no configuration is needed.
18
15
 
19
- ## Install
16
+ Qualified against official Pi `0.87.1` and fork commit [`b1b4ac34d807b577d66f5879119a9ed4833c8ed7`](https://github.com/fitchmultz/pi/commit/b1b4ac34d807b577d66f5879119a9ed4833c8ed7). CI uses these exact versions.
20
17
 
21
- Build the fork:
18
+ ## Install
22
19
 
23
20
  ```bash
24
- git clone https://github.com/fitchmultz/pi.git
25
- cd pi
26
- git checkout 8fb7886130ff1fedc415bdd6fea03aef8a8957d8
27
- npm install --ignore-scripts
28
- npm run build
21
+ pi install npm:pi-posthorse # from npm
22
+ pi install git:github.com/fitchmultz/pi-posthorse # from Git; add @<tag> to pin a release
23
+ pi -e npm:pi-posthorse # try it for one run without installing
29
24
  ```
30
25
 
31
- After updating the fork, run the install and build commands again, then restart Pi. `pi --version` reads the checkout's package metadata, so it does not prove that the updated source has been built.
26
+ Update with `pi update npm:pi-posthorse` or `pi update --extensions`; move a pinned Git install with `pi install git:github.com/fitchmultz/pi-posthorse@<new tag>`. Uninstall with `pi remove npm:pi-posthorse` (or the Git source you installed). Removing the package leaves `.pi/notes` and Pi's session history in place.
32
27
 
33
- Run it as `node packages/coding-agent/dist/bundle/cli.js`, or run `npm link` inside `packages/coding-agent` so that build becomes your `pi` command.
28
+ Restart Pi after installing or updating Posthorse to load the new extension code.
34
29
 
35
- Then install Posthorse with that `pi`:
30
+ Keep exactly one copy loaded. `pi list` shows every package source; if a second source or a local checkout is listed, `pi remove` it, otherwise two copies register the same tools and compete for the same rollover hook.
36
31
 
37
- ```bash
38
- pi install git:github.com/fitchmultz/pi-posthorse # from Git; add @<tag> to pin a release
39
- pi install npm:pi-posthorse # from npm
40
- pi -e git:github.com/fitchmultz/pi-posthorse # try it for one run without installing
41
- ```
32
+ ## Official Pi and the fork
42
33
 
43
- Update with `pi update npm:pi-posthorse` or `pi update --extensions`; move a pinned Git install with `pi install git:github.com/fitchmultz/pi-posthorse@<new tag>`. Uninstall with `pi remove npm:pi-posthorse` (or the Git source you installed). Removing the package leaves `.pi/notes` and Pi's session history in place.
34
+ Both hosts get the same tools, guidance, reminders, notes, history, and recovery records. The rollover itself differs:
44
35
 
45
- Restart Pi after installing or updating Posthorse to load the new extension code.
36
+ - **Fork.** Rollover is a native `context_window` entry: the old window leaves model context and only a short boundary message plus the handoff remains. Posthorse claims automatic rollover through `session_before_auto_compact`, before Pi prepares a summary or resolves summarization credentials, bounds retained tool receipts before the fresh window is published, reads Pi's live compaction settings, and lets native background tool calls continue across the reset.
37
+ - **Official Pi.** Rollover is a compaction entry that keeps no earlier conversation and whose summary is the handoff; no summary model is called. `new_context` commits at turn end after every tool in the batch succeeds. Automatic rollover answers Pi's `session_before_compact` for threshold and overflow triggers; manual `/compact` stays Pi's own summary.
46
38
 
47
- Keep exactly one copy loaded. `pi list` shows every package source; if an older entry such as `git:github.com/fitchmultz/pi-headroom.git` or a local checkout is still listed, `pi remove` it before installing the npm package, otherwise two copies register the same tools and compete for the same rollover hook.
39
+ Official Pi limits that the fork removes:
40
+
41
+ - The handoff appears as Pi's compaction-summary message.
42
+ - Automatic rollover runs only when Pi's compaction preparation finds something to compact and summarization credentials resolve. With the default `keepRecentTokens`, a small overflow or truncated response, or a second large tool result without a new user turn, can skip it; lowering `keepRecentTokens` narrows that gap.
43
+ - Reminders and `get_context_remaining` read persisted settings (a CLI snapshot), because extensions cannot read Pi's live settings. Automatic rollover itself uses the live settings Pi passes to the hook. SDK hosts can pass their live settings with `createPosthorse(getPolicy)`.
44
+ - Retained tool receipts are not shortened before the fresh window.
45
+ - Sessions are not migrated between hosts. Official Pi ignores fork `context_window` entries, so a fork session opened in official Pi can bring earlier history back into context.
48
46
 
49
47
  ## How it works
50
48
 
51
49
  1. **Stable guidance.** Window behavior lives in a native system-prompt section. There is no per-request meter. Posthorse preserves custom prompts and falls back to appending guidance when an earlier extension replaces the full prompt; that compatibility path can prevent Pi from preserving an additive prompt prefix.
52
- 2. **One best-effort checkpoint.** While Pi compaction is enabled, one reminder may appear shortly before Pi's rollover line. A large turn, overflow, restart, or smaller model can reach rollover without it. Reminders are fingerprinted by window, context size, and reserve, so switching to a different context size gets a fresh reminder. Stale reminders and reminders left after disabling compaction are filtered from model input while remaining in history.
53
- 3. **`get_context_remaining`.** Reports the best available native estimate of tokens until Pi's automatic rollover line and until the configured context limit. That configured limit is not a measured provider rejection boundary. Pi's value is an estimate until the active model reports usage.
54
- 4. **`new_context`.** Requests an atomic rollover after the complete tool batch succeeds. An optional handoff is persisted and becomes the first state of the fresh window. If a sibling tool in the same batch fails, Pi does not commit the boundary; the checkpoint reminder still applies.
55
- 5. **Automatic rollover without summaries.** With a supported context budget and room for a recovery record, Posthorse claims Pi's automatic threshold and overflow trigger through `session_before_auto_compact`, before Pi resolves summarization credentials or prepares a summary. Oversized first turns and tool results can then roll over even without summarization credentials. Otherwise Pi's own compaction remains in control. Manual `/compact` is unchanged.
56
- 6. **Bounded recovery record.** The automatic handoff keeps direct user inputs, `ask_question` outcomes, visible coordination messages, and the trailing tool batch without a later complete assistant response: call arguments, bounded result text, and entry ids to recover the rest. Failed or interrupted requests may already have received those results. A clearly labeled, possibly stale older checkpoint comes last. Older assistant prose and earlier tool batches are not treated as current state. Newly submitted input stays separate and is saved after the boundary, not copied into the handoff.
57
- 7. **`notes` and `history`.** Notes are shared across linked worktrees, at the main checkout for conventional Git layouts or inside the common Git directory when metadata is stored separately. History searches normalized transcript text and returns stored images for a requested entry.
50
+ 2. **One best-effort checkpoint.** While Pi compaction is enabled, one reminder may appear shortly before Pi's rollover line, asking the model to rewrite its current-state note in place and call `new_context`. A large turn, overflow, restart, or smaller model can reach rollover without it. Reminders are fingerprinted by window, context size, and reserve, so switching to a different context size gets a fresh reminder. Stale reminders and reminders left after disabling compaction are filtered from model input while remaining in history.
51
+ 3. **`get_context_remaining`.** Reports the best available native estimate of tokens until Pi's automatic rollover line and until the configured context limit. That configured limit is not a measured provider rejection boundary. Pi's value is an estimate until the active model reports usage. The checkpoint reminder marks the deadline, so the guidance does not prompt routine budget checks; the tool is described for use before a large step late in a window.
52
+ 4. **`new_context`.** Requests an atomic rollover after the current foreground (synchronous) tools succeed. Items 4–6 and the receipt bounding below describe the fork; official Pi differences are listed above. An optional handoff is persisted and becomes the first state of the fresh window. A failed synchronous checkpoint save or other foreground sibling prevents Pi from committing the boundary; the checkpoint reminder still applies. Native asynchronous background calls continue across the reset without delaying it. Pi preserves their call provenance and delivers their results in the fresh context.
53
+ 5. **Automatic rollover without summaries.** With a supported context budget and room for a recovery record, Posthorse claims Pi's automatic threshold and overflow trigger through `session_before_auto_compact`, before Pi resolves summarization credentials or prepares a summary. Oversized first turns and tool results can then roll over even without summarization credentials. Native asynchronous background calls can remain in progress across automatic rollover too. When a recovery record cannot fit but Pi will retain native receipts in the next window, Posthorse claims a window without a handoff so Pi can prepare and bound those receipts or refuse the window. Already-consumed receipts do not trigger an empty rollover. Otherwise Pi's own compaction remains in control. Manual `/compact` is unchanged.
54
+ 6. **Bounded recovery record.** The automatic handoff quotes the window's first owner request, its latest owner input (a direct user input or `ask_question` outcome), and its latest input of any kind, including visible coordination messages. It adds tool result evidence: tool names and namespaces, call ids, requested and admitted execution arguments when available, result excerpts of up to 1,500 characters, and entry ids to recover the rest. Every other current-window input gets one index line with its label, time, and entry id; owner inputs keep a one-line preview there. Native async results from the current projected window remain eligible even across later complete assistant responses, since a result can arrive during a response that never received it. A result Pi still projects is also eligible when its call was edited out or predates the window, without attributing it to another call. These results may already have been received or handled; they are not proof of current progress. Synchronous results with projected call provenance are kept only from the trailing batch without a later complete response. Pi context edits also apply to these inputs and results: omitted entries stay out, and replacements supersede their original content. A clearly labeled, possibly stale older checkpoint comes last. Older assistant prose is not treated as current state. Newly submitted input stays separate and is saved after the boundary, not copied into the handoff.
55
+ 7. **`notes` and `history`.** Notes are shared across linked worktrees, at the main checkout for conventional Git layouts or inside the common Git directory when metadata is stored separately. History searches normalized transcript text, skips entries whose full text the model already sees in its active context (and says how many), and returns stored images for a requested entry. Edited-away originals and checkpoint reminders stay searchable. Tool calls and results retain names, namespaces, and call ids; admitted execution arguments are labeled separately from requested arguments when present. When a handoff carries edited content, its entry ID points to the replacement; `history read` of that ID returns the replacement text or images. Reading the original entry ID still returns the raw journal content.
56
+
57
+ Before each fresh window becomes active, Posthorse bounds oversized retained native receipts through append-only context edits. It shares the available capacity across all receipts after accounting for the prompt, tools, pending input, handoff, carried calls, Pi's configured reserve when automatic rollover is enabled and supported, and a safety margin. Pi separately clamps each provider request's output limit to its remaining context capacity. When receipts need shortening, excerpts use half the remaining capacity so the next response has working room. Excerpts retain the native call/result identity and error state, with `history read` references for complete recovery, including images. Existing replacements remain the recovery source across later windows; raw pre-edit content is never used for a new excerpt. Already-fitting receipts stay unchanged. If fixed input and recovery references cannot fit, rollover fails visibly without publishing a window marker or any proposed edits. Later prompts retry preparation against the unchanged window; correcting the configuration allows ordinary rollover to proceed. Pi independently refuses estimated native transcript input that exceeds physical context capacity, including when compaction is disabled. Its output clamp still permits fitting requests with reduced output, even above the compaction reserve line. Raw wire replacements through `onPayload` / `before_provider_request` and custom transport implementations retain responsibility for their own request sizing.
58
58
 
59
- At turn end, Posthorse checks whether usage is in the reminder band before explicitly looking up the full branch. Context filtering skips its branch lookup when model input contains neither `posthorse-reminder` nor legacy `headroom-reminder` messages. Complete history remains available through paged searches and reads.
59
+ At turn end, Posthorse checks whether usage is in the reminder band before explicitly looking up the full branch. Context filtering skips its branch lookup when model input contains no `posthorse-reminder` messages, and on the fork while the window's marker message is present. Complete history remains available through paged searches and reads.
60
60
 
61
61
  Automatic recovery is an emergency input record, not proof of progress. The fresh model is told to restore notes and todo state, inspect history when needed, and verify live state before taking stateful or external action.
62
62
 
@@ -66,7 +66,7 @@ Posthorse follows Pi's effective `compaction` settings, including Pi's decision
66
66
 
67
67
  The model's context window minus `compaction.reserveTokens` must leave at least 10,000 usable tokens. Below that (for example an 8K or 16K model with the default 16,384 reserve) Posthorse reports an unsupported configuration in the guidance and in `get_context_remaining`, turns automatic behavior off for that model, and leaves Pi's own compaction in place. Lower the reserve or use a larger model. The checkpoint reminder band is the last 10% of usable context, capped at 32,000 tokens, so a large reserve cannot trigger a reminder immediately in a fresh window.
68
68
 
69
- Explicit and automatic handoffs are capped at 20,000 characters and half the active model's fresh operational capacity after prompt/tool overhead and any pending input, whichever is smaller. Oversized explicit handoffs are rejected with an instruction to save fuller state in notes; automatic rollover stays with Pi when no safe recovery record fits. When usage is not known yet, notes and history pages use the same model-aware limit.
69
+ Explicit and automatic handoffs are capped at 20,000 characters and half the active model's fresh operational capacity after prompt/tool overhead and any pending input, whichever is smaller. Oversized explicit handoffs are rejected with an instruction to save fuller state in notes; automatic rollover stays with Pi when no safe recovery record fits and no native receipts require fresh-window preparation. Notes and history pages are capped at 40,000 characters and shrink to the remaining budget; when usage is not known yet, they also leave half the fresh operational capacity free.
70
70
 
71
71
  Only one automatic compaction or rollover policy extension should be enabled at a time. Pi keeps the last non-cancel result from multiple handlers of the same hook, so load order would otherwise decide which policy wins.
72
72
 
@@ -74,8 +74,8 @@ Only one automatic compaction or rollover policy extension should be enabled at
74
74
 
75
75
  - `new_context({ handoff? })`
76
76
  - `get_context_remaining()`
77
- - `notes({ op, ... })`: `list`, `read`, and `search` are paged; continue with the returned character `offset`. Search excerpts center on the match. `write` replaces content (empty content clears); `append` adds one newline-terminated record.
78
- - `history({ op, ... })`: `search` continues with the returned `cursor` and the same `query` and `all` scope; `limit` is the maximum results per page. `read` returns stored images with the first page and the next character `offset` when text remains. Results keep native entry and window ids.
77
+ - `notes({ op, ... })`: `list`, `read`, and `search` are paged; continue with the returned character `offset`. `list` shows notes newest first with size and modification time (UTC) and takes an optional folder `path`; `read` of a folder returns its listing. `search` covers text notes, skipping binary files (a NUL byte in the first 8,000 bytes, as Git decides), and its excerpts center on the match. `write` replaces content (empty content clears); `append` adds one newline-terminated record.
78
+ - `history({ op, ... })`: `search` skips entries whose full text is already in the active context, continues with the returned `cursor` and the same `query` and `all` scope, and treats `limit` as the maximum results per page. `read` pages text and stored images; continue with the returned character `offset` and `imageOffset`. An image-only continuation can use an `offset` equal to the text length. If a page needs fresh context, retry with both offsets unchanged. Results keep native entry and window ids.
79
79
 
80
80
  In the TUI, tools use Pi's native expandable cards. Collapsed cards show the operation and target, a short content preview, and counts or page ranges with the next offset when more remains. Expand with Pi's tool-output shortcut (`Ctrl+O` by default), or click the card's header or body in fullscreen mode. Expanded cards show the complete returned page and its metadata, not content the tool has not fetched yet. Writes and context requests also show the submitted content or handoff.
81
81
 
@@ -85,37 +85,31 @@ Notes list/search and history search share the remaining context budget with rea
85
85
 
86
86
  `history search` puts matching original content before recovery material: handoffs, compaction and branch summaries, checkpoint reminders, and `notes`, `new_context`, and `history` calls/results. Ordinary prose or another tool call in the same assistant entry keeps its priority when that content matches. Every entry remains searchable; `history read` returns the complete normalized entry, including any recovery content omitted from a search excerpt.
87
87
 
88
- Within each group, current-branch matches are newest first. With `all: true`, Posthorse searches every session file in the active Pi session directory, newest-modified sessions first and newest entries within each session; this is not a global timestamp sort. The per-page result limit applies after priority, so newer echoes cannot displace older original matches. Entries copied by a fork are reported once. Search cursors advance past the previous entry, including partial headers or excerpts, so appending lookup calls and results does not keep pagination alive forever. Searches are live: external session edits or file reordering can change results; start a new search to include newer sources.
88
+ Within each group, current-branch matches are newest first. With `all: true`, Posthorse searches session files in the active Pi session directory whose saved working directory matches the current one, along with their nested subagent sessions and the selected current session file when it is in that directory. Files without a valid session header or an attributable working directory are excluded; the active branch remains available. Within that scope, sessions are ordered newest-modified first and entries newest first; this is not a global timestamp sort. All-session hits use a file-qualified ID (`native-id@file-key`, with a 10-character key); pass the complete ID to `history read`. Full-length keys printed by earlier releases still read. Hits from nested subagent runs are tagged `[subagent]`, because their user-role input came from a parent agent. A read that resolves a bare ID in another session file prints that entry's qualified ID, so continuations reopen the same file. When ripgrep is available (Pi's managed copy, then `PATH`), all-session search and bare-ID reads skip session files that cannot contain the query or ID. A query that could match text session files store escaped (quotes, backslashes) or label text that exists only in normalized entries (brackets, parentheses, braces, colons, `$`, leading or trailing spaces, or phrases such as "Call ID" and "No handoff") scans every file instead. Image summaries come only from entries with an image block, so image-count and comma queries also search every file that has one. Either way, results match a full scan. The per-page result limit applies after priority, so newer echoes cannot displace older original matches. Each included session file contributes its own matches, including fork copies, so unrelated entries sharing a native ID are never hidden. Search cursors advance past the previous entry, including partial headers or excerpts, so appending lookup calls and results does not keep pagination alive forever. Searches are live: external session edits or file reordering can change results; start a new search to include newer sources.
89
89
 
90
- Notes live in `.pi/notes/` at the main checkout for conventional Git layouts, or the current directory outside Git. When Git metadata is stored separately, including in submodules, all checkouts use `.pi/notes/` inside Git's common directory instead; no configuration is needed. If that metadata is unavailable, notes stay local to the checkout. Old checkout-local notes are imported when that checkout is accessed, without overwriting shared files or deleting the originals. Writes report the actual storage path. Add `.pi/notes/` to `.gitignore` when the project should not track it.
90
+ Notes live in `.pi/notes/` at the main checkout for conventional Git layouts, or the current directory outside Git. When Git metadata is stored separately, including in submodules, all checkouts use `.pi/notes/` inside Git's common directory instead; no configuration is needed. If that metadata is unavailable, notes stay local to the checkout. Writes report the actual storage path. Add `.pi/notes/` to `.gitignore` when the project should not track it.
91
91
 
92
- `write` uses Pi's native complete-file replacement: a failed write before publication leaves the previous note intact. It follows symlink targets and preserves ordinary permissions and ownership, or fails before publication. Hardlink aliases and already-open handles keep the previous file; ACLs, extended attributes, and power-loss durability are not guaranteed. Full replacements do not merge concurrent appends or other replacements. `append` retains one `O_APPEND` write per newline-terminated record.
92
+ `write` is a complete-file replacement in the note's directory: a failed write before publication leaves the previous note intact. It follows symlink targets and preserves ordinary permissions and ownership, or fails before publication. Hardlink aliases and already-open handles keep the previous file; ACLs, extended attributes, and power-loss durability are not guaranteed. Within one Pi process, writes and appends to the same note run in order; across processes, full replacements do not merge concurrent appends or other replacements. `append` retains one `O_APPEND` write per newline-terminated record.
93
93
 
94
94
  ## Data and privacy
95
95
 
96
96
  - Posthorse makes no network requests.
97
97
  - Notes are plaintext files under `.pi/notes`. They survive package removal and may be committed unless ignored.
98
- - `history` with `all: true` scans nested JSONL files in the active Pi session directory, including subagent sessions.
98
+ - `history` with `all: true` scans project-matching JSONL files and their nested subagent sessions in the active Pi session directory, not unrelated projects that share that directory. It runs ripgrep locally over that directory when available.
99
99
  - History can return user text, assistant text and thinking, assistant failure status and error messages, tool arguments and results, handoffs, custom messages, and images. Direct shell entries Pi marked `excludeFromContext` come back as a placeholder only.
100
100
  - Returned history content enters the currently selected model and provider context.
101
101
  - Removing Posthorse does not remove notes or Pi session history.
102
102
 
103
- ## Compatibility
104
-
105
- Reminders persisted by pi-headroom (`headroom-reminder`) are recognized alongside `posthorse-reminder` for deduplication, filtering, and recovery records. Notes, tool names, `.pi/notes`, and Pi's `context_window` entries are unchanged.
106
-
107
103
  ## Develop
108
104
 
109
105
  ```bash
110
106
  npm ci --ignore-scripts
111
- npm run check # public npm declarations can check types, but cannot run Posthorse
112
- # After the compatibility runner installs the selected fork SDK/types cohort:
113
- PI_COMPAT_HOST=fork npm run check:compat
107
+ npm run check:compat # typecheck, unit tests, and the real-SDK suite for the installed host
114
108
  PI_FORK=../pi scripts/integration.sh # additional full source-harness integration (disposable fork built)
115
109
  ```
116
110
 
117
- `npm run check` type-checks the extension, renderers, and tests against the exact 0.87.0 npm declaration baseline. Runtime tests require the qualified fork SDK, including `publishLocalFile`, installed in this checkout's dependency graph. `npm test` covers reminder policy, notes/history behavior, failed note replacement, and real native card components, including width, expansion, and legacy results. Tests use the public SDK entry; official Pi is not an operating target.
111
+ `npm run check` type-checks the extension, renderers, and tests against the official npm declarations. `npm test` covers reminder policy, notes/history behavior, failed note replacement, and real native card components, including width and expansion.
118
112
 
119
- `check:compat` adds `test:native`: real SDK loading, prompt composition, mixed recovery-output budgets, cursor progress, reminder filtering, interrupted-response recovery, oversized-result rollover without summary compaction, explicit `new_context`, and checkpoint restore without provider replay. It requires native fork capabilities; it never substitutes synthetic windows or silently skips on official. The runner must install the fork cohort in this checkout's `node_modules` so both `tsc` and SDK imports resolve that graph. `PI_HOST_INDEX` and `PI_COMPAT_EXPECTED_PACKAGE_DIR` are verified against it, and `PI_COMPAT_EXPECTED_VERSION` verifies the version. A same-version official graph is not a fork. Official qualification is a separate actual CLI startup/refusal probe, not `check:compat`.
113
+ `test:native` runs the real-SDK suite for whichever host is installed: `test/official.test.mjs` for official Pi, `test/native.test.mjs` when the fork SDK is installed. Both cover real SDK loading, prompt composition, explicit and automatic rollover without a summary request, recovery records, notes, and history; the fork suite adds receipt bounding, native async calls, and checkpoint restore. When `PI_COMPAT_HOST` is `fork` or `official`, a mismatch with the installed SDK fails instead of running the other suite. The shared compatibility runner installs each host's SDK cohort in this checkout's `node_modules` so both `tsc` and SDK imports resolve that graph; `PI_HOST_INDEX` and `PI_COMPAT_EXPECTED_PACKAGE_DIR` are verified against it, and `PI_COMPAT_EXPECTED_VERSION` verifies the version.
120
114
 
121
115
  For a focused read-only SDK diagnostic only, `PI_HOST_INDEX=/absolute/fork/dist/index.js node --test test/native.test.mjs` can inspect an immutable host; this does not qualify this checkout's types. The larger source integration suite remains available through `scripts/integration.sh`; use a disposable built fork checkout because it briefly copies a test into that host. All native fixtures must use an isolated HOME/agent directory with offline mode and no provider credentials. These checks do not claim native Windows support.