my-durable 0.0.0-stage → 0.0.2

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 (259) hide show
  1. package/.env.example +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +328 -2
  4. package/ai_docs/durable_pi/announcement.md +860 -0
  5. package/ai_docs/durable_pi/changelog.md +51 -0
  6. package/ai_docs/durable_pi/chord-delta-findings.md +362 -0
  7. package/ai_docs/durable_pi/coding-agent-integration.md +70 -0
  8. package/ai_docs/durable_pi/pico-v5-chord-usage.md +391 -0
  9. package/ai_docs/durable_pi/pico-v5-handoff.md +921 -0
  10. package/ai_docs/durable_pi/readme.md +617 -0
  11. package/ai_docs/durable_pi/sources.md +22 -0
  12. package/ai_docs/durable_pi/spec.md +4696 -0
  13. package/ai_docs/durable_pi/vacation-planner.md +57 -0
  14. package/ai_docs/pi/chord.md +268 -0
  15. package/ai_docs/pi/cli-integration.md +110 -0
  16. package/ai_docs/pi/cli.md +325 -0
  17. package/ai_docs/pi/codemode.md +211 -0
  18. package/ai_docs/pi/compaction.md +467 -0
  19. package/ai_docs/pi/configuration.md +51 -0
  20. package/ai_docs/pi/containerization.md +187 -0
  21. package/ai_docs/pi/custom-provider.md +177 -0
  22. package/ai_docs/pi/environment-variables.md +102 -0
  23. package/ai_docs/pi/extensions.md +278 -0
  24. package/ai_docs/pi/how-pi-works.md +53 -0
  25. package/ai_docs/pi/index.md +43 -0
  26. package/ai_docs/pi/json.md +230 -0
  27. package/ai_docs/pi/keybindings.md +198 -0
  28. package/ai_docs/pi/llama-cpp.md +118 -0
  29. package/ai_docs/pi/mcp.md +274 -0
  30. package/ai_docs/pi/message-types.md +265 -0
  31. package/ai_docs/pi/models.md +209 -0
  32. package/ai_docs/pi/overview.md +166 -0
  33. package/ai_docs/pi/packages.md +133 -0
  34. package/ai_docs/pi/pi-agent-core.md +568 -0
  35. package/ai_docs/pi/pi-ai.md +1942 -0
  36. package/ai_docs/pi/prompt-templates.md +63 -0
  37. package/ai_docs/pi/providers.md +204 -0
  38. package/ai_docs/pi/quickstart.md +140 -0
  39. package/ai_docs/pi/readme.md +99 -0
  40. package/ai_docs/pi/rpc-commands.md +864 -0
  41. package/ai_docs/pi/rpc-extension-ui.md +204 -0
  42. package/ai_docs/pi/rpc.md +197 -0
  43. package/ai_docs/pi/sdk.md +151 -0
  44. package/ai_docs/pi/security.md +103 -0
  45. package/ai_docs/pi/session-format.md +297 -0
  46. package/ai_docs/pi/sessions.md +70 -0
  47. package/ai_docs/pi/settings.md +173 -0
  48. package/ai_docs/pi/shell-aliases.md +97 -0
  49. package/ai_docs/pi/skills.md +97 -0
  50. package/ai_docs/pi/slash-commands.md +64 -0
  51. package/ai_docs/pi/sources.md +57 -0
  52. package/ai_docs/pi/terminal-setup.md +223 -0
  53. package/ai_docs/pi/termux.md +121 -0
  54. package/ai_docs/pi/themes.md +140 -0
  55. package/ai_docs/pi/tmux.md +59 -0
  56. package/ai_docs/pi/toolkit.md +168 -0
  57. package/ai_docs/pi/tui.md +129 -0
  58. package/ai_docs/pi/usage.md +98 -0
  59. package/ai_docs/pi/virtual-models.md +118 -0
  60. package/ai_docs/pi/windows.md +71 -0
  61. package/apps/dpi-backend/.nvmrc +1 -0
  62. package/apps/dpi-backend/API.md +99 -0
  63. package/apps/dpi-backend/README.md +119 -0
  64. package/apps/dpi-backend/deploy/backend.env.example +9 -0
  65. package/apps/dpi-backend/deploy/dpi-backend.plist +26 -0
  66. package/apps/dpi-backend/deploy/dpi-backend.service +22 -0
  67. package/apps/dpi-backend/package.json +33 -0
  68. package/apps/dpi-backend/prompts/one_sentence.md +3 -0
  69. package/apps/dpi-backend/prompts/smart_ass_opus_fix.md +169 -0
  70. package/apps/dpi-backend/scripts/check-contract.mjs +151 -0
  71. package/apps/dpi-backend/scripts/migrate-playground.ts +66 -0
  72. package/apps/dpi-backend/scripts/record-nested-fixture.mts +154 -0
  73. package/apps/dpi-backend/scripts/token.ts +57 -0
  74. package/apps/dpi-backend/src/addons.ts +139 -0
  75. package/apps/dpi-backend/src/auth.ts +20 -0
  76. package/apps/dpi-backend/src/config.ts +54 -0
  77. package/apps/dpi-backend/src/contract-check.ts +118 -0
  78. package/apps/dpi-backend/src/errors.ts +50 -0
  79. package/apps/dpi-backend/src/extensions.ts +510 -0
  80. package/apps/dpi-backend/src/faux.ts +80 -0
  81. package/apps/dpi-backend/src/history.ts +165 -0
  82. package/apps/dpi-backend/src/idempotency.ts +20 -0
  83. package/apps/dpi-backend/src/jev.ts +43 -0
  84. package/apps/dpi-backend/src/jobs.ts +790 -0
  85. package/apps/dpi-backend/src/main.ts +76 -0
  86. package/apps/dpi-backend/src/model-catalog.ts +180 -0
  87. package/apps/dpi-backend/src/pairing-files.ts +142 -0
  88. package/apps/dpi-backend/src/pairing.ts +459 -0
  89. package/apps/dpi-backend/src/routes.ts +217 -0
  90. package/apps/dpi-backend/src/shared-state.ts +85 -0
  91. package/apps/dpi-backend/src/store.ts +1240 -0
  92. package/apps/dpi-backend/src/streams.ts +256 -0
  93. package/apps/dpi-backend/src/tunnel.ts +955 -0
  94. package/apps/dpi-backend/tsconfig.json +13 -0
  95. package/apps/dpi-playground/README.md +83 -0
  96. package/apps/dpi-playground/apps/dpi-playground-fe/dist/assets/index-CFGc7lHT.js +77 -0
  97. package/apps/dpi-playground/apps/dpi-playground-fe/dist/assets/index-DFdKpwm_.css +1 -0
  98. package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/anthropic.png +0 -0
  99. package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/deepseek.png +0 -0
  100. package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/durable-pi.svg +14 -0
  101. package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/gemini.png +0 -0
  102. package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/grok.png +0 -0
  103. package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/kimi.png +0 -0
  104. package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/lab.png +0 -0
  105. package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/meta.png +0 -0
  106. package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/minimax.png +0 -0
  107. package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/openai.png +0 -0
  108. package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/qwen.png +0 -0
  109. package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/xiaomi.png +0 -0
  110. package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/zai.png +0 -0
  111. package/apps/dpi-playground/apps/dpi-playground-fe/dist/index.html +16 -0
  112. package/apps/dpi-playground/apps/dpi-playground-fe/index.html +15 -0
  113. package/apps/dpi-playground/apps/dpi-playground-fe/package.json +5 -0
  114. package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/anthropic.png +0 -0
  115. package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/deepseek.png +0 -0
  116. package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/durable-pi.svg +14 -0
  117. package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/gemini.png +0 -0
  118. package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/grok.png +0 -0
  119. package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/kimi.png +0 -0
  120. package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/lab.png +0 -0
  121. package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/meta.png +0 -0
  122. package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/minimax.png +0 -0
  123. package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/openai.png +0 -0
  124. package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/qwen.png +0 -0
  125. package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/xiaomi.png +0 -0
  126. package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/zai.png +0 -0
  127. package/apps/dpi-playground/apps/dpi-playground-fe/src/App.vue +180 -0
  128. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/commands.ts +119 -0
  129. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/composables.ts +129 -0
  130. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/hub.ts +40 -0
  131. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/icon-path.ts +7 -0
  132. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/ids.ts +2 -0
  133. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/jobs.ts +80 -0
  134. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/link-policy.ts +16 -0
  135. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/markdown-policy.ts +115 -0
  136. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/markdown.ts +60 -0
  137. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/pair-close.ts +23 -0
  138. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/pair-handoff.ts +56 -0
  139. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/pair.ts +166 -0
  140. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/qrcode.ts +323 -0
  141. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/signature.ts +146 -0
  142. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/store.ts +373 -0
  143. package/apps/dpi-playground/apps/dpi-playground-fe/src/client/stream.ts +183 -0
  144. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/AgentView.vue +342 -0
  145. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/Composer.vue +198 -0
  146. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/ContextBar.vue +254 -0
  147. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/DeviceCredentialsDialog.vue +40 -0
  148. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/DeviceIcon.vue +56 -0
  149. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/DirBrowser.vue +95 -0
  150. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/ExtensionsPanel.vue +96 -0
  151. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/HelpDialog.vue +69 -0
  152. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/Inspector.vue +425 -0
  153. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/JobCard.vue +221 -0
  154. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/MobileNav.vue +39 -0
  155. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/ModelPicker.vue +71 -0
  156. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/NetworkSidebar.vue +236 -0
  157. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/NewSessionDialog.vue +114 -0
  158. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/OfflinePanel.vue +128 -0
  159. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/PairDeviceWizard.vue +433 -0
  160. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/PromptDialog.vue +89 -0
  161. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/StateEditor.vue +179 -0
  162. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TasksPanel.vue +280 -0
  163. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/Toast.vue +11 -0
  164. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TokenManager.vue +175 -0
  165. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TopBar.vue +114 -0
  166. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TranscriptItem.vue +152 -0
  167. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TreeRow.vue +195 -0
  168. package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TunnelStatusBar.vue +107 -0
  169. package/apps/dpi-playground/apps/dpi-playground-fe/src/extensions.css +105 -0
  170. package/apps/dpi-playground/apps/dpi-playground-fe/src/jobs.css +655 -0
  171. package/apps/dpi-playground/apps/dpi-playground-fe/src/main.ts +22 -0
  172. package/apps/dpi-playground/apps/dpi-playground-fe/src/network.css +675 -0
  173. package/apps/dpi-playground/apps/dpi-playground-fe/src/style.css +3203 -0
  174. package/apps/dpi-playground/apps/dpi-playground-fe/tsconfig.json +16 -0
  175. package/apps/dpi-playground/apps/dpi-playground-fe/vite.config.ts +14 -0
  176. package/apps/dpi-playground/apps/dpi-playground-hub/package.json +5 -0
  177. package/apps/dpi-playground/apps/dpi-playground-hub/src/audit.ts +406 -0
  178. package/apps/dpi-playground/apps/dpi-playground-hub/src/client-ip.ts +72 -0
  179. package/apps/dpi-playground/apps/dpi-playground-hub/src/credentials.ts +265 -0
  180. package/apps/dpi-playground/apps/dpi-playground-hub/src/guard.ts +43 -0
  181. package/apps/dpi-playground/apps/dpi-playground-hub/src/http-errors.ts +156 -0
  182. package/apps/dpi-playground/apps/dpi-playground-hub/src/identity.ts +60 -0
  183. package/apps/dpi-playground/apps/dpi-playground-hub/src/limiter.ts +165 -0
  184. package/apps/dpi-playground/apps/dpi-playground-hub/src/main.ts +460 -0
  185. package/apps/dpi-playground/apps/dpi-playground-hub/src/monitor.ts +320 -0
  186. package/apps/dpi-playground/apps/dpi-playground-hub/src/mux.ts +277 -0
  187. package/apps/dpi-playground/apps/dpi-playground-hub/src/pairing.ts +816 -0
  188. package/apps/dpi-playground/apps/dpi-playground-hub/src/proxy.ts +128 -0
  189. package/apps/dpi-playground/apps/dpi-playground-hub/src/rotation.ts +583 -0
  190. package/apps/dpi-playground/apps/dpi-playground-hub/src/seed.ts +11 -0
  191. package/apps/dpi-playground/apps/dpi-playground-hub/src/transport.ts +108 -0
  192. package/apps/dpi-playground/apps/dpi-playground-hub/src/tunnel-token.ts +11 -0
  193. package/apps/dpi-playground/apps/dpi-playground-hub/src/tunnel-transport.ts +560 -0
  194. package/apps/dpi-playground/apps/dpi-playground-hub/src/tunnel.ts +873 -0
  195. package/apps/dpi-playground/apps/dpi-playground-hub/tsconfig.json +13 -0
  196. package/apps/dpi-playground/package.json +37 -0
  197. package/apps/dpi-sdk/README.md +74 -0
  198. package/apps/dpi-sdk/package.json +20 -0
  199. package/apps/dpi-sdk/scripts/net.ts +73 -0
  200. package/apps/dpi-sdk/src/api.ts +631 -0
  201. package/apps/dpi-sdk/src/app-state.ts +111 -0
  202. package/apps/dpi-sdk/src/client.ts +330 -0
  203. package/apps/dpi-sdk/src/errors.ts +16 -0
  204. package/apps/dpi-sdk/src/jobs.ts +99 -0
  205. package/apps/dpi-sdk/src/models.ts +472 -0
  206. package/apps/dpi-sdk/src/node/chunk.ts +488 -0
  207. package/apps/dpi-sdk/src/node/config.ts +175 -0
  208. package/apps/dpi-sdk/src/node/drivers.ts +402 -0
  209. package/apps/dpi-sdk/src/node/ops.ts +114 -0
  210. package/apps/dpi-sdk/src/node/private-dir.ts +48 -0
  211. package/apps/dpi-sdk/src/node/registry-migration.ts +549 -0
  212. package/apps/dpi-sdk/src/node/registry.ts +390 -0
  213. package/apps/dpi-sdk/src/node/sse-writer.ts +151 -0
  214. package/apps/dpi-sdk/src/protocol/backoff.ts +31 -0
  215. package/apps/dpi-sdk/src/protocol/limits.ts +199 -0
  216. package/apps/dpi-sdk/src/protocol/pairing.ts +311 -0
  217. package/apps/dpi-sdk/src/protocol/rest-routes.ts +105 -0
  218. package/apps/dpi-sdk/src/protocol/tunnel.ts +331 -0
  219. package/apps/dpi-sdk/src/sse.ts +117 -0
  220. package/apps/dpi-sdk/src/transcript.ts +291 -0
  221. package/apps/dpi-sdk/tsconfig.json +13 -0
  222. package/apps/dpi-sdk/w +20 -0
  223. package/apps/dpi-tui/README.md +54 -0
  224. package/apps/dpi-tui/bin/dpi-tui.mjs +5 -0
  225. package/apps/dpi-tui/package.json +20 -0
  226. package/apps/dpi-tui/src/app.ts +433 -0
  227. package/apps/dpi-tui/src/commands.ts +151 -0
  228. package/apps/dpi-tui/src/main.ts +122 -0
  229. package/apps/dpi-tui/src/pairing.ts +287 -0
  230. package/apps/dpi-tui/src/pickers.ts +238 -0
  231. package/apps/dpi-tui/src/render.ts +164 -0
  232. package/apps/dpi-tui/src/theme.ts +57 -0
  233. package/apps/dpi-tui/tsconfig.json +14 -0
  234. package/bin/device.mjs +5 -0
  235. package/bin/hub.mjs +5 -0
  236. package/bin/tui.mjs +5 -0
  237. package/deploy/Caddyfile +120 -0
  238. package/docs/verification.md +102 -0
  239. package/dpi-network.example.yaml +62 -0
  240. package/images/01_durable_agent_network.svg +185 -0
  241. package/images/02_pi_made_durable.svg +204 -0
  242. package/images/03_restart_mid_tool.svg +168 -0
  243. package/images/07_one_api_every_client.svg +123 -0
  244. package/images/08_device_session_agent.svg +123 -0
  245. package/images/09_compact_keep_history.svg +143 -0
  246. package/images/10_shared_app_state.svg +177 -0
  247. package/images/11_durable_jobs_wait.svg +141 -0
  248. package/images/12_tools_change_mid_run.svg +231 -0
  249. package/images/13_hook_blocks_tool.svg +122 -0
  250. package/images/14_fork_at_any_entry.svg +119 -0
  251. package/images/15_web_client.png +0 -0
  252. package/images/16_terminal_client.svg +43 -0
  253. package/images/17_devices_are_data.svg +182 -0
  254. package/images/18_should_every_agent_be_durable.svg +118 -0
  255. package/images/19_sqlite_on_every_device.svg +155 -0
  256. package/images/20_admission_and_effects.svg +134 -0
  257. package/justfile +33 -0
  258. package/package.json +41 -4
  259. package/scripts/verify.mjs +210 -0
@@ -0,0 +1,51 @@
1
+ > Source: [packages/durable/CHANGELOG.md](https://github.com/earendil-works/pi/blob/b78e6a9085343ec0f308c3d377da72528b4cf7ee/packages/durable/CHANGELOG.md)
2
+ > Retrieved with Firecrawl: 2026-10-05. Repository snapshot: `b78e6a9085343ec0f308c3d377da72528b4cf7ee`.
3
+ > Relative documentation links point to local copies where available, otherwise to the source snapshot.
4
+
5
+ # Changelog
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [1.0.3] - 2026-10-05
10
+
11
+ ### Breaking Changes
12
+
13
+ - `FileSystem` requires `openBinaryReader()` and `openDirReader()`; custom environments must implement them.
14
+ - `BinaryReader` requires `scanLines()`.
15
+ - `FileSystem` requires `watch()`.
16
+ - `Shell.exec()` accepts an argv array besides a shell string, and `ShellExecOptions.onOutput` receives a third `info` argument naming the stream (`stdout` or `stderr`); custom environments must accept both forms and pass the stream.
17
+
18
+ ### Added
19
+
20
+ - `openBinaryReader()` for bounded positional reads of one opened regular file, with `noFollow` to refuse a final-component symlink.
21
+ - `openDirReader()` for paged directory listings that read metadata only for returned entries.
22
+ - Argv form of `exec()`, which runs a program without a shell.
23
+ - `createEnvConformance()` and `registerEnvConformance()` in `@earendil-works/pi-durable/testing` for checking custom `ExecutionEnv` implementations.
24
+ - `settings.progress` with `partialIntervalMs` and `outputIntervalMs` configures how often generation partials and running tool output are committed; defaults stay 100 ms ([#10357](https://github.com/earendil-works/pi/issues/10357))
25
+ - `ShellExecOptions.window` and `ShellOutputInfo.skipped`: an environment may omit shell output outside the caller's retained tail and report the omission, so remote environments need not transfer output the caller drops. `ToolExecutionApi.outputWindow` provides the window and `output(chunk, skipped)` counts omissions; the `bash` tool passes them through.
26
+ - `BinaryReader.scanLines()` locates and measures a span of lines in one pass inside the environment; `LineScanner`, `StreamDecoder`, `rangeDecoder()` and `startsWithBom()` in `@earendil-works/pi-durable/env` let other environments decode and scan exactly like `NodeExecutionEnv`.
27
+ - `FileSystem.watch()` reports changes to files and directories, recursive with excludes, including missing targets, with explicit `overflow` and `error` and a `native` or `polling` mode. `NodeExecutionEnv` polls on Windows (native watchers there keep directories open, which blocks renaming their parents) and on network and FUSE file systems, and on macOS rescans shortly after installing native watchers because FSEvents misses changes made right after `fs.watch` returns; its `watch` option sets the mode, poll interval, and directory limit.
28
+
29
+ ### Changed
30
+
31
+ - The `read` tool reads only the file's header, one scan, and the lines it shows, instead of loading the whole file; its results are unchanged.
32
+
33
+ ### Fixed
34
+
35
+ - `NodeExecutionEnv.flushFile()` on a directory fails with `is_directory` on Windows, as on POSIX.
36
+ - Tail-retained tool output no longer depends on when progress commits happened: a progress snapshot compacted the stored output to the kept window, which could move where a later window's first line started.
37
+ - `NodeExecutionEnv` shell output and text line readers no longer drop a U+FEFF that follows a chunk boundary; Node's streaming `TextDecoder` with BOM handling dropped it, unlike decoding all of the bytes at once.
38
+
39
+ ## [1.0.2] - 2026-10-04
40
+
41
+ ### Fixed
42
+
43
+ - Persisted a distinct provider session UUID per conversation and forwarded it for prompt-cache and session affinity ([#10424](https://github.com/earendil-works/pi/issues/10424))
44
+
45
+ ## [1.0.1] - 2026-10-03
46
+
47
+ ## [1.0.0] - 2026-10-01
48
+
49
+ ### Added
50
+
51
+ - Initial release of `@earendil-works/pi-durable`, a durable agent harness. See the [README](readme.md) and the [design document](https://github.com/earendil-works/pi/blob/main/packages/durable/docs/spec.md).
@@ -0,0 +1,362 @@
1
+ > Source: [packages/durable/docs/chord-delta-findings.md](https://github.com/earendil-works/pi/blob/b78e6a9085343ec0f308c3d377da72528b4cf7ee/packages/durable/docs/chord-delta-findings.md)
2
+ > Retrieved with Firecrawl: 2026-10-05. Repository snapshot: `b78e6a9085343ec0f308c3d377da72528b4cf7ee`.
3
+ > Relative documentation links point to local copies where available, otherwise to the source snapshot.
4
+
5
+ # Chord delta investigation findings
6
+
7
+ ## Decision and scope
8
+
9
+ The standalone ID-addressed graph tracker experiment was removed. It solved
10
+ reference reassignment and replicated identity, but its memory, import, and
11
+ hydration costs were unacceptable for the drawing workload. This rejects the
12
+ measured implementation, not every possible graph representation.
13
+
14
+ Keep the existing tree/path delta implementation, its weak proxy-cache changes,
15
+ and the spread-first cloning improvement. Keeping the weak-cache patch is not a
16
+ claim that its performance is satisfactory: permanent retention improved greatly,
17
+ but exhaustive reads still allocate gigabytes and became slower.
18
+
19
+ The graph prototype never had a package export or Chord service integration.
20
+ Removing it does not change State/ReplicatedState or adopt graph semantics in the
21
+ Pico specifications. Existing delta alias behavior remains a separate correctness
22
+ problem; this investigation did not establish general mutable-graph correctness
23
+ for the tree protocol.
24
+
25
+ ## Workload and measurement rules
26
+
27
+ Measured on an Apple M5 Max with 128 GiB RAM, Node v26.0.0. The document contains
28
+ 20,000 strokes with 100 separately allocated `{ x, y, pressure }` point objects
29
+ each: two million points, approximately 2.04 million total objects/arrays. It is
30
+ not a packed numeric buffer. Full traversal uses a flat strokes array; mutation
31
+ benchmarks partition strokes into layers and include a 200 KB string.
32
+
33
+ The plain document occupies about **139.5 MiB of JavaScript heap**. Its roughly
34
+ 66 MB serialized representation is a different measurement.
35
+
36
+ - Use independent processes, three trials per configuration, and rotated variant
37
+ order. Report medians. Large graph/traversal workers used an 8 GiB V8 heap limit.
38
+ - Separate initial import, snapshot publication, mutation, flush, JSON encoding,
39
+ decoding, and replica application. Fixture construction is outside mutation
40
+ timing; importing a newly assigned object is inside it.
41
+ - Measure producer-only and producer-plus-replica memory separately. Release raw
42
+ graph inputs and emitted batches in separate call frames before retained-heap
43
+ measurements; otherwise temporary references can pin entire extra documents.
44
+ - Ready/retained heap is measured after GC, relative to the warmed empty process.
45
+ Sampled heap includes temporary allocations and uncollected garbage. It is not
46
+ an exact allocation peak. Maximum RSS includes heap capacity and native/runtime
47
+ allocations, not just live JavaScript objects.
48
+ - Weak-reference reclamation requires real job/event-loop boundaries, not merely
49
+ a synchronous `global.gc()` after a loop. Yield, collect, and let finalizers run.
50
+ - Validate serialized replay outside timing. Graph correctness must compare
51
+ identity/topology as well as values: deep equality alone misses lost aliases.
52
+ - Raw-object and minimal-proxy controls perform no replication. They are local
53
+ cost floors, not equivalent implementations.
54
+
55
+ The historical measurements below predate the final spread-first clone change
56
+ unless explicitly stated. They are not new measurements of the current source.
57
+
58
+ ## Operation log versus cloned baseline
59
+
60
+ The operation log at `2c995acf4` compares whole-container assignments immediately
61
+ and coalesces recorded operations. The baseline design at `86bac52f9` marks dirty
62
+ paths, compares against a retained copy at flush, and refreshes that copy.
63
+
64
+ A layer swap illustrates the difference. Two independently allocated layers can
65
+ have equal geometry and different names. Both designs eventually emit just two
66
+ name sets, **73 bytes**, but still inspect the geometry to discover that result.
67
+ The baseline additionally refreshes its copy. Tiny output does not imply cheap
68
+ mutation or flush.
69
+
70
+ For a fair clone-cost comparison, only the baseline's `cloneJson` helper was
71
+ replaced with the exact helper from `2c995acf4`. No diff, coalescing, baseline
72
+ refresh, or proxy behavior changed. This was the earlier dynamic-assignment
73
+ helper, not the later spread-first helper.
74
+
75
+ Mutation plus flush, milliseconds:
76
+
77
+ | Workload | Original baseline | Matched-clone baseline | Operation log |
78
+ | --- | ---: | ---: | ---: |
79
+ | One stroke swap | 0.114 | 0.086 | 0.085 |
80
+ | Swap 1,000-stroke layers | 89.940 | 47.924 | 21.048 |
81
+ | Swap 10,000-stroke layers | 906.390 | 483.264 | 214.425 |
82
+ | Ten 1,000-stroke swaps before flush | 90.598 | 49.455 | 209.346 |
83
+ | Sixty selections before flush | 0.121 | 0.101 | 2.346 |
84
+ | Swap dissimilar 100-stroke layers | 12.097 | 7.515 | 168.273 |
85
+
86
+ Batching favors the baseline because it compares the final dirty region once;
87
+ the operation log pays assignment-time comparison repeatedly. Ten swaps restore
88
+ the original value and emit no operations in all three variants. The dissimilar
89
+ case triggers the operation log's 4,096-metadata threshold and publishes a roughly
90
+ 66 MB root replacement; the baselines emit about 2 MB of patches. This difference
91
+ is not caused by the cloning helper.
92
+
93
+ All 63 independent-process configurations completed. Small-fixture checks and
94
+ full-size serialized replay checks passed; original and matched-clone baselines
95
+ emitted identical batches in the checked cases. RHS references came from
96
+ `tracker.target` to model unwrapping. These benchmarks do **not** establish general
97
+ alias correctness.
98
+
99
+ ### Semantics cannot be inferred from passing performance cases
100
+
101
+ Historical probes found replica divergences around aliases, held references,
102
+ reindexing, and detach/reinsert, despite the existing tests passing. The baseline
103
+ also diverged for shared initial objects and held elements after `unshift`.
104
+
105
+ For example, selecting an item by reference and subsequently editing it means
106
+ two producer paths observe one object. A tree replica holds two separate copies;
107
+ it needs updates at both paths. Keeping a reference across an array insertion
108
+ also means subsequent writes must follow the object's new position, not its old
109
+ index. Neither cloning a baseline nor caching a proxy at a path automatically
110
+ solves these cases.
111
+
112
+ Other approaches considered:
113
+
114
+ - Deep copy-on-assignment changes alias semantics and adds traversal/allocation.
115
+ Earlier clone measurements were about 7.4 ms for 1,000 strokes, 76.6 ms for
116
+ 10,000, and 153.8 ms for 20,000. Adding copies increased a large swap from about
117
+ 213 ms to 385 ms. Those figures predate spread-first cloning.
118
+ - Persistent trees share unchanged structure and avoid a permanent complete
119
+ baseline copy. They do not independently solve live aliases or relocation.
120
+ - A path-based wire copy operation needs ordering barriers: existing operation
121
+ coalescing can otherwise change the source before the copy executes.
122
+ - Declared reference fields/application IDs, stricter tree ownership, and explicit
123
+ path setters change the contract. No replacement API was selected; ordinary
124
+ JavaScript mutation syntax remained the desired interface.
125
+
126
+ ## What the graph experiment did
127
+
128
+ Each object/array became a canonical node with a numeric ID. Scalars were stored
129
+ inline; object edges referenced canonical nodes. A map indexed nodes by ID, a
130
+ weak map recognized imported raw objects, and lazy producer proxies exposed
131
+ ordinary mutation syntax. This was normalized graph storage, not a reverse-edge
132
+ index layered beside a second complete document.
133
+
134
+ Wire references used `["@", id]`, with separate object/array definitions and
135
+ ID-addressed set, delete, splice, length, and release operations. Revisioned
136
+ snapshots allowed recovery. Replicas allocated shells before linking definitions,
137
+ materializing actual JavaScript objects with aliases and cycles. Incremental
138
+ updates preserved held replica identities; rebasing reset identities.
139
+
140
+ Known-object assignments became reference writes. Swapping two huge layers no
141
+ longer walked their descendants. Fresh independently reconstructed equal objects
142
+ were new identities, however, and needed complete definitions rather than a
143
+ small value diff. Detached nodes remained resident until explicit collection.
144
+
145
+ Fifty tests covered topology, cycles, held references, array operations, import
146
+ rollback, revision recovery, collection, and deterministic mixed mutations. The
147
+ full benchmark completed 120 independent processes with value validation and
148
+ held-identity checks for graph swaps. The initial full-traversal comparison added
149
+ 12 processes. Correctness on these cases did not make the implementation cheap.
150
+
151
+ ### Graph mutation and publication costs
152
+
153
+ Historical sibling delta before the weak-cache change versus graph, mutation
154
+ plus flush:
155
+
156
+ | Workload | Tree delta ms | Graph ms | Tree bytes | Graph bytes |
157
+ | --- | ---: | ---: | ---: | ---: |
158
+ | 100 scattered edits | 0.265 | 0.158 | 5,243 | 2,235 |
159
+ | Swap 1,000-stroke layers | 21.523 | 0.002334 | 73 | 111 |
160
+ | Swap 10,000-stroke layers | 221.373 | 0.002584 | 73 | 109 |
161
+ | Ten 1,000-stroke swaps before flush | 211.370 | 0.015083 | 2 | 471 |
162
+ | Sixty selections before flush | 0.768 | 0.046125 | 66 | 102 |
163
+ | Append one new 100-point stroke | 0.011 | 0.095 | 3,357 | 7,567 |
164
+ | Fresh equal-geometry 1,000-stroke layer | 12.311 | 78.716 | 37 | 7,440,956 |
165
+ | Append ten characters to a 200 KB string | 0.010 | 0.000958 | 29 | 200,299 |
166
+
167
+ Graph bytes include its envelope; tree bytes are decoded operation arrays, not
168
+ path-dictionary encoding. The formats provide different identity guarantees.
169
+ The graph retained ordered reference writes for cancelled swaps. Its missing
170
+ string append/truncate compression was a prototype limitation, not an inherent
171
+ cost of graph addressing.
172
+
173
+ With JSON encode/decode and replica application included, the fresh-layer case
174
+ cost **165.773 ms** for graph versus **12.017 ms** for tree delta. The 10,000-stroke
175
+ swap pipeline cost **0.009958 ms** versus **214.119 ms**. Initial import and
176
+ hydration are not included in those incremental numbers.
177
+
178
+ Twenty thousand hot nested reads cost about **20.84 ms** for graph, **9.12 ms** for
179
+ tree delta, **6.79 ms** for a minimal proxy, and **0.082 ms** for raw objects. This
180
+ small working set touched only 1,000 points and missed exhaustive-read allocation.
181
+
182
+ ### Initial graph costs and lifetime
183
+
184
+ Representative scalar-workload measurements:
185
+
186
+ | Metric | Tree delta | Graph |
187
+ | --- | ---: | ---: |
188
+ | Ready producer heap | 139.5 MiB | 430.4 MiB |
189
+ | Ready producer plus replica | 279.3 MiB | 719.3 MiB |
190
+ | Initial import | 0.020 ms | 1,118.907 ms |
191
+ | Snapshot flush | 169.218 ms | 594.904 ms |
192
+ | Snapshot wire size | 66,364,338 bytes | 147,487,101 bytes |
193
+ | Snapshot JSON encode | 176.961 ms | 366.942 ms |
194
+ | Snapshot JSON decode | 159.388 ms | 1,179.166 ms |
195
+ | Snapshot apply | 0.027 ms | 1,417.161 ms |
196
+ | Pipeline-process maximum RSS | 852.8 MiB | 4,978.0 MiB |
197
+
198
+ Snapshot phase timings came from a separately requested rebase. Tree application
199
+ adopts the already decoded snapshot; graph application validates and materializes
200
+ objects. RSS includes earlier import/hydration allocations and is not a minimum
201
+ live-set requirement.
202
+
203
+ Five fresh 1,000-stroke layer replacements grew graph producer heap from **430.2
204
+ to 563.9 MiB**. Explicit collection removed 510,010 nodes in **705.5 ms**, emitted
205
+ a **7.14 MB** release batch, and left **486.3 MiB** retained. Producer plus replica
206
+ went from **719.2 to 967.0 MiB**, then **831.2 MiB** after collection. Lookup-table
207
+ capacity and externally held values can prevent a return to the ready footprint.
208
+
209
+ Automatic collection, immutable historical snapshots, bounded logs, hardened
210
+ transport limits, and service integration were not implemented.
211
+
212
+ ## Full traversal exposed the memory problem
213
+
214
+ Reading every point through producer proxies allocated wrappers for nearly every
215
+ container. Historical retained-heap results, after traversal and GC:
216
+
217
+ | Variant | Ready MiB | After full read MiB | Cold read ms | Warm read ms |
218
+ | --- | ---: | ---: | ---: | ---: |
219
+ | Raw document | 139.47 | 139.49 | 8.00 | 2.95 |
220
+ | Minimal cached read-only proxy | 139.47 | 452.50 | 705.10 | 550.35 |
221
+ | Tree delta before weak caches | 139.26 | 3,525.93 | 2,720.98 | 470.89 |
222
+ | Graph prototype | 430.39 | 2,159.75 | 1,437.65 | 388.74 |
223
+ | Historical matched-clone baseline | 294.22 | 2,228.71 | 827.13 | 360.95 |
224
+
225
+ The baseline row was measured in a later three-process run using asynchronous GC
226
+ boundaries, not extrapolated from its ready heap. Its immediately sampled heap
227
+ was **2,245.37 MiB** and maximum RSS **2,456.30 MiB**. No diff was running: this was
228
+ read-only traversal. All three historical trackers strongly cached wrappers.
229
+
230
+ ### Existing delta: permanent retention fixed, allocation volume not fixed
231
+
232
+ A separate matched before/after run measured the weak-cache patch:
233
+
234
+ | Metric | Before | After |
235
+ | --- | ---: | ---: |
236
+ | Ready producer heap | 139.50 MiB | 139.50 MiB |
237
+ | Retained after traversal, job boundaries, and GC | 3,526.17 MiB | 203.54 MiB |
238
+ | Immediately sampled heap after cold read | 3,531.93 MiB | 2,491.78 MiB |
239
+ | Maximum RSS | 3,858.11 MiB | 3,391.06 MiB |
240
+ | Cold traversal | 2,397.40 ms | 6,298.76 ms |
241
+ | Warm same-job traversal | 466.43 ms | 844.13 ms |
242
+
243
+ These are separate runs from the preceding table. Warm time is the median of
244
+ three traversals per process after collection: the first reconstructs collected
245
+ wrappers and the following two reuse same-job caches.
246
+
247
+ The patch separates public proxies from entry/placement metadata, uses weak
248
+ ordinary caches and incarnation-safe finalizer cleanup, and keeps explicit alias
249
+ metadata separately. Held descendants retain ancestor metadata so array
250
+ reindexing still works if ancestor public proxies were collected. Assigned known
251
+ proxies are unwrapped, avoiding wrappers embedded in raw targets. Placement reuse
252
+ prevents duplicate splice emission after reindexing or reattachment. Shared
253
+ handlers, lazy child maps, and job-local caches reduce some overhead.
254
+
255
+ But one point read can still allocate a proxy, handler, tracking entry, placement
256
+ record, placement set, weak references, cache entries, and finalization records.
257
+ Across roughly two million containers, this is gigabytes. `WeakRef` targets newly
258
+ created or dereferenced during an uninterrupted synchronous traversal must remain
259
+ alive through that job. The temporary strong caches also survive until scheduled
260
+ microtask cleanup. Collection afterward does not prevent the allocation peak.
261
+
262
+ The remaining roughly **64 MiB** above the raw document after collection is
263
+ bookkeeping/index overhead; its exact composition needs a heap profile. The
264
+ roughly **1.2 KB per visited container** of additional immediately sampled heap is
265
+ an aggregate average, not an allocation-by-allocation attribution.
266
+
267
+ Eleven isolated GC regressions cover unused proxy reclamation, tracker disposal,
268
+ held identities and descendants, array reacquisition/reindexing, alias rewrapping,
269
+ detach/reinsert, root rebasing, blocked views, and repeated wide-root read windows.
270
+ Twelve windows over a 10,000-object root array retained about 0.35 MiB bookkeeping.
271
+ Normal mutation correctness must not depend on finalizers running promptly.
272
+
273
+ This patch does not resolve the general tree-protocol alias problem. Bulk
274
+ read-only inspection through `tracker.target` avoids proxy construction, but
275
+ writing through it bypasses tracking. That escape hatch does not make normal
276
+ producer-proxy traversal cheap.
277
+
278
+ ## Clone layout: a separate, smaller improvement
279
+
280
+ Why did a 139.5 MiB document plus a baseline occupy 294.2 MiB rather than 279 MiB?
281
+ V8 layout inspection on this Node version showed:
282
+
283
+ - A literal `{ x, y, pressure }` occupies 48 bytes, with three in-object slots.
284
+ - Building `{}` by dynamic assignment occupies 56 bytes, with one unused slot.
285
+ - Two million points times eight bytes accounts for **15.26 MiB** extra.
286
+
287
+ The old clone therefore occupied about **154.7 MiB**, not 139.5 MiB. The discrepancy
288
+ was not hidden proxy allocation.
289
+
290
+ Shallow object spread followed by recursive replacement of object-valued children
291
+ kept the compact layout. Arrays retain their recursive array branch; null-prototype
292
+ objects retain a null prototype. Full-fixture clone-only measurements, three fresh
293
+ processes per method:
294
+
295
+ | Clone construction | Additional retained heap | Median clone time |
296
+ | --- | ---: | ---: |
297
+ | Dynamic property assignment | 154.725 MiB | 162.338 ms |
298
+ | Spread first, then recurse | 139.466 MiB | 93.924 ms |
299
+
300
+ This helper was adopted in existing delta. Regression tests cover detached nested
301
+ values, null prototypes, inherited-name collisions, independent expansion of
302
+ shared input values, and published payload isolation. V8 layouts/timings are
303
+ engine-specific. `structuredClone()` still produced the 56-byte point layout in
304
+ the layout probe; JSON round-trip produced 48-byte points but introduces text
305
+ encoding/decoding and temporary text allocation.
306
+
307
+ A baseline using the new helper would be expected to need about **279 MiB** for
308
+ document plus copy. That is an inference from measured clone sizes, not a rerun
309
+ of the complete historical baseline. The improvement does not address gigabytes
310
+ of proxy bookkeeping during traversal.
311
+
312
+ ## Retained reproduction and provenance
313
+
314
+ From the repository root:
315
+
316
+ ```sh
317
+ node --expose-gc packages/chord/test/delta-traversal.bench.ts --quick --out /tmp/delta-traversal-quick.json
318
+ node --expose-gc packages/chord/test/delta-traversal.bench.ts --out /tmp/delta-traversal.json
319
+ node --expose-gc packages/chord/test/delta-traversal.bench.ts --modes delta --out /tmp/delta-only.json
320
+ ```
321
+
322
+ The retained benchmark supports raw, minimal-proxy, and current tree-delta modes.
323
+ It checks traversal results, uses separate processes, records source hashes, and
324
+ allows multiple event-loop/GC turns. It no longer contains the removed graph
325
+ implementation or reproduces the historical graph/mutation comparison.
326
+
327
+ Targeted tests from `packages/chord`:
328
+
329
+ ```sh
330
+ node ../../node_modules/vitest/dist/cli.js --run test/delta.test.ts test/delta-clone.test.ts test/delta-tracker/retention.test.ts test/services.test.ts test/service-wire.test.ts test/facets.test.ts
331
+ ```
332
+
333
+ Historical source identifiers:
334
+
335
+ - Baseline: `86bac52f9`; matched clone helper / operation log: `2c995acf4`.
336
+ - Pre-retention sibling delta SHA-256:
337
+ `af008c77367a0ba362637b3b0422576c752a205339a33eb26479a41ac087957f`.
338
+ - Weak-cache delta before spread-first cloning:
339
+ `7a51c9295543b6fb47e06d8460f122242ee9a96a0ef5653d5595b8219792a6d8`.
340
+ - Removed graph implementation:
341
+ `90893bfa02a7aeb0fc1da9fdd62f53027ab3d22728337d00f7040c19344fec6c`.
342
+
343
+ The graph code was an uncommitted experiment; its hash identifies the measured
344
+ source but does not provide a Git revision from which to restore it. Historical
345
+ raw outputs were left in `/tmp/chord-graph-prototype/` (`full.json`,
346
+ `traversal-final.json`, `retention-before.json`, `retention-final.json`, and
347
+ `baseline-traversal.json`). Matched-baseline data was in
348
+ `/tmp/pi-copy-cost.QAlME4/`; clone-only samples in `/tmp/chord-clone-compare.jsonl`.
349
+ These are optional local artifacts, not durable dependencies of this document.
350
+
351
+ ## Requirements for a future attempt
352
+
353
+ Start with exhaustive producer reads, not only small hot working sets and
354
+ mutation/flush benchmarks. Measure cold allocation, warm access, cross-job
355
+ reclamation, held identities, alias/reindex behavior, import, hydration, and
356
+ fresh-object replacement separately. Never present post-GC retained heap as peak
357
+ memory, tiny patches as proof of cheap comparison, or successful value replay as
358
+ proof of identity correctness.
359
+
360
+ No measured design in this investigation simultaneously achieved low retained
361
+ and transient memory, cheap ordinary reads, cheap large reference reassignment,
362
+ and the desired broad mutable-JavaScript semantics.
@@ -0,0 +1,70 @@
1
+ > Source: [packages/coding-agent/src/experimental/durable/README.md](https://github.com/earendil-works/pi/blob/b78e6a9085343ec0f308c3d377da72528b4cf7ee/packages/coding-agent/src/experimental/durable/README.md)
2
+ > Retrieved with Firecrawl: 2026-10-05. Repository snapshot: `b78e6a9085343ec0f308c3d377da72528b4cf7ee`.
3
+ > Relative documentation links point to local copies where available, otherwise to the source snapshot.
4
+
5
+ # durable
6
+
7
+ A small local coding agent on `@earendil-works/pi-durable`. One process owns the model runtime, the durable
8
+ Harness, its SQLite storage, and the TUI. It reuses pi's model runtime, auth, settings, system prompt, keybindings,
9
+ theme, and interactive components; the agent itself is the durable Harness with its built-in `CodingTools`.
10
+
11
+ ```bash
12
+ node --import ./packages/coding-agent/src/experimental/source-resolver.ts packages/coding-agent/src/experimental/durable/main.ts
13
+ node --import ./packages/coding-agent/src/experimental/source-resolver.ts packages/coding-agent/src/experimental/durable/main.ts --continue
14
+ ```
15
+
16
+ A new session starts with pi's default model and thinking level from `settings.json`. `--continue` opens the newest
17
+ session for the current directory. Sessions live under
18
+ `~/.pi/agent/experimental/durable-sessions/<cwd-hash>/<session>/session.sqlite`; a lock keeps a second process out
19
+ (a lock left by a crash goes stale after 10 seconds, and the next start waits for that). Log in with pi itself;
20
+ credentials are shared.
21
+
22
+ ## What it shows
23
+
24
+ - **Durability:** every streamed partial, tool output, queue, and turn is committed. Kill the process in the middle
25
+ of a tool call and start it again with `--continue`: the interrupted call gets an interrupted result and the turn
26
+ finishes. Nothing in the TUI handles recovery; it only renders the conversation view.
27
+ - **One view:** the TUI renders `Conversation.viewState()`, the structural mount of the transcript and the built-in
28
+ documents (`pi.live`, `pi.inbox`, `pi.agent`, `pi.usage`). Streaming, tool progress, the queue, retries,
29
+ compaction status, the model, and usage all come from it.
30
+ - **Subagents:** the `subagent` tool runs a task in a child conversation owned by the call. `/agents` switches the
31
+ view to any conversation, also while the subagent works, and the editor then talks to it: steer it while busy, or
32
+ keep chatting after the call returned. Esc aborts the shown conversation; aborting the main turn aborts its
33
+ subagents.
34
+ - **Task graph:** a live panel of `Harness.taskGraph()`, shown by default and toggled with `/tasks`: every live task, what it waits on, and the
35
+ conversations it owns.
36
+
37
+ ## Commands
38
+
39
+ - submit: prompt while idle, steer while busy
40
+ - follow-up key (`app.message.followUp`): queue a follow-up
41
+ - Esc: abort the shown conversation's work, including a manual compaction
42
+ - `/model` or the model key: select a model for the shown conversation
43
+ - thinking key (Shift+Tab): cycle thinking levels
44
+ - `/compact [instructions]`: summarize older context; reports "Nothing to compact" when the context fits in
45
+ `compaction.keepRecentTokens`
46
+ - `/agents`: switch conversations
47
+ - `/tasks`: hide or show the task panel
48
+ - tools expand key (Ctrl+O): expand tool output and compaction summaries
49
+ - clear key (Ctrl+C) or Ctrl+D: exit at once, unlike pi's clear-first Ctrl+C; work in flight resumes with
50
+ `--continue`
51
+
52
+ ## Layout
53
+
54
+ | file | role |
55
+ | --- | --- |
56
+ | `main.ts` | arguments, open, run, close |
57
+ | `sessions.ts` | session directories and the lock |
58
+ | `runtime.ts` | Harness, registry, settings, environments; the plain `DurableView` and `DurableController` |
59
+ | `prompt.ts` | pi's system prompt sections (tools, rules, docs, AGENTS.md, skills, cwd) as one extension |
60
+ | `subagent.ts` | the foreground subagent tool |
61
+ | `tui.ts` | rendering with pi's interactive components |
62
+
63
+ Compaction thresholds, retry policy, queue modes, and request timeouts come from pi's `settings.json` as loaded at
64
+ startup, read through Harness settings getters at each use. pi's HTTP dispatcher is configured as in pi; without it some provider streams end
65
+ early.
66
+
67
+ A turn that ends without an answer shows a notice; one recovered after a restart does not, since only submissions
68
+ made by this process are watched.
69
+
70
+ Not here: sessions list and resume picker, forks and tree navigation, extensions, prompt templates, images, `/login`.