pi-tuck 0.0.0-stage → 1.0.1

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 (121) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/LICENSE +81 -0
  3. package/README.md +125 -2
  4. package/dist/index.d.ts +10 -0
  5. package/dist/index.js +90 -0
  6. package/dist/src/assets.d.ts +58 -0
  7. package/dist/src/assets.js +80 -0
  8. package/dist/src/budget.d.ts +133 -0
  9. package/dist/src/budget.js +106 -0
  10. package/dist/src/build-id.d.ts +1 -0
  11. package/dist/src/build-id.js +1 -0
  12. package/dist/src/compaction.d.ts +141 -0
  13. package/dist/src/compaction.js +680 -0
  14. package/dist/src/diagnostic-replay.d.ts +34 -0
  15. package/dist/src/diagnostic-replay.js +24 -0
  16. package/dist/src/diagnostics.d.ts +117 -0
  17. package/dist/src/diagnostics.js +307 -0
  18. package/dist/src/eligibility-cache.d.ts +19 -0
  19. package/dist/src/eligibility-cache.js +33 -0
  20. package/dist/src/history-image.d.ts +64 -0
  21. package/dist/src/history-image.js +139 -0
  22. package/dist/src/ledger.d.ts +41 -0
  23. package/dist/src/ledger.js +99 -0
  24. package/dist/src/omissions.d.ts +18 -0
  25. package/dist/src/omissions.js +80 -0
  26. package/dist/src/protocol.d.ts +9 -0
  27. package/dist/src/protocol.js +34 -0
  28. package/dist/src/recall.d.ts +130 -0
  29. package/dist/src/recall.js +446 -0
  30. package/dist/src/semantic-recall.d.ts +321 -0
  31. package/dist/src/semantic-recall.js +1188 -0
  32. package/dist/src/setup.d.ts +3 -0
  33. package/dist/src/setup.js +191 -0
  34. package/dist/src/source-export.d.ts +10 -0
  35. package/dist/src/source-export.js +15 -0
  36. package/dist/src/tuck/core/brief-meter.d.ts +42 -0
  37. package/dist/src/tuck/core/brief-meter.js +273 -0
  38. package/dist/src/tuck/core/brief.d.ts +49 -0
  39. package/dist/src/tuck/core/brief.js +391 -0
  40. package/dist/src/tuck/core/content.d.ts +17 -0
  41. package/dist/src/tuck/core/content.js +59 -0
  42. package/dist/src/tuck/core/filter-noise.d.ts +3 -0
  43. package/dist/src/tuck/core/filter-noise.js +6 -0
  44. package/dist/src/tuck/core/format.d.ts +8 -0
  45. package/dist/src/tuck/core/format.js +12 -0
  46. package/dist/src/tuck/core/global-indices.d.ts +18 -0
  47. package/dist/src/tuck/core/global-indices.js +54 -0
  48. package/dist/src/tuck/core/keyword-query.d.ts +23 -0
  49. package/dist/src/tuck/core/keyword-query.js +47 -0
  50. package/dist/src/tuck/core/normalize.d.ts +14 -0
  51. package/dist/src/tuck/core/normalize.js +134 -0
  52. package/dist/src/tuck/core/rank.d.ts +42 -0
  53. package/dist/src/tuck/core/rank.js +292 -0
  54. package/dist/src/tuck/core/recall-operation.d.ts +6 -0
  55. package/dist/src/tuck/core/recall-operation.js +7 -0
  56. package/dist/src/tuck/core/render-entries.d.ts +6 -0
  57. package/dist/src/tuck/core/render-entries.js +1 -0
  58. package/dist/src/tuck/core/sanitize.d.ts +1 -0
  59. package/dist/src/tuck/core/sanitize.js +3 -0
  60. package/dist/src/tuck/core/search-entries.d.ts +28 -0
  61. package/dist/src/tuck/core/search-entries.js +237 -0
  62. package/dist/src/tuck/core/source-order.d.ts +4 -0
  63. package/dist/src/tuck/core/source-order.js +11 -0
  64. package/dist/src/tuck/core/source-time.d.ts +15 -0
  65. package/dist/src/tuck/core/source-time.js +31 -0
  66. package/dist/src/tuck/core/summarize.d.ts +21 -0
  67. package/dist/src/tuck/core/summarize.js +27 -0
  68. package/dist/src/tuck/core/token-estimate.d.ts +37 -0
  69. package/dist/src/tuck/core/token-estimate.js +79 -0
  70. package/dist/src/tuck/core/tool-args.d.ts +2 -0
  71. package/dist/src/tuck/core/tool-args.js +8 -0
  72. package/dist/src/tuck/core/transcript.d.ts +95 -0
  73. package/dist/src/tuck/core/transcript.js +115 -0
  74. package/dist/src/tuck/cut.d.ts +36 -0
  75. package/dist/src/tuck/cut.js +195 -0
  76. package/dist/src/tuck/types.d.ts +71 -0
  77. package/dist/src/tuck/types.js +1 -0
  78. package/dist/src/tuck.d.ts +138 -0
  79. package/dist/src/tuck.js +345 -0
  80. package/index.ts +70 -0
  81. package/package.json +67 -4
  82. package/scripts/eg2-runtime.py +307 -0
  83. package/scripts/provision-eg2.py +139 -0
  84. package/scripts/semantic-worker.py +217 -0
  85. package/src/assets.ts +92 -0
  86. package/src/budget.ts +146 -0
  87. package/src/build-id.ts +1 -0
  88. package/src/compaction.ts +559 -0
  89. package/src/diagnostic-replay.ts +22 -0
  90. package/src/diagnostics.ts +144 -0
  91. package/src/eligibility-cache.ts +43 -0
  92. package/src/history-image.ts +136 -0
  93. package/src/ledger.ts +107 -0
  94. package/src/omissions.ts +70 -0
  95. package/src/protocol.ts +28 -0
  96. package/src/recall.ts +441 -0
  97. package/src/semantic-recall.ts +485 -0
  98. package/src/setup.ts +163 -0
  99. package/src/source-export.ts +17 -0
  100. package/src/tuck/core/brief-meter.ts +235 -0
  101. package/src/tuck/core/brief.ts +385 -0
  102. package/src/tuck/core/content.ts +59 -0
  103. package/src/tuck/core/filter-noise.ts +9 -0
  104. package/src/tuck/core/format.ts +13 -0
  105. package/src/tuck/core/global-indices.ts +55 -0
  106. package/src/tuck/core/keyword-query.ts +67 -0
  107. package/src/tuck/core/normalize.ts +132 -0
  108. package/src/tuck/core/rank.ts +331 -0
  109. package/src/tuck/core/recall-operation.ts +8 -0
  110. package/src/tuck/core/render-entries.ts +6 -0
  111. package/src/tuck/core/sanitize.ts +5 -0
  112. package/src/tuck/core/search-entries.ts +303 -0
  113. package/src/tuck/core/source-order.ts +12 -0
  114. package/src/tuck/core/source-time.ts +23 -0
  115. package/src/tuck/core/summarize.ts +41 -0
  116. package/src/tuck/core/token-estimate.ts +102 -0
  117. package/src/tuck/core/tool-args.ts +8 -0
  118. package/src/tuck/core/transcript.ts +133 -0
  119. package/src/tuck/cut.ts +220 -0
  120. package/src/tuck/types.ts +35 -0
  121. package/src/tuck.ts +352 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,67 @@
