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.
- package/.env.example +7 -0
- package/LICENSE +21 -0
- package/README.md +328 -2
- package/ai_docs/durable_pi/announcement.md +860 -0
- package/ai_docs/durable_pi/changelog.md +51 -0
- package/ai_docs/durable_pi/chord-delta-findings.md +362 -0
- package/ai_docs/durable_pi/coding-agent-integration.md +70 -0
- package/ai_docs/durable_pi/pico-v5-chord-usage.md +391 -0
- package/ai_docs/durable_pi/pico-v5-handoff.md +921 -0
- package/ai_docs/durable_pi/readme.md +617 -0
- package/ai_docs/durable_pi/sources.md +22 -0
- package/ai_docs/durable_pi/spec.md +4696 -0
- package/ai_docs/durable_pi/vacation-planner.md +57 -0
- package/ai_docs/pi/chord.md +268 -0
- package/ai_docs/pi/cli-integration.md +110 -0
- package/ai_docs/pi/cli.md +325 -0
- package/ai_docs/pi/codemode.md +211 -0
- package/ai_docs/pi/compaction.md +467 -0
- package/ai_docs/pi/configuration.md +51 -0
- package/ai_docs/pi/containerization.md +187 -0
- package/ai_docs/pi/custom-provider.md +177 -0
- package/ai_docs/pi/environment-variables.md +102 -0
- package/ai_docs/pi/extensions.md +278 -0
- package/ai_docs/pi/how-pi-works.md +53 -0
- package/ai_docs/pi/index.md +43 -0
- package/ai_docs/pi/json.md +230 -0
- package/ai_docs/pi/keybindings.md +198 -0
- package/ai_docs/pi/llama-cpp.md +118 -0
- package/ai_docs/pi/mcp.md +274 -0
- package/ai_docs/pi/message-types.md +265 -0
- package/ai_docs/pi/models.md +209 -0
- package/ai_docs/pi/overview.md +166 -0
- package/ai_docs/pi/packages.md +133 -0
- package/ai_docs/pi/pi-agent-core.md +568 -0
- package/ai_docs/pi/pi-ai.md +1942 -0
- package/ai_docs/pi/prompt-templates.md +63 -0
- package/ai_docs/pi/providers.md +204 -0
- package/ai_docs/pi/quickstart.md +140 -0
- package/ai_docs/pi/readme.md +99 -0
- package/ai_docs/pi/rpc-commands.md +864 -0
- package/ai_docs/pi/rpc-extension-ui.md +204 -0
- package/ai_docs/pi/rpc.md +197 -0
- package/ai_docs/pi/sdk.md +151 -0
- package/ai_docs/pi/security.md +103 -0
- package/ai_docs/pi/session-format.md +297 -0
- package/ai_docs/pi/sessions.md +70 -0
- package/ai_docs/pi/settings.md +173 -0
- package/ai_docs/pi/shell-aliases.md +97 -0
- package/ai_docs/pi/skills.md +97 -0
- package/ai_docs/pi/slash-commands.md +64 -0
- package/ai_docs/pi/sources.md +57 -0
- package/ai_docs/pi/terminal-setup.md +223 -0
- package/ai_docs/pi/termux.md +121 -0
- package/ai_docs/pi/themes.md +140 -0
- package/ai_docs/pi/tmux.md +59 -0
- package/ai_docs/pi/toolkit.md +168 -0
- package/ai_docs/pi/tui.md +129 -0
- package/ai_docs/pi/usage.md +98 -0
- package/ai_docs/pi/virtual-models.md +118 -0
- package/ai_docs/pi/windows.md +71 -0
- package/apps/dpi-backend/.nvmrc +1 -0
- package/apps/dpi-backend/API.md +99 -0
- package/apps/dpi-backend/README.md +119 -0
- package/apps/dpi-backend/deploy/backend.env.example +9 -0
- package/apps/dpi-backend/deploy/dpi-backend.plist +26 -0
- package/apps/dpi-backend/deploy/dpi-backend.service +22 -0
- package/apps/dpi-backend/package.json +33 -0
- package/apps/dpi-backend/prompts/one_sentence.md +3 -0
- package/apps/dpi-backend/prompts/smart_ass_opus_fix.md +169 -0
- package/apps/dpi-backend/scripts/check-contract.mjs +151 -0
- package/apps/dpi-backend/scripts/migrate-playground.ts +66 -0
- package/apps/dpi-backend/scripts/record-nested-fixture.mts +154 -0
- package/apps/dpi-backend/scripts/token.ts +57 -0
- package/apps/dpi-backend/src/addons.ts +139 -0
- package/apps/dpi-backend/src/auth.ts +20 -0
- package/apps/dpi-backend/src/config.ts +54 -0
- package/apps/dpi-backend/src/contract-check.ts +118 -0
- package/apps/dpi-backend/src/errors.ts +50 -0
- package/apps/dpi-backend/src/extensions.ts +510 -0
- package/apps/dpi-backend/src/faux.ts +80 -0
- package/apps/dpi-backend/src/history.ts +165 -0
- package/apps/dpi-backend/src/idempotency.ts +20 -0
- package/apps/dpi-backend/src/jev.ts +43 -0
- package/apps/dpi-backend/src/jobs.ts +790 -0
- package/apps/dpi-backend/src/main.ts +76 -0
- package/apps/dpi-backend/src/model-catalog.ts +180 -0
- package/apps/dpi-backend/src/pairing-files.ts +142 -0
- package/apps/dpi-backend/src/pairing.ts +459 -0
- package/apps/dpi-backend/src/routes.ts +217 -0
- package/apps/dpi-backend/src/shared-state.ts +85 -0
- package/apps/dpi-backend/src/store.ts +1240 -0
- package/apps/dpi-backend/src/streams.ts +256 -0
- package/apps/dpi-backend/src/tunnel.ts +955 -0
- package/apps/dpi-backend/tsconfig.json +13 -0
- package/apps/dpi-playground/README.md +83 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/assets/index-CFGc7lHT.js +77 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/assets/index-DFdKpwm_.css +1 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/anthropic.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/deepseek.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/durable-pi.svg +14 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/gemini.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/grok.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/kimi.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/lab.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/meta.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/minimax.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/openai.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/qwen.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/xiaomi.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/zai.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/index.html +16 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/index.html +15 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/package.json +5 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/anthropic.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/deepseek.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/durable-pi.svg +14 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/gemini.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/grok.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/kimi.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/lab.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/meta.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/minimax.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/openai.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/qwen.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/xiaomi.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/zai.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/App.vue +180 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/commands.ts +119 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/composables.ts +129 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/hub.ts +40 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/icon-path.ts +7 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/ids.ts +2 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/jobs.ts +80 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/link-policy.ts +16 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/markdown-policy.ts +115 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/markdown.ts +60 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/pair-close.ts +23 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/pair-handoff.ts +56 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/pair.ts +166 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/qrcode.ts +323 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/signature.ts +146 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/store.ts +373 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/stream.ts +183 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/AgentView.vue +342 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/Composer.vue +198 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/ContextBar.vue +254 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/DeviceCredentialsDialog.vue +40 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/DeviceIcon.vue +56 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/DirBrowser.vue +95 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/ExtensionsPanel.vue +96 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/HelpDialog.vue +69 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/Inspector.vue +425 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/JobCard.vue +221 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/MobileNav.vue +39 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/ModelPicker.vue +71 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/NetworkSidebar.vue +236 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/NewSessionDialog.vue +114 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/OfflinePanel.vue +128 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/PairDeviceWizard.vue +433 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/PromptDialog.vue +89 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/StateEditor.vue +179 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TasksPanel.vue +280 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/Toast.vue +11 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TokenManager.vue +175 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TopBar.vue +114 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TranscriptItem.vue +152 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TreeRow.vue +195 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TunnelStatusBar.vue +107 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/extensions.css +105 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/jobs.css +655 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/main.ts +22 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/network.css +675 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/style.css +3203 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/tsconfig.json +16 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/vite.config.ts +14 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/package.json +5 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/audit.ts +406 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/client-ip.ts +72 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/credentials.ts +265 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/guard.ts +43 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/http-errors.ts +156 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/identity.ts +60 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/limiter.ts +165 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/main.ts +460 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/monitor.ts +320 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/mux.ts +277 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/pairing.ts +816 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/proxy.ts +128 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/rotation.ts +583 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/seed.ts +11 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/transport.ts +108 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/tunnel-token.ts +11 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/tunnel-transport.ts +560 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/tunnel.ts +873 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/tsconfig.json +13 -0
- package/apps/dpi-playground/package.json +37 -0
- package/apps/dpi-sdk/README.md +74 -0
- package/apps/dpi-sdk/package.json +20 -0
- package/apps/dpi-sdk/scripts/net.ts +73 -0
- package/apps/dpi-sdk/src/api.ts +631 -0
- package/apps/dpi-sdk/src/app-state.ts +111 -0
- package/apps/dpi-sdk/src/client.ts +330 -0
- package/apps/dpi-sdk/src/errors.ts +16 -0
- package/apps/dpi-sdk/src/jobs.ts +99 -0
- package/apps/dpi-sdk/src/models.ts +472 -0
- package/apps/dpi-sdk/src/node/chunk.ts +488 -0
- package/apps/dpi-sdk/src/node/config.ts +175 -0
- package/apps/dpi-sdk/src/node/drivers.ts +402 -0
- package/apps/dpi-sdk/src/node/ops.ts +114 -0
- package/apps/dpi-sdk/src/node/private-dir.ts +48 -0
- package/apps/dpi-sdk/src/node/registry-migration.ts +549 -0
- package/apps/dpi-sdk/src/node/registry.ts +390 -0
- package/apps/dpi-sdk/src/node/sse-writer.ts +151 -0
- package/apps/dpi-sdk/src/protocol/backoff.ts +31 -0
- package/apps/dpi-sdk/src/protocol/limits.ts +199 -0
- package/apps/dpi-sdk/src/protocol/pairing.ts +311 -0
- package/apps/dpi-sdk/src/protocol/rest-routes.ts +105 -0
- package/apps/dpi-sdk/src/protocol/tunnel.ts +331 -0
- package/apps/dpi-sdk/src/sse.ts +117 -0
- package/apps/dpi-sdk/src/transcript.ts +291 -0
- package/apps/dpi-sdk/tsconfig.json +13 -0
- package/apps/dpi-sdk/w +20 -0
- package/apps/dpi-tui/README.md +54 -0
- package/apps/dpi-tui/bin/dpi-tui.mjs +5 -0
- package/apps/dpi-tui/package.json +20 -0
- package/apps/dpi-tui/src/app.ts +433 -0
- package/apps/dpi-tui/src/commands.ts +151 -0
- package/apps/dpi-tui/src/main.ts +122 -0
- package/apps/dpi-tui/src/pairing.ts +287 -0
- package/apps/dpi-tui/src/pickers.ts +238 -0
- package/apps/dpi-tui/src/render.ts +164 -0
- package/apps/dpi-tui/src/theme.ts +57 -0
- package/apps/dpi-tui/tsconfig.json +14 -0
- package/bin/device.mjs +5 -0
- package/bin/hub.mjs +5 -0
- package/bin/tui.mjs +5 -0
- package/deploy/Caddyfile +120 -0
- package/docs/verification.md +102 -0
- package/dpi-network.example.yaml +62 -0
- package/images/01_durable_agent_network.svg +185 -0
- package/images/02_pi_made_durable.svg +204 -0
- package/images/03_restart_mid_tool.svg +168 -0
- package/images/07_one_api_every_client.svg +123 -0
- package/images/08_device_session_agent.svg +123 -0
- package/images/09_compact_keep_history.svg +143 -0
- package/images/10_shared_app_state.svg +177 -0
- package/images/11_durable_jobs_wait.svg +141 -0
- package/images/12_tools_change_mid_run.svg +231 -0
- package/images/13_hook_blocks_tool.svg +122 -0
- package/images/14_fork_at_any_entry.svg +119 -0
- package/images/15_web_client.png +0 -0
- package/images/16_terminal_client.svg +43 -0
- package/images/17_devices_are_data.svg +182 -0
- package/images/18_should_every_agent_be_durable.svg +118 -0
- package/images/19_sqlite_on_every_device.svg +155 -0
- package/images/20_admission_and_effects.svg +134 -0
- package/justfile +33 -0
- package/package.json +41 -4
- 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`.
|