1
+ # Changelog
2
+
3
+ All notable changes to pi-tuck. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
4
+ [Semantic Versioning](https://semver.org/).
5
+
6
+ ## [1.0.1] - 2026-10-30
7
+
8
+ First release on npm. 1.0.0 was tagged but never published; its notes below describe what 1.0.1 ships besides these changes.
9
+
10
+ ### Added
11
+
12
+ - Linux support. Compaction and keyword recall work as on macOS; semantic search stays macOS on Apple silicon only.
13
+
14
+ ### Changed
15
+
16
+ - The `tuck_recall` and `tuck_search` tool descriptions are shorter: about 1,050 tokens per request instead of 1,670. The parameters
17
+ accept the same input.
18
+
19
+ ### Fixed
20
+
21
+ - A long session whose dialogue no longer fit the summary stalled for tens of seconds at every cut (88 s at 3,900 entries), because
22
+ the ranked view re-rendered the whole candidate summary for each block it considered. It now keeps the summary's size up to date
23
+ as blocks join, so a cut takes well under a second and grows linearly with the session. Summaries are byte for byte the same.
24
+
25
+ ## [1.0.0] - 2026-10-30
26
+
27
+ Tagged, not published.
28
+
29
+ ### Added
30
+
31
+ - Semantic search is on by default. Its local runtime (EmbeddingGemma-2, llama.cpp, a standalone CPython) is downloaded once, in the
32
+ background, from pinned public sources, each file checked against its SHA-256. Turn it off with `{"semanticRecall": false}` in
33
+ `~/.pi/agent/pi-tuck.json`.
34
+ - Summaries list the agent's other tool calls (shell commands and the like) before the cut, newest first and capped at 30, without
35
+ their output but with a pointer to it, so an agent that writes little prose still sees what it already ran.
36
+ - Runs started by a custom message (Pi 1.4) are budgeted by that message.
37
+ - Sessions written by pi-vcc-based compactors (`VCC-TRANSCRIPT/1` summaries, `vcc_recall` and `vcc_search` calls) are read as this
38
+ format, with tool names updated for the model.
39
+
40
+ ### Changed
41
+
42
+ - Summaries keep the whole dialogue, a list of the files read, written or edited, and the session's first prompt word for word, and end
43
+ with an instruction to re-read files before editing them. They stay small as a session grows, where 0.2.1's re-rendered the whole tool
44
+ history at every compaction. The ranked view remains as the fallback when the dialogue does not fit.
45
+ - Tools are renamed `tuck_recall` and `tuck_search` (from `pi_tuck_recall` and `pi_tuck_search`), and commands `/tuck-semantic` and
46
+ `/tuck-incident`. Summaries are headed `TUCK-TRANSCRIPT/1`.
47
+ - Tested against Pi 1.1.0's and 1.4.0's libraries.
48
+
49
+ ### Fixed
50
+
51
+ - On Pi 1.4, a prompt that needed a cut hung the session: Pi now counts a run as started before `before_agent_start`, and a
52
+ compaction asked for there waited on that run. The cut is made when the prompt arrives, while Pi is still idle.
53
+ - A revoked source that stops semantic recall records why, instead of reading as recall that was never switched on.
54
+
55
+ ### Removed
56
+
57
+ - The bounded, masked and full summary views; the dialogue view replaces them.
58
+ - The external `~/.config/pi/semantic-recall.json` configuration and `PI_SEMANTIC_RECALL_CONFIG`.
59
+
60
+ ## [0.2.1] - 2026-10-24
61
+
62
+ Source preview on GitHub, not published to npm. Deterministic compaction with `pi_tuck_recall` and `pi_tuck_search`; semantic search
63
+ opt-in, with a runtime the user had to supply.
64
+
65
+ [1.0.1]: https://github.com/galyavin/pi-tuck/releases/tag/v1.0.1
66
+ [1.0.0]: https://github.com/galyavin/pi-tuck/tree/v1.0.0
67
+ [0.2.1]: https://github.com/galyavin/pi-tuck/tree/e00fa99
package/LICENSE ADDED
@@ -0,0 +1,81 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sting8k
4
+ Copyright (c) 2026 Rustem Galyavin
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+
24
+ ---
25
+
26
+ Portions derived from pi-blackhole (https://github.com/k0valik/pi-blackhole).
27
+ The following notice is retained for those portions:
28
+
29
+ MIT License
30
+
31
+ Copyright (c) 2026 k0valik
32
+
33
+ Permission is hereby granted, free of charge, to any person obtaining a copy
34
+ of this software and associated documentation files (the "Software"), to deal
35
+ in the Software without restriction, including without limitation the rights
36
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
37
+ copies of the Software, and to permit persons to whom the Software is
38
+ furnished to do so, subject to the following conditions:
39
+
40
+ The above copyright notice and this permission notice shall be included in all
41
+ copies or substantial portions of the Software.
42
+
43
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
44
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
45
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
46
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
47
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
48
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
49
+ SOFTWARE.
50
+
51
+ ---
52
+
53
+ Pi-derived inlined message constructors
54
+
55
+ The following notice applies to the adapted createCustomMessage and
56
+ createBranchSummaryMessage construction branches in src/tuck/cut.ts
57
+ and their distributed generated equivalents. Notice source: Pi v1.1.0,
58
+ https://github.com/earendil-works/pi/blob/abe508e1b89912adde45528136c3221eb69acdd7/LICENSE
59
+ This notice-source pin does not identify the original copy revision.
60
+
61
+ MIT License
62
+
63
+ Copyright (c) 2025 Mario Zechner
64
+
65
+ Permission is hereby granted, free of charge, to any person obtaining a copy
66
+ of this software and associated documentation files (the "Software"), to deal
67
+ in the Software without restriction, including without limitation the rights
68
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
69
+ copies of the Software, and to permit persons to whom the Software is
70
+ furnished to do so, subject to the following conditions:
71
+
72
+ The above copyright notice and this permission notice shall be included in all
73
+ copies or substantial portions of the Software.
74
+
75
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
76
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
77
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
78
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
79
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
80
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
81
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,126 @@
1
- # Temporary Holding Version
1
+ # pi-tuck
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Deterministic compaction for [Pi](https://pi.dev). When a session's context fills up, pi-tuck replaces older turns with a summary built
4
+ from the session itself, not written by a model: no extra model call, no cost, no invented history, and the same input always gives the
5
+ same summary. Nothing is lost: the full history stays in the session file, and the agent gets two tools to read it back, by keyword and
6
+ by meaning.
7
+
8
+ ## Install
9
+
10
+ ```sh
11
+ pi install npm:pi-tuck@1.0.1
12
+ ```
13
+
14
+ or straight from GitHub:
15
+
16
+ ```sh
17
+ pi install git:github.com/galyavin/pi-tuck@v1.0.1
18
+ ```
19
+
20
+ Requires Pi 1.1 or later (tested on 1.1.0 and 1.4.0) on macOS or Linux. Semantic search (`tuck_search` by meaning) runs only on macOS
21
+ on Apple silicon; elsewhere `tuck_search` uses keywords and everything else works.
22
+ Start a new Pi session after installing.
23
+
24
+ ## What it does
25
+
26
+ **Compaction.** pi-tuck takes over Pi's compaction, both automatic and `/compact`. A summary keeps:
27
+
28
+ - the dialogue: what you asked and what the agent answered, in order;
29
+ - the files the agent read, wrote or edited, newest first (up to 60), with pointers to read them back;
30
+ - the agent's other tool calls, such as shell commands, newest first (up to 30), without their output but with pointers to it;
31
+ - your first prompt in the session, word for word (up to 12,000 characters).
32
+
33
+ Tool output is left out of the summary; it stays in the session file. If the dialogue itself is too long for the room left, the summary
34
+ keeps a ranked selection of records instead. Each summary ends with a short instruction to the agent: re-read a file before editing it,
35
+ and look things up rather than guess.
36
+
37
+ **`tuck_recall`** finds earlier text by keyword and reads any original message or tool result exactly, by the index shown in a summary.
38
+ It can also write an original back out to a file.
39
+
40
+ **`tuck_search`** finds earlier history related to a question even when the wording differs. It combines keyword hits with local
41
+ semantic search (see below) and returns the original text with pointers for `tuck_recall`.
42
+
43
+ **Commands**
44
+
45
+ | Command | Does |
46
+ |---|---|
47
+ | `/tuck-semantic on\|off\|status` | Turns semantic search on or off for this session, or shows its state. |
48
+ | `/tuck-incident on\|next\|off\|status` | Records this session's compaction decisions to a file you can attach to a bug report. |
49
+
50
+ `/compact` works as usual; pi-tuck does not take custom summary instructions. Pi's own setting `"compaction": { "enabled": false }`
51
+ turns compaction off.
52
+
53
+ **Small context windows.** pi-tuck keeps room for the model's reply: the larger of the model's output limit and Pi's
54
+ `compaction.reserveTokens` (default 16,384). It refuses a request that cannot fit rather than let the provider cut it off. For a model
55
+ with a window of 32k tokens or less, lower the reserve for that model in Pi's `settings.json`:
56
+
57
+ ```json
58
+ { "compaction": { "modelOverrides": { "my-provider/my-model": { "reserveTokens": 4096 } } } }
59
+ ```
60
+
61
+ ## Semantic search
62
+
63
+ Semantic search is on by default and runs entirely on your Mac: an embedding model,
64
+ [EmbeddingGemma-2](https://huggingface.co/google/embeddinggemma-2), served by [llama.cpp](https://github.com/ggml-org/llama.cpp) on the
65
+ CPU. No text leaves your machine.
66
+
67
+ The first time a session starts, pi-tuck downloads what it needs, once, about 390 MB in the background, and keeps working with keyword
68
+ search meanwhile:
69
+
70
+ | File | From |
71
+ |---|---|
72
+ | EmbeddingGemma-2, 8-bit GGUF (310 MB) and its tokenizer (32 MB) | huggingface.co |
73
+ | llama.cpp release b11476 for macOS arm64 (12 MB) | github.com/ggml-org/llama.cpp |
74
+ | CPython 3.13 (25 MB) and 16 Python packages for the tokenizer (9 MB) | github.com/astral-sh/python-build-standalone, pypi.org |
75
+
76
+ Every file is pinned to a SHA-256 in [src/assets.ts](src/assets.ts) and checked before use. Everything goes to `~/.pi/agent/pi-tuck`;
77
+ to remove it, delete that folder. Setup needs about 6.5 GB of free disk space while it runs and uses 460 MB after. If it fails, for
78
+ example offline, pi-tuck says so and tries again at the next session start. One-shot runs (`pi -p`) don't start the setup.
79
+
80
+ Indexing starts in the background the first time a session uses `tuck_search`, and catches up after each compaction. Semantic search is off when Pi
81
+ runs in a folder that contains Pi's agent folder, such as your home folder, because its own files live there; open Pi in a project folder.
82
+
83
+ **Turn it off** by creating `~/.pi/agent/pi-tuck.json`:
84
+
85
+ ```json
86
+ { "semanticRecall": false }
87
+ ```
88
+
89
+ Nothing is then downloaded, and `tuck_search` uses keywords only. If you set `PI_CODING_AGENT_DIR`, the file and the download folder are
90
+ in that folder instead.
91
+
92
+ ## Use from code
93
+
94
+ The package also exports its parts for hosts that embed Pi through its SDK:
95
+
96
+ ```ts
97
+ import { installCompaction, createSemanticRecall, ensureSemanticRuntime } from "pi-tuck";
98
+
99
+ const config = JSON.parse(readFileSync(await ensureSemanticRuntime("/abs/path/outside/projects"), "utf8"));
100
+ const semanticRecall = createSemanticRecall(config);
101
+ const handle = await installCompaction(pi, { semanticRecall });
102
+ await handle.setSemanticEnabled(true, ctx); // e.g. in a session_start handler
103
+ ```
104
+
105
+ `installCompaction` options (reply reserve, host-injected messages, recall policy and others) are documented in
106
+ [src/compaction.ts](src/compaction.ts). `handle.close()` stops the semantic worker; call it before disposing a session.
107
+
108
+ ## Development
109
+
110
+ ```sh
111
+ npm install
112
+ npm run check # types
113
+ npm test # unit tests (Vitest) and the Python runtime tests
114
+ npm run build # dist/ for the library export
115
+ ```
116
+
117
+ ## Credits
118
+
119
+ pi-tuck grew out of [pi-vcc](https://github.com/sting8k/pi-vcc) by sting8k and includes code from
120
+ [pi-blackhole](https://github.com/k0valik/pi-blackhole) by k0valik and from [Pi](https://github.com/earendil-works/pi) by Mario
121
+ Zechner, all MIT; their notices are in [LICENSE](LICENSE). The downloaded model is Apache-2.0, llama.cpp is MIT, and CPython is under the
122
+ PSF license.
123
+
124
+ ## License
125
+
126
+ MIT
@@ -0,0 +1,10 @@
1
+ import { type ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ export type { CompactionOptions } from "./src/compaction.js";
3
+ export { installCompaction } from "./src/compaction.js";
4
+ export { inspectIncident, readableIncident } from "./src/diagnostic-replay.js";
5
+ export { createSemanticRecall, SemanticRecallController } from "./src/semantic-recall.js";
6
+ export type { SemanticRecallConfig, PriorResult, SemanticState } from "./src/semantic-recall.js";
7
+ export { ensureSemanticRuntime } from "./src/setup.js";
8
+ /** pi-tuck as a Pi extension: compaction and history recall always; semantic recall too, unless `pi-tuck.json` in Pi's agent folder
9
+ * says `{"semanticRecall": false}`. Its runtime is set up in the background the first time a session starts. */
10
+ export default function (pi: ExtensionAPI): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,90 @@
1
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
2
+ import { readFileSync, realpathSync } from "node:fs";
3
+ import { join, sep } from "node:path";
4
+ import { installCompaction } from "./src/compaction.js";
5
+ import { createSemanticRecall } from "./src/semantic-recall.js";
6
+ import { defaultRoot } from "./src/assets.js";
7
+ import { ensureSemanticRuntime } from "./src/setup.js";
8
+ export { installCompaction } from "./src/compaction.js";
9
+ export { inspectIncident, readableIncident } from "./src/diagnostic-replay.js";
10
+ export { createSemanticRecall, SemanticRecallController } from "./src/semantic-recall.js";
11
+ export { ensureSemanticRuntime } from "./src/setup.js";
12
+ /** pi-tuck as a Pi extension: compaction and history recall always; semantic recall too, unless `pi-tuck.json` in Pi's agent folder
13
+ * says `{"semanticRecall": false}`. Its runtime is set up in the background the first time a session starts. */
14
+ export default async function (pi) {
15
+ const semanticRecall = createSemanticRecall();
16
+ // `/tuck-semantic off` holds for the rest of the session, including over a setup that finishes later.
17
+ let off = false;
18
+ const handle = await installCompaction(pi, { semanticRecall, onSemanticSetting: async (enabled) => { off = !enabled; } });
19
+ const settings = readSettings(join(getAgentDir(), "pi-tuck.json"));
20
+ if (settings.semanticRecall === false)
21
+ return;
22
+ let session = 0, said = "", setup;
23
+ // A context outlives its session (a setup can finish after /new); a notice to an ended session is dropped.
24
+ const say = (ctx, message, level) => { try {
25
+ ctx.ui.notify(message, level);
26
+ }
27
+ catch { /* ended */ } };
28
+ const once = (ctx, message, level) => { if (said !== message) {
29
+ said = message;
30
+ say(ctx, message, level);
31
+ } };
32
+ pi.on("session_shutdown", () => { session++; });
33
+ pi.on("session_start", (_event, ctx) => {
34
+ const current = ++session;
35
+ off = false;
36
+ if (settings.error)
37
+ once(ctx, `pi-tuck: ${settings.error}; using the defaults.`, "warning");
38
+ if (process.platform !== "darwin" || process.arch !== "arm64")
39
+ return once(ctx, "pi-tuck: semantic recall needs macOS on Apple silicon for now; compaction and word recall work.", "info");
40
+ // Semantic recall keeps its runtime and indexes outside the project it serves, so not when Pi runs in a folder that holds its
41
+ // agent folder, such as the home folder.
42
+ if (within(real(getAgentDir()), real(ctx.cwd)))
43
+ return once(ctx, "pi-tuck: semantic recall is off here because Pi's agent folder is inside this folder; open Pi in a project folder to use it.", "info");
44
+ if (!ctx.hasUI)
45
+ return; // a one-shot run (pi -p) would end before a first-time setup could
46
+ if (!semanticRecall.status().configured)
47
+ semanticRecall.refuse("Semantic recall is being set up; word recall works meanwhile");
48
+ setup ??= ensureSemanticRuntime(defaultRoot(), message => say(ctx, message, "info"));
49
+ setup.then(async () => {
50
+ if (current !== session || off)
51
+ return;
52
+ try {
53
+ await handle.setSemanticEnabled(true, ctx); // reads the runtime's configuration through the outside-project check
54
+ }
55
+ catch (error) {
56
+ if (current === session)
57
+ say(ctx, `pi-tuck: semantic recall could not start (${reason(error)}); it tries again when a session starts.`, "warning");
58
+ }
59
+ }, error => {
60
+ setup = undefined; // the next session start tries again
61
+ semanticRecall.refuse(`Semantic recall setup failed: ${reason(error)}`);
62
+ if (current === session)
63
+ say(ctx, `pi-tuck: semantic recall setup failed (${reason(error)}). Word recall works; setup retries when a session starts.`, "warning");
64
+ });
65
+ });
66
+ }
67
+ function readSettings(path) {
68
+ let text;
69
+ try {
70
+ text = readFileSync(path, "utf8");
71
+ }
72
+ catch {
73
+ return {};
74
+ }
75
+ try {
76
+ const value = JSON.parse(text);
77
+ if (value && typeof value === "object" && (value.semanticRecall === undefined || typeof value.semanticRecall === "boolean"))
78
+ return value;
79
+ }
80
+ catch { /* reported below */ }
81
+ return { error: `${path} should be a JSON object like {"semanticRecall": false}` };
82
+ }
83
+ const reason = (error) => error instanceof Error ? error.message : String(error);
84
+ const within = (path, folder) => path === folder || path.startsWith(folder.endsWith(sep) ? folder : folder + sep);
85
+ const real = (path) => { try {
86
+ return realpathSync(path);
87
+ }
88
+ catch {
89
+ return path;
90
+ } };
@@ -0,0 +1,58 @@
1
+ export interface Download {
2
+ url: string;
3
+ sha256: string;
4
+ size: number;
5
+ }
6
+ /** Where pi-tuck keeps the runtime, its downloads and the indexes: one folder, so deleting it removes all of them. */
7
+ export declare const defaultRoot: () => string;
8
+ /** A relocatable CPython for macOS arm64; the runtime's virtual environment is made from it and keeps pointing at it. */
9
+ export declare const PYTHON: Download & {
10
+ version: string;
11
+ name: string;
12
+ };
13
+ /** EmbeddingGemma-2, 8-bit GGUF. */
14
+ export declare const MODEL: Download;
15
+ /** The model's own tokenizer: passages are split on its token offsets. */
16
+ export declare const TOKENIZER: Download;
17
+ /** llama.cpp release b11476 (commit 988190680d5a). `files` are the server and the libraries it loads from its own folder; the archive
18
+ * names most libraries through symlinks, which the runtime refuses, so each is copied as a plain file under the name the server loads. */
19
+ export declare const LLAMA: Download & {
20
+ folder: string;
21
+ files: Record<string, string>;
22
+ license: string;
23
+ };
24
+ /** The tokenizer library and its declared dependencies, installed offline with hash checking. */
25
+ export declare const WHEELS: Record<string, Download>;
26
+ /** What scripts/eg2-runtime.py checks the runtime against: the model, its encoding contract and the native build. */
27
+ export declare const PINS: {
28
+ embedding: {
29
+ model: string;
30
+ revision: string;
31
+ dimensions: number;
32
+ maxTokens: number;
33
+ queryInstruction: string;
34
+ documentInstruction: string;
35
+ pooling: string;
36
+ dtype: string;
37
+ textOnly: boolean;
38
+ };
39
+ runtime: {
40
+ tokenizers: string;
41
+ python: string;
42
+ };
43
+ backend: {
44
+ name: string;
45
+ commit: string;
46
+ artifact: string;
47
+ computePrecision: string;
48
+ gpuLayers: number;
49
+ compute: string;
50
+ modelRepository: string;
51
+ modelRevision: string;
52
+ modelSHA256: string;
53
+ };
54
+ };
55
+ /** A digest of every pinned input: a changed pin provisions a new runtime beside the old one instead of mutating it. */
56
+ export declare const RUNTIME_ID: string;
57
+ /** The provisioned runtime's configuration, written last by provisioning: its presence means the runtime is complete. */
58
+ export declare const runtimeConfigPath: (root: string) => string;
@@ -0,0 +1,80 @@
1
+ // Everything semantic recall's local runtime is built from: a public source and its SHA-256 for each file. setup.ts downloads and
2
+ // verifies these, and a change to any of them gives the runtime a new id, so an update provisions beside the old runtime, never over it.
3
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
4
+ import { createHash } from "node:crypto";
5
+ import { join } from "node:path";
6
+ /** Where pi-tuck keeps the runtime, its downloads and the indexes: one folder, so deleting it removes all of them. */
7
+ export const defaultRoot = () => join(getAgentDir(), "pi-tuck");
8
+ const MODEL_REVISION = "031f0d4b35536f69ab3509d4893c923264fcf253", TOKENIZER_REVISION = "914f7f89142e33e77833254d9c9b90c3cef7303b";
9
+ const LLAMA_COMMIT = "988190680d5a89fce97de3c20df2c2813731fd61";
10
+ /** A relocatable CPython for macOS arm64; the runtime's virtual environment is made from it and keeps pointing at it. */
11
+ export const PYTHON = {
12
+ name: "cpython-3.13.18+20261026", version: "3.13", size: 25394044,
13
+ url: "https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.13.18%2B20261026-aarch64-apple-darwin-install_only.tar.gz",
14
+ sha256: "e5af2fde6a937a0e0c795a0c595c4efb70578d57463a2657c819992b9b9acbac",
15
+ };
16
+ /** EmbeddingGemma-2, 8-bit GGUF. */
17
+ export const MODEL = {
18
+ url: `https://huggingface.co/unsloth/embeddinggemma-2-GGUF/resolve/${MODEL_REVISION}/embeddinggemma-2-Q8_0.gguf`,
19
+ sha256: "6f1bd4ac6c5df7444f9cca7ca36cafe6cfa34cd6f49fefb1e0b4be8143aed8bc", size: 309855520,
20
+ };
21
+ /** The model's own tokenizer: passages are split on its token offsets. */
22
+ export const TOKENIZER = {
23
+ url: `https://huggingface.co/google/embeddinggemma-2/resolve/${TOKENIZER_REVISION}/tokenizer.json`,
24
+ sha256: "4d777ef5bdc1aa36227abdfb77c3e49e7b9c892d16e1b6bda41c393504828be4", size: 32170510,
25
+ };
26
+ /** llama.cpp release b11476 (commit 988190680d5a). `files` are the server and the libraries it loads from its own folder; the archive
27
+ * names most libraries through symlinks, which the runtime refuses, so each is copied as a plain file under the name the server loads. */
28
+ export const LLAMA = {
29
+ url: "https://github.com/ggml-org/llama.cpp/releases/download/b11476/llama-b11476-bin-macos-arm64.tar.gz",
30
+ sha256: "577634a1b8a59e8dabe02ba10de1e610be0574dfaf1cf3020e6dd42853ed877e", size: 12012019,
31
+ folder: "llama-b11476",
32
+ files: {
33
+ "llama-server": "1bf5602a2be50097c44282a6ecc63234d94ee8f96d39fd45fed6eff4e74b6b41",
34
+ "libllama-server-impl.dylib": "d699e1d7f0b3d5c63c9e7cd88c50e37c96cbf64802dbcefdc784983565a29608",
35
+ "libllama-common.0.dylib": "e90e6f5bba4ff114f7fb9ed1328171b1c4eceac71ee8923178f5d417310b0d4e",
36
+ "libmtmd.0.dylib": "c87488222bc71275f75da743e8cc628b014b59d051bc0e303c444e96f54762a7",
37
+ "libllama.0.dylib": "423bd595e045f0bb8225ad721571d5ab9842815f1b975f2462d2c7c8e0594bd7",
38
+ "libggml.0.dylib": "85ac40d540a5cc86f28a70270ddcdde677e92eb585c3bacd1b7e82270fe8934b",
39
+ "libggml-cpu.0.dylib": "937073375f79a187d8fda2e3900a9095e979593b691d6bbb146956743ceddd82",
40
+ "libggml-blas.0.dylib": "6526259e8f70a5b3a394df3ba082050c290d7653846efbdc84b3710b22fb4eac",
41
+ "libggml-metal.0.dylib": "a40058fab152c973339e40e80df900c1b311e06bcfb4c1c473cb169c7ee88a5c",
42
+ "libggml-rpc.0.dylib": "48c13b7c2c56e2b569b636e6a28d1c0853ad2d1db1980c0afd374651c6e897d0",
43
+ "libggml-base.0.dylib": "6fba8bb7fb529801d1b73782ac1707b653b1ea4caf53f667b85bc801e2883326",
44
+ },
45
+ license: "94f29bbed6a22c35b992c5c6ebf0e7c92f13b836b90f36f461c9cf2f0f1d010d",
46
+ };
47
+ const pypi = (path, sha256, size) => ({ url: "https://files.pythonhosted.org/packages/" + path, sha256, size });
48
+ /** The tokenizer library and its declared dependencies, installed offline with hash checking. */
49
+ export const WHEELS = {
50
+ "anyio-4.15.1-py3-none-any.whl": pypi("12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", "6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", 132079),
51
+ "certifi-2026.7.22-py3-none-any.whl": pypi("0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", "62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", 136983),
52
+ "click-8.5.0-py3-none-any.whl": pypi("58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", "255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", 125251),
53
+ "filelock-4.0.12-py3-none-any.whl": pypi("da/a1/b047e1c486a2c66283633b7dbb73872ca16ca1ce6a426f70d4d037fc8e1c/filelock-4.0.12-py3-none-any.whl", "5f17ee83ecee8a6f3e389c75822fb70a1c2f0506b99438a6dffa1d56793588c8", 111732),
54
+ "fsspec-2026.9.0-py3-none-any.whl": pypi("6c/c0/a98505f18594f1bce828bb159cec0fcf9860562f1a2c85913409fc8f3d9e/fsspec-2026.9.0-py3-none-any.whl", "8dd6e646e99ea382bd85f97a45e6b526a442d79423a7dc673f1e2756d05fcb5f", 221738),
55
+ "h11-0.16.0-py3-none-any.whl": pypi("04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", "63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", 37515),
56
+ "hf_xet-1.7.0-cp38-abi3-macosx_11_0_arm64.whl": pypi("c3/47/a26ebdce7056a61e931f228439bc0ab08cbec239d1690f965e5e637cba79/hf_xet-1.7.0-cp38-abi3-macosx_11_0_arm64.whl", "59fba37039233c7fcbe196817d6cdcf1b40dfb17b410f229d85b0cf0a1848da4", 3560352),
57
+ "httpcore-1.0.9-py3-none-any.whl": pypi("7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", "2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", 78784),
58
+ "httpx-0.28.1-py3-none-any.whl": pypi("2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", "d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", 73517),
59
+ "huggingface_hub-1.33.0-py3-none-any.whl": pypi("fc/16/963096d224b80909432dc16561a615fd33d2d13beef3ce4c63fa25e40867/huggingface_hub-1.33.0-py3-none-any.whl", "04e434b06e100eddbce9a6e817d72693a7884b10a79bd67ab48080d5c07eb899", 846435),
60
+ "idna-3.20-py3-none-any.whl": pypi("58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", "ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", 69583),
61
+ "packaging-26.3-py3-none-any.whl": pypi("63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", "d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", 129956),
62
+ "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl": pypi("b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", "2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", 173252),
63
+ "tokenizers-0.23.2-cp310-abi3-macosx_11_0_arm64.whl": pypi("67/49/22da045a91732384d3a3771816bf188dc5a1f702c32e635afa7c679c0bef/tokenizers-0.23.2-cp310-abi3-macosx_11_0_arm64.whl", "986670e43691469dcee610ea0f846f91a8f84e91fc6f7a48d4c064414c0ec2bf", 3101593),
64
+ "tqdm-4.70.1-py3-none-any.whl": pypi("a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl", "c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73", 80199),
65
+ "typing_extensions-4.16.0-py3-none-any.whl": pypi("49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", "481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", 45571),
66
+ };
67
+ /** What scripts/eg2-runtime.py checks the runtime against: the model, its encoding contract and the native build. */
68
+ export const PINS = {
69
+ embedding: { model: "google/embeddinggemma-2", revision: TOKENIZER_REVISION, dimensions: 768, maxTokens: 8192,
70
+ queryInstruction: "task: search result | query: ", documentInstruction: "title: none | text: ",
71
+ pooling: "mask-aware mean including prompt, projected token states, then float32 L2", dtype: "Q8_0", textOnly: true },
72
+ runtime: { tokenizers: "0.23.2", python: PYTHON.version },
73
+ backend: { name: "llama.cpp", commit: LLAMA_COMMIT, artifact: "Q8_0", computePrecision: "float32", gpuLayers: 0,
74
+ compute: "CPU float32 with f32 KV; quantized weights where selected", modelRepository: "unsloth/embeddinggemma-2-GGUF",
75
+ modelRevision: MODEL_REVISION, modelSHA256: MODEL.sha256 },
76
+ };
77
+ /** A digest of every pinned input: a changed pin provisions a new runtime beside the old one instead of mutating it. */
78
+ export const RUNTIME_ID = createHash("sha256").update(JSON.stringify({ PYTHON, MODEL, TOKENIZER, LLAMA, WHEELS, PINS })).digest("hex").slice(0, 16);
79
+ /** The provisioned runtime's configuration, written last by provisioning: its presence means the runtime is complete. */
80
+ export const runtimeConfigPath = (root) => join(root, "runtime", RUNTIME_ID, "semantic-recall.json");