@xingular/harness 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +28 -25
  3. package/dist/cli.js +6675 -2501
  4. package/dist/sdk/accounts/accounts.d.ts +27 -3
  5. package/dist/sdk/accounts/accounts.js +78 -24
  6. package/dist/sdk/accounts/secrets.d.ts +12 -0
  7. package/dist/sdk/accounts/secrets.js +114 -7
  8. package/dist/sdk/accounts/store.d.ts +26 -1
  9. package/dist/sdk/accounts/store.js +1 -1
  10. package/dist/sdk/agents/builtin.js +15 -1
  11. package/dist/sdk/agents/planResearch.d.ts +5 -0
  12. package/dist/sdk/agents/planResearch.js +19 -0
  13. package/dist/sdk/agents/profiles.d.ts +2 -1
  14. package/dist/sdk/agents/profiles.js +9 -4
  15. package/dist/sdk/annotations/pageScript.js +349 -48
  16. package/dist/sdk/annotations/part.js +34 -23
  17. package/dist/sdk/annotations/store.d.ts +7 -0
  18. package/dist/sdk/annotations/store.js +13 -5
  19. package/dist/sdk/browser/backend.d.ts +16 -0
  20. package/dist/sdk/browser/backend.js +17 -0
  21. package/dist/sdk/browser/contracts.d.ts +19 -0
  22. package/dist/sdk/browser/contracts.js +116 -0
  23. package/dist/sdk/browser/tools.d.ts +1 -4
  24. package/dist/sdk/browser/tools.js +14 -91
  25. package/dist/sdk/cli.js +5 -4
  26. package/dist/sdk/commands/list.d.ts +3 -0
  27. package/dist/sdk/commands/list.js +75 -0
  28. package/dist/sdk/commands/registry.d.ts +250 -0
  29. package/dist/sdk/commands/registry.js +47 -0
  30. package/dist/sdk/commands/routes.d.ts +16 -0
  31. package/dist/sdk/commands/routes.js +33 -0
  32. package/dist/sdk/core/commandBudget.d.ts +23 -0
  33. package/dist/sdk/core/commandBudget.js +57 -0
  34. package/dist/sdk/core/index.js +17 -1
  35. package/dist/sdk/core/interactiveProfile.d.ts +46 -0
  36. package/dist/sdk/core/interactiveProfile.js +143 -0
  37. package/dist/sdk/core/workspace.d.ts +13 -0
  38. package/dist/sdk/core/workspace.js +88 -0
  39. package/dist/sdk/core/workspaceBashTool.d.ts +4 -0
  40. package/dist/sdk/core/workspaceBashTool.js +36 -0
  41. package/dist/sdk/core/workspaceSandbox.d.ts +31 -0
  42. package/dist/sdk/core/workspaceSandbox.js +320 -0
  43. package/dist/sdk/core/workspaceWriteOperations.d.ts +22 -0
  44. package/dist/sdk/core/workspaceWriteOperations.js +206 -0
  45. package/dist/sdk/doctor/types.d.ts +2 -2
  46. package/dist/sdk/harness.d.ts +7 -0
  47. package/dist/sdk/harness.js +42 -2
  48. package/dist/sdk/index.d.ts +6 -1
  49. package/dist/sdk/index.js +3 -0
  50. package/dist/sdk/pi/runtime.d.ts +2 -1
  51. package/dist/sdk/pi/runtime.js +2 -2
  52. package/dist/sdk/plugins/builtin.js +29 -12
  53. package/dist/sdk/plugins/builtinRegistry.d.ts +10 -3
  54. package/dist/sdk/plugins/builtinRegistry.js +30 -8
  55. package/dist/sdk/plugins/catalogue.js +2 -1
  56. package/dist/sdk/plugins/commandPrefix.d.ts +8 -0
  57. package/dist/sdk/plugins/commandPrefix.js +21 -0
  58. package/dist/sdk/plugins/firstParty.d.ts +1 -1
  59. package/dist/sdk/plugins/firstParty.js +8 -2
  60. package/dist/sdk/plugins/inventory.js +4 -3
  61. package/dist/sdk/plugins/manifest.d.ts +2 -0
  62. package/dist/sdk/plugins/manifest.js +2 -1
  63. package/dist/sdk/plugins/surfaces.d.ts +5 -0
  64. package/dist/sdk/plugins/surfaces.js +11 -0
  65. package/dist/sdk/plugins/types.d.ts +15 -0
  66. package/dist/sdk/prompt/compile.d.ts +14 -0
  67. package/dist/sdk/prompt/compile.js +65 -29
  68. package/dist/sdk/prompt/standard.d.ts +17 -0
  69. package/dist/sdk/prompt/standard.js +21 -0
  70. package/dist/sdk/server/methods.js +18 -0
  71. package/dist/sdk/server/remote.js +36 -11
  72. package/dist/sdk/sessions/compactionCheckpoint.d.ts +11 -0
  73. package/dist/sdk/sessions/compactionCheckpoint.js +79 -0
  74. package/dist/sdk/sessions/continuation.d.ts +3 -0
  75. package/dist/sdk/sessions/continuation.js +5 -1
  76. package/dist/sdk/sessions/contracts.d.ts +168 -0
  77. package/dist/sdk/sessions/contracts.js +59 -1
  78. package/dist/sdk/sessions/control.d.ts +72 -0
  79. package/dist/sdk/sessions/control.js +158 -0
  80. package/dist/sdk/sessions/initialBash.d.ts +7 -0
  81. package/dist/sdk/sessions/initialBash.js +7 -0
  82. package/dist/sdk/sessions/planMode.d.ts +14 -3
  83. package/dist/sdk/sessions/planMode.js +73 -16
  84. package/dist/sdk/sessions/registry.d.ts +1 -1
  85. package/dist/sdk/sessions/registry.js +2 -2
  86. package/dist/sdk/sessions/runStore.d.ts +14 -0
  87. package/dist/sdk/sessions/runStore.js +35 -0
  88. package/dist/sdk/sessions/runner.d.ts +40 -2
  89. package/dist/sdk/sessions/runner.js +161 -13
  90. package/dist/sdk/sessions/sessions.d.ts +23 -3
  91. package/dist/sdk/sessions/sessions.js +202 -17
  92. package/dist/sdk/sessions/subagentService.d.ts +9 -0
  93. package/dist/sdk/sessions/subagentService.js +63 -4
  94. package/dist/sdk/sessions/workSlice.d.ts +103 -0
  95. package/dist/sdk/sessions/workSlice.js +297 -0
  96. package/dist/sdk/sessions/workspaceBoundary.d.ts +15 -0
  97. package/dist/sdk/sessions/workspaceBoundary.js +46 -0
  98. package/dist/sdk/terminal/browserControl.js +2 -1
  99. package/dist/sdk/terminal/controller.d.ts +11 -3
  100. package/dist/sdk/terminal/controller.js +40 -13
  101. package/dist/sdk/terminal/opentui/theme.d.ts +43 -0
  102. package/dist/sdk/terminal/opentui/theme.js +87 -0
  103. package/dist/sdk/terminal/piExtension.d.ts +6 -1
  104. package/dist/sdk/terminal/piExtension.js +154 -54
  105. package/dist/sdk/terminal/standalone.d.ts +7 -0
  106. package/dist/sdk/terminal/standalone.js +72 -13
  107. package/dist/sdk/terminal/subagentMonitor.d.ts +11 -2
  108. package/dist/sdk/terminal/subagentMonitor.js +32 -1
  109. package/dist/sdk/terminal/terminals.d.ts +19 -2
  110. package/dist/sdk/terminal/terminals.js +245 -35
  111. package/dist/sdk/terminal/tui/annotateCommand.d.ts +1 -1
  112. package/dist/sdk/terminal/tui/annotateCommand.js +3 -3
  113. package/dist/sdk/terminal/tui/commandPages.d.ts +3 -1
  114. package/dist/sdk/terminal/tui/commandPages.js +3 -0
  115. package/dist/sdk/terminal/tui/commands.d.ts +2 -0
  116. package/dist/sdk/terminal/tui/commands.js +205 -124
  117. package/dist/sdk/terminal/tui/list.d.ts +14 -1
  118. package/dist/sdk/terminal/tui/list.js +96 -14
  119. package/dist/sdk/terminal/tui/pageOverlay.js +13 -4
  120. package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +1 -1
  121. package/dist/sdk/terminal/tui/pages/notesPage.d.ts +5 -5
  122. package/dist/sdk/terminal/tui/pages/notesPage.js +18 -18
  123. package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +0 -5
  124. package/dist/sdk/terminal/tui/pages/pluginsPage.js +9 -3
  125. package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +1 -0
  126. package/dist/sdk/terminal/tui/pages/subagentsPage.js +98 -16
  127. package/dist/sdk/terminal/tui/pages/usagePage.d.ts +43 -0
  128. package/dist/sdk/terminal/tui/pages/usagePage.js +370 -0
  129. package/dist/sdk/terminal/tui/piAdapter.d.ts +1 -2
  130. package/dist/sdk/terminal/tui/piAdapter.js +3 -5
  131. package/dist/sdk/terminal/tui/pluginCommands.d.ts +2 -0
  132. package/dist/sdk/terminal/tui/pluginCommands.js +13 -9
  133. package/dist/sdk/terminal/tui/runtime.js +23 -3
  134. package/dist/sdk/terminal/tui/sessionPicker.d.ts +40 -0
  135. package/dist/sdk/terminal/tui/sessionPicker.js +141 -0
  136. package/dist/sdk/tools/checklist.js +1 -0
  137. package/dist/sdk/tools/checkpoint.d.ts +27 -0
  138. package/dist/sdk/tools/checkpoint.js +154 -0
  139. package/dist/sdk/tools/getCapabilities.d.ts +31 -0
  140. package/dist/sdk/tools/getCapabilities.js +34 -0
  141. package/dist/sdk/tools/spawnSubagent.js +4 -3
  142. package/dist/sdk/usage/analytics.d.ts +20 -0
  143. package/dist/sdk/usage/analytics.js +151 -0
  144. package/dist/sdk/usage/ledger.d.ts +38 -0
  145. package/dist/sdk/usage/ledger.js +142 -0
  146. package/dist/sdk/usage/limits/adapters.d.ts +11 -0
  147. package/dist/sdk/usage/limits/adapters.js +349 -0
  148. package/dist/sdk/usage/limits/capture.d.ts +8 -0
  149. package/dist/sdk/usage/limits/capture.js +47 -0
  150. package/dist/sdk/usage/limits/http.d.ts +12 -0
  151. package/dist/sdk/usage/limits/http.js +51 -0
  152. package/dist/sdk/usage/limits/normalize.d.ts +15 -0
  153. package/dist/sdk/usage/limits/normalize.js +211 -0
  154. package/dist/sdk/usage/limits/service.d.ts +30 -0
  155. package/dist/sdk/usage/limits/service.js +176 -0
  156. package/dist/sdk/usage/limits/types.d.ts +65 -0
  157. package/dist/sdk/usage/limits/types.js +1 -0
  158. package/dist/sdk/usage/serverMethods.d.ts +3 -0
  159. package/dist/sdk/usage/serverMethods.js +22 -0
  160. package/dist/sdk/usage/service.d.ts +32 -0
  161. package/dist/sdk/usage/service.js +66 -0
  162. package/dist/sdk/usage/settings.d.ts +7 -0
  163. package/dist/sdk/usage/settings.js +28 -0
  164. package/dist/sdk/usage/types.d.ts +60 -0
  165. package/dist/sdk/usage/types.js +4 -0
  166. package/dist/terminal-opentui.js +5207 -1883
  167. package/dist/terminal-pi.js +1658 -271
  168. package/docs/architecture/02-sessions-and-runs.md +128 -26
  169. package/docs/architecture/04-prompt.md +50 -12
  170. package/docs/architecture/06-plugins-and-capabilities.md +24 -7
  171. package/docs/architecture/07-tools-and-gate.md +104 -24
  172. package/docs/architecture/08-memory-and-history.md +4 -2
  173. package/docs/architecture/09-accounts-and-connectors.md +31 -1
  174. package/docs/architecture/10-server-protocol.md +58 -3
  175. package/docs/architecture/11-terminal.md +105 -42
  176. package/docs/architecture/12-storage.md +30 -2
  177. package/docs/architecture/13-world-tools.md +22 -1
  178. package/docs/architecture/14-surfaces-and-parity.md +54 -7
  179. package/docs/architecture/README.md +3 -1
  180. package/docs/architecture/proposals/PROP-0017-page-annotations.md +6 -2
  181. package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +1 -1
  182. package/docs/architecture/proposals/PROP-0031-plugin-command-owner-namespaces.md +54 -0
  183. package/docs/architecture/proposals/PROP-0032-planning-quality.md +196 -0
  184. package/docs/architecture/proposals/PROP-0033-compaction-plan-checkpoints.md +75 -0
  185. package/docs/architecture/proposals/PROP-0034-conversation-fast-mode.md +120 -0
  186. package/docs/architecture/proposals/PROP-0035-retain-subagent-status.md +47 -0
  187. package/docs/architecture/proposals/PROP-0036-first-class-harness-commands.md +59 -0
  188. package/docs/architecture/proposals/PROP-0037-workspace-write-boundary.md +63 -0
  189. package/docs/architecture/proposals/PROP-0038-annotate-open-shadow-roots.md +73 -0
  190. package/docs/architecture/proposals/PROP-0039-workspace-boundary-hardening.md +139 -0
  191. package/docs/architecture/proposals/PROP-0040-one-launch-path.md +87 -0
  192. package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +247 -0
  193. package/docs/architecture/proposals/PROP-0042-usage-and-provider-limits.md +127 -0
  194. package/docs/architecture/proposals/PROP-0043-read-only-plan-bash.md +74 -0
  195. package/docs/architecture/proposals/README.md +14 -0
  196. package/docs/decisions/ADR-0001-owned-terminal.md +1 -1
  197. package/docs/decisions/ADR-0021-required-command-namespaces.md +1 -1
  198. package/docs/decisions/ADR-0031-plugin-command-owner-namespaces.md +37 -0
  199. package/docs/decisions/ADR-0032-compaction-run-checkpoints.md +33 -0
  200. package/docs/decisions/ADR-0033-plan-mode-research-delegation.md +47 -0
  201. package/docs/decisions/ADR-0034-first-class-harness-terminal-commands.md +25 -0
  202. package/docs/decisions/ADR-0035-one-launch-path.md +40 -0
  203. package/docs/decisions/ADR-0036-workspace-write-operation-boundary.md +54 -0
  204. package/docs/decisions/ADR-0037-usage-ledger-and-provider-limits.md +44 -0
  205. package/docs/decisions/ADR-0038-plan-bash-os-read-only.md +29 -0
  206. package/docs/experiments/EXP-0003-performance-audit/data/session-runtime-replication.json +420 -0
  207. package/docs/experiments/EXP-0003-performance-audit/data/session-runtime.json +420 -0
  208. package/docs/experiments/EXP-0003-performance-audit/data/terminal-compact.json +376 -0
  209. package/docs/experiments/EXP-0003-performance-audit/data/terminal-history.json +422 -0
  210. package/docs/experiments/EXP-0003-performance-audit/data/terminal-question.json +95 -0
  211. package/docs/experiments/EXP-0003-performance-audit/data/terminal-wide.json +1036 -0
  212. package/docs/experiments/EXP-0003-performance-audit/plan.md +109 -0
  213. package/docs/experiments/EXP-0003-performance-audit/results.md +151 -0
  214. package/docs/handoff-2026-10-25-app-parity-and-solo-tabs.md +202 -0
  215. package/docs/parity.md +80 -24
  216. package/docs/releases/1.1.1.md +28 -0
  217. package/docs/releases/1.2.0.md +49 -0
  218. package/docs/releases/4.0.0.md +10 -9
  219. package/docs/releases/README.md +35 -30
  220. package/docs/study/00-index.md +3 -0
  221. package/docs/study/STUDY-0003-long-horizon-runs.md +210 -0
  222. package/docs/terminal-tui.md +100 -59
  223. package/package.json +4 -3
@@ -0,0 +1,127 @@
1
+ ---
2
+ id: PROP-0042
3
+ title: Usage analytics and provider limits as a built-in capability
4
+ status: Accepted
5
+ created: 2026-10-26
6
+ updated: 2026-10-26
7
+ affects: [INV-MEM-5, INV-MEM-6, INV-STO-2, INV-SRV-1, INV-PLG-10, INV-ACC-3, 02-sessions-and-runs.md, 06-plugins-and-capabilities.md, 08-memory-and-history.md, 09-accounts-and-connectors.md, 10-server-protocol.md, 11-terminal.md, 12-storage.md, 14-surfaces-and-parity.md, parity.md]
8
+ ---
9
+
10
+ # PROP-0042: Usage analytics and provider limits as a built-in capability
11
+
12
+ ## Problem
13
+
14
+ A run already records what each model request used: `usage.updated` carries per-request tokens,
15
+ cost, provider, model, TTFT and duration (`src/sessions/contracts.ts`, emitted in
16
+ `src/sessions/runner.ts`). Nothing aggregates it, and the SQLite `runs` table stores only ids,
17
+ status and timestamps (`src/sessions/runIndex.ts`). A person cannot answer "what did this account
18
+ spend this week, on which model?" without reading run folders.
19
+
20
+ The account store already has the shape of provider limits — `Limit`, `AccountState.limits`, and
21
+ health derived from `usedPercent`/`resetsAt` (`src/accounts/store.ts`) — and `account.status`
22
+ carries a `limits` array (`src/sessions/contracts.ts`). No producer ever fills it: `Probe.limits`
23
+ is never assigned in `Accounts.probe`, and `runner.ts` only classifies provider error text. A
24
+ person cannot see how close a subscription plan, credit balance or rate window is.
25
+
26
+ The capability is something a person uses, so it belongs in the Harness with a terminal surface
27
+ before any host screen (`INV-PAR-1`).
28
+
29
+ ## Affected rules
30
+
31
+ - **INV-MEM-5** Files are the source of truth. `index.sqlite` is derived and can be deleted and
32
+ rebuilt. The usage ledger is derived data inside that index; it adds no run file.
33
+ - **INV-MEM-6** Index migrations are append-only. Migration 8 appends the usage tables.
34
+ - **INV-STO-2** `index.sqlite` can be deleted and rebuilt; the ledger is rebuilt from run events.
35
+ - **INV-SRV-1** A new `usage` method group is additive. Existing method names, params and result
36
+ shapes do not change.
37
+ - **INV-PLG-10** The methods and commands are contributed by one built-in feature module, so the
38
+ public method table remains assembled by `server/methods.ts` and the plugin has one owner.
39
+ - **INV-ACC-3** The harness never writes a secret to its own files. Provider limit reads use the
40
+ credential Pi already resolved through `Models.getAuth`, send it only to the provider, and store
41
+ no credential. A refreshed OAuth token is written only through the existing credential store.
42
+ - **INV-RUN-7** The meaning of every usage field is unchanged. The ledger copies them; aggregates
43
+ are new derived data.
44
+
45
+ ## Proposal
46
+
47
+ A built-in plugin `usage` owns a **read-only** capability with one full-screen terminal page and
48
+ two commands:
49
+
50
+ - `/usage:` and `/usage:analysis [today|7d|30d|all]` — token, cost and latency analytics from a
51
+ derived ledger.
52
+ - `/usage:limit` — what each provider last reported about its plan windows, balances or rate
53
+ limits.
54
+
55
+ **Ledger.** Migration 8 adds `usage_requests` (one row per model request) and `usage_indexed` to
56
+ `index.sqlite`. A run's ledger rows are written from its `usage.updated` events when the run ends,
57
+ and repaired by `open`, `rebuildIndex()` and the first analysis call when the ledger is empty. A
58
+ per-request row is used when the event carries `request`; a provider that reported only cumulative
59
+ totals contributes one row from its latest event. Cost uses the provider's own figure; a request
60
+ with no cost may be priced at Pi's catalogue rates for display, and the estimate is marked
61
+ separately, never presented as a provider figure. Aggregates are bounded: one scope, one period,
62
+ top ten rows per breakdown, at most 400 days, with an unpriced-request count.
63
+
64
+ **Limits.** Three sources, in this order:
65
+
66
+ 1. **Response headers**, captured by wrapping each account runtime's provider streams with
67
+ `onResponse`; a terminal session's runtime forwards only the limit-bearing headers over its
68
+ control-key-protected socket. Four families are understood: `anthropic-ratelimit-*`, Anthropic unified plan
69
+ windows, `x-codex-*` plan windows, and the common `x-ratelimit-*` headers. `Retry-After` on a
70
+ 429 is kept as a reset hint. Raw headers are never stored.
71
+ 2. **The provider's own usage endpoint**, read with `Models.getAuth` and a bounded HTTPS GET that
72
+ refuses redirects and other hosts: OpenRouter `GET /api/v1/key`, DeepSeek
73
+ `GET /user/balance`, Anthropic's organization `rate_limits` (admin credential only; configured
74
+ limits, not usage), ChatGPT/Codex `GET /backend-api/wham/usage` (account id from the access
75
+ token), and OpenCode Go `GET /zen/go/v1/usage`.
76
+ 3. **Unavailable**, with a reason. No percentage is ever invented. A provider with no documented
77
+ endpoint, a custom base URL, an expired sign-in or no support shows local usage only.
78
+
79
+ Undocumented plan endpoints stay behind the plugin setting `experimentalProbes` (off by
80
+ default). A reading is cached per account and provider for `refreshMinutes` (default 10); a
81
+ page open or `r` refreshes only what is due. Readings live in the account's existing state
82
+ (`limits`, plus a new `providerLimits` readout list) and never create a new file.
83
+
84
+ **Surfaces.** Public methods `usage.summary`, `usage.limits`, `usage.sync`; protected terminal
85
+ routes `terminal.usage.summary` and `terminal.usage.limits` for the page, all through the same
86
+ service. The App screen is a later host task, tracked as `PAR-016`.
87
+
88
+ ## Alternatives
89
+
90
+ - **Read every run's `events.jsonl` on demand.** Correct but unbounded: a year of runs is many
91
+ megabytes per page open. A derived table is what the index is for.
92
+ - **Store analytics in a new data folder.** Violates `INV-STO-3` and duplicates derived data the
93
+ index can rebuild.
94
+ - **Scrape provider dashboards.** Not stable, not authenticated, and not a provider API.
95
+ - **Show a percentage for every provider by assuming a cap.** Fabrication; rejected.
96
+ - **Make limit reads automatic in the background.** Surprise egress with a person's credential;
97
+ rejected in favour of page-open and explicit refresh.
98
+ - **Do nothing.** Leaves usage and limits unanswerable in both surfaces.
99
+
100
+ ## Consequences
101
+
102
+ - Two additive method routes and two terminal commands; no protocol version change.
103
+ - `index.sqlite` gains two tables (migration 8), rebuilt from run files.
104
+ - `AccountState` gains an optional `providerLimits` readout list and `Limit` gains optional
105
+ absolute fields and `remainingPercent`; old state files load with defaults, and old readers
106
+ ignore the additions.
107
+ - A page open may make one small read-only request per due provider, using the account's existing
108
+ credential. No model request, no token spend, no secret written.
109
+ - The harness remains the only writer of the ledger and of limit snapshots.
110
+
111
+ ## Migration
112
+
113
+ No data rewrite. The index migrates in one transaction with the existing backup/restore path; an
114
+ old index simply indexes existing runs on the next `open` or `usage.sync`. Account state files
115
+ without `providerLimits` read as an empty list. Reverting removes the plugin module and commands;
116
+ the extra index tables are inert.
117
+
118
+ ## Checks
119
+
120
+ - `npm run typecheck`, `npm test` (`src/usage/*.test.ts`, `src/plugins/usage.test.ts`).
121
+ - `npm run prove` with an injected reader: `usage.summary` after a fake recorded run and
122
+ `usage.limits` returning an explicit unavailable reason, with no network.
123
+ - Terminal verification of `/usage:`, `/usage:analysis`, `/usage:limit` and the two tabs.
124
+ - App checks (`npm run typecheck`, `npm test`, `npm run check:layers`, `npm run check:ui`,
125
+ `cargo test`) after the Harness change.
126
+ - Real provider header names are confirmed only by a separate owner-authorized request per
127
+ provider; fixtures keep only redacted header values.
@@ -0,0 +1,74 @@
1
+ ---
2
+ id: PROP-0043
3
+ title: Read-only Bash in plan mode, enforced by the OS sandbox
4
+ status: Accepted; implemented
5
+ created: 2026-10-26
6
+ updated: 2026-10-26
7
+ affects: [INV-RUN-8, INV-GATE-3, INV-GATE-8, 02-sessions-and-runs.md, 07-tools-and-gate.md, 11-terminal.md, terminal-tui.md, parity.md, ADR-0038]
8
+ ---
9
+
10
+ # PROP-0043: Read-only Bash in plan mode, enforced by the OS sandbox
11
+
12
+ ## Problem
13
+
14
+ Plan mode refuses Bash entirely: `planRefusal` (`src/sessions/planMode.ts`) did not list `bash`,
15
+ the terminal dropped it from the plan's active set (`planModeTools`), and a test pinned the refusal.
16
+ A plan therefore could not check its claims with read-only commands such as `git log`, `git diff`,
17
+ `rg` or `node --version`, although the plan instructions ask for evidence-grounded findings.
18
+
19
+ ## Affected rules
20
+
21
+ - `INV-RUN-8`: "… it has no connectors, user questions, writes, commands or nested subagents." (about
22
+ plan research children; unchanged) and the plan instruction "Do not change files or remote data, run
23
+ commands, …".
24
+ - `INV-GATE-3` / `INV-GATE-8`: default Bash writes inside the workspace; `outside` scope and Full
25
+ Permission can widen it.
26
+
27
+ ## Proposal
28
+
29
+ A plan run (SDK sessions and the terminal) may call the model's `bash` tool. Every such call runs in
30
+ a new **read-only scope** of the existing workspace sandbox (`core/workspaceSandbox.ts`):
31
+
32
+ - Reads are as broad as default Bash. Writes are denied everywhere except `/dev/null`, `/dev/tty`,
33
+ and a private temp folder made for the call; `TMPDIR` points to it and it is removed afterwards.
34
+ - No network. No signals outside the sandbox. On macOS no mach services at all, so a command cannot
35
+ act through system daemons (preferences, Apple Events, Launch Services). On Linux, `bwrap` adds
36
+ `--unshare-net --unshare-ipc` and a tmpfs over `/run`.
37
+ - Nothing widens the scope: the Full Permission bypass and `workspaceWriteScope: 'outside'` are never
38
+ consulted. The gate refuses an `outside` request before approvals. The hard-link scan is skipped,
39
+ because the scope permits no workspace writes.
40
+ - A missing sandbox fails closed, as for default Bash.
41
+ - As defence in depth, the gate also refuses a short list of system-acting commands
42
+ (`PLAN_BASH_DENIED`: `sudo`, `osascript`, `open`, `launchctl`, `defaults`, `kill`, `ssh`, …) in any
43
+ segment of the command. The OS sandbox is the boundary; the list is not.
44
+
45
+ The plan instruction says read-only shell inspection is allowed, writes and network fail, and the
46
+ model must not work around a failed write. Unchanged: plan research children still have no Bash, a
47
+ user `!` command still exits plan mode, and `!` is still refused while a plan request runs.
48
+
49
+ ## Alternatives
50
+
51
+ - **Do nothing.** Plans stay unable to verify claims with commands.
52
+ - **A command allowlist without the sandbox.** Easy to get around (`find -exec`, `rg --pre`,
53
+ `git -c core.pager=…`, `$(…)`, aliases), and every new tool needs a review. Rejected as the
54
+ boundary; kept only as a small denylist.
55
+ - **Default workspace-scoped Bash in plan mode.** Would let a plan change the workspace.
56
+
57
+ ## Consequences
58
+
59
+ Hosts need no change; plan mode reaches the runner through `Sessions`. The terminal keeps `bash` in
60
+ its plan active set. No protocol, stored data or prompt prefix changes: the SDK tool list is
61
+ unchanged and the instruction is the dynamic plan section. Builds and tests that write fail in plan
62
+ mode, which is intended.
63
+
64
+ ## Migration
65
+
66
+ None. It can be undone by removing `bash` from the plan gate.
67
+
68
+ ## Checks
69
+
70
+ `src/core/workspaceSandbox.test.ts` (real sandbox: reads succeed; workspace, home and link writes
71
+ fail; the temp folder works and is removed; a local TCP connection fails; widening callbacks are
72
+ never called; win32 fails closed), `src/sessions/planMode.test.ts`, `src/sessions/sessions.test.ts`,
73
+ `src/terminal/piExtension.lifecycle.test.ts`, `npm run prove`, and the App's checks. The Linux `bwrap`
74
+ path needs an Ubuntu CI run.
@@ -59,6 +59,20 @@ proposal is `Accepted`.
59
59
  | [PROP-0028](PROP-0028-normalize-question-wireframes.md) | Normalize high-confidence question wireframe borders in the terminal preview | Implemented; production PTY verification passed | INV-PAR-1/2, 11-terminal.md, 14-surfaces-and-parity.md, terminal-tui.md, parity, ADR-0029 | 2026-10-24 |
60
60
  | [PROP-0029](PROP-0029-agent-profiles.md) | Agent profiles that subagents start from | Accepted; implemented | INV-RUN-2, INV-VAR-4, INV-STO-3, INV-SRV-1, 02, 03, 04, 06, 10, 11, 12, 14, parity, ADR-0030 | 2026-10-24 |
61
61
  | [PROP-0030](PROP-0030-subagent-title.md) | A parent-set title for each subagent, shown in the roster | Accepted; implemented | 02, 07, 10, 11, terminal-tui.md, 14, parity | 2026-10-24 |
62
+ | [PROP-0031](PROP-0031-plugin-command-owner-namespaces.md) | Namespace plugin commands by owner | Implemented; Harness and App checks passed | INV-TUI-7, 06, 11, 14, terminal-tui.md, README.md, ADR-0021, ADR-0031 | 2026-10-25 |
63
+ | [PROP-0032](PROP-0032-planning-quality.md) | Evidence-grounded planning with read-only research delegation | Implemented; live model pilot pending authorization | INV-RUN-8, INV-GATE-3, INV-CAP-1, 02, 06, 07, 14, parity, ADR-0033 | 2026-10-25 |
64
+ | [PROP-0033](PROP-0033-compaction-plan-checkpoints.md) | Rehydrate bounded run checkpoints after compaction | Accepted; implemented | INV-PRM-1/4/10, INV-RUN-8/12, 02, 04, 11, 14, ADR-0032 | 2026-10-25 |
65
+ | [PROP-0034](PROP-0034-conversation-fast-mode.md) | Conversation-scoped provider fast mode | Proposed | INV-RUN-1/3, INV-PAR-1/2, 02, 09, 10, 11, 12, 14, parity, App thread runtime settings | 2026-10-25 |
66
+ | [PROP-0035](PROP-0035-retain-subagent-status.md) | Retain terminal subagent status until the next run | Implemented; initial PTY failure resolved; full verifier passed at three sizes (2026-11-19) | 11-terminal.md, terminal-tui.md | 2026-10-25 |
67
+ | [PROP-0036](PROP-0036-first-class-harness-commands.md) | First-class Harness terminal commands | Implemented | INV-PLG-10, INV-TUI-7, 06, 11, 14, terminal-tui.md, parity, ADR-0034 | 2026-10-25 |
68
+ | [PROP-0037](PROP-0037-workspace-write-boundary.md) | Default filesystem writes to the active project workspace | Implemented | INV-GATE-1/3/8, INV-RUN-1, INV-TUI-8, 02, 04, 07, 10, 11, 12, 14, parity | 2026-11-19 |
69
+ | [PROP-0038](PROP-0038-annotate-open-shadow-roots.md) | Annotate inside open shadow roots | Implemented | 13-world-tools.md, note target, PROP-0017, PROP-0018, parity | 2026-10-25 |
70
+ | [PROP-0039](PROP-0039-workspace-boundary-hardening.md) | Harden workspace writes against inode escapes and unverified retries | Accepted; implementation validated on macOS, Linux validation pending | INV-GATE-3/8, INV-TUI-8, 02, 07, 11, 14, parity | 2026-10-26 |
71
+
72
+ | [PROP-0040](PROP-0040-one-launch-path.md) | One launch path and command contract across front ends | Accepted; implementation in progress | 01–04, 06–07, 10–14, parity | 2026-11-19 |
73
+ | [PROP-0041](PROP-0041-bounded-work-slices.md) | Bounded work slices, a durable checkpoint, and a stall guard for long replies | Implemented; terminal card, App presentation and /slices open (PAR-004) | INV-RUN-2/3/4/12, INV-PRM-1/2/4/5/10, INV-GATE-2, INV-STO-3, INV-PAR-1/2, 02, 04, 07, 10–12, 14, parity | 2026-10-26 |
74
+ | [PROP-0042](PROP-0042-usage-and-provider-limits.md) | Usage analytics and provider limits as a built-in capability | Accepted; implemented | INV-MEM-5/6, INV-STO-2, INV-SRV-1, INV-PLG-10, INV-ACC-3, 02, 06, 08–12, 14, parity, ADR-0037 | 2026-10-26 |
75
+ | [PROP-0043](PROP-0043-read-only-plan-bash.md) | Read-only Bash in plan mode, enforced by the OS sandbox | Accepted; implemented | INV-RUN-8, INV-GATE-3/8, 02, 07, 11, terminal-tui, parity, ADR-0038 | 2026-10-26 |
62
76
 
63
77
  Numbers are four digits, start at `0001`, and are never reused.
64
78
 
@@ -34,7 +34,7 @@ Keep `TerminalAssets` and `Terminals.prepare/send/state/input/dispose` compatibl
34
34
  socket/controller bridge and its final gate. Keep the private control claim in process memory
35
35
  across native session replacement, revalidate prepared resource trust before every load, and
36
36
  scope native resume/import to the terminal's original working folder. Put unavoidable Pi
37
- implementation imports in one adapter checked against the pinned 1.2.0 version.
37
+ implementation imports in one adapter checked against the pinned Pi version.
38
38
 
39
39
  Use the shipped OMP 18.11.3 artifacts as the visual reference, with Harness identity and existing
40
40
  commands. [The living reference](../terminal-tui.md) records artifact digests, evidence and
@@ -1,6 +1,6 @@
1
1
  # ADR-0021 — Required built-in commands use the bare namespace
2
2
 
3
- Status: Accepted (owner requested implementation of PROP-0021, 2026-10-24).
3
+ Status: Superseded by [ADR-0031](ADR-0031-plugin-command-owner-namespaces.md).
4
4
  Supersedes: The command-namespace portion of [ADR-0018](ADR-0018-builtin-feature-plugins.md).
5
5
  Related: [PROP-0021](../architecture/proposals/PROP-0021-command-namespaces-and-duplicates.md),
6
6
  [11 Terminal](../architecture/11-terminal.md),
@@ -0,0 +1,37 @@
1
+ # ADR-0031 — Plugin commands use owner-qualified namespaces
2
+
3
+ Status: Accepted (PROP-0031, 2026-10-25).
4
+ Supersedes: The command-namespace decision in [ADR-0021](ADR-0021-required-command-namespaces.md).
5
+ Related: [PROP-0031](../architecture/proposals/PROP-0031-plugin-command-owner-namespaces.md),
6
+ [11 Terminal](../architecture/11-terminal.md),
7
+ [14 Surfaces and parity](../architecture/14-surfaces-and-parity.md),
8
+ [06 Plugins and capabilities](../architecture/06-plugins-and-capabilities.md).
9
+
10
+ ## Context
11
+
12
+ The generic `/plugin:<command>` form hid command ownership for optional built-ins and Pi extensions,
13
+ while required built-ins used a separate bare-command rule. Both forms left a global collision surface
14
+ and made help, completion and invocation inconsistent across owners. Annotation routes also used the
15
+ generic plugin prefix despite the plugin's annotation-focused purpose.
16
+
17
+ ## Decision
18
+
19
+ Every plugin-owned terminal command is invoked as `/<owner-prefix>:<command>`, regardless of whether
20
+ the owner is a required built-in, optional built-in or Pi extension. Core terminal commands remain
21
+ unprefixed, and `/skill:<name>` remains reserved for skill invocation. Built-in plugins use stable
22
+ prefixes; Pi packages may set `harness.commandPrefix` and otherwise derive the prefix from normalized
23
+ package identity, while loose extensions use normalized extension names. Prefixes identify owners and
24
+ must be unique; duplicate commands within an owner and owner-prefix collisions are rejected with
25
+ source diagnostics.
26
+
27
+ A built-in plugin module may choose a default command for its namespace root. Web annotations uses `/annotate:` for
28
+ capture, `/annotate:annotate` as an equivalent alias, and `/annotate:notes` for saved annotations.
29
+ For one release, the previous `/plugin:<command>` and bare required-plugin forms remain hidden
30
+ compatibility aliases when unambiguous. They are omitted from help and completion.
31
+
32
+ ## Consequences
33
+
34
+ The namespace rule applies uniformly to completion, help, highlighting and dispatch. Plugin packages
35
+ may opt into a stable custom prefix with `harness.commandPrefix`. The compatibility aliases expire
36
+ after one release and should not be used for new scripts or keybindings. This changes no server method,
37
+ capability identifier, account data or annotation storage format.
@@ -0,0 +1,33 @@
1
+ # ADR-0032 — Rehydrate bounded run checkpoints after compaction
2
+
3
+ Status: Accepted (PROP-0033, 2026-10-25).
4
+ Related: [PROP-0033](../architecture/proposals/PROP-0033-compaction-plan-checkpoints.md),
5
+ [02 Sessions and runs](../architecture/02-sessions-and-runs.md),
6
+ [04 Prompt](../architecture/04-prompt.md),
7
+ [11 Terminal](../architecture/11-terminal.md),
8
+ [14 Surfaces and parity](../architecture/14-surfaces-and-parity.md).
9
+
10
+ ## Context
11
+
12
+ Pi preserves the native conversation transcript when compacting, but subsequent model requests use a
13
+ summary and recent messages. The Harness already records the latest checklist as a run event and
14
+ restores it when a stopped run is continued; an active run lacked equivalent rehydration after
15
+ compaction. SDK plan instructions are dynamic context and may also be included in the summarized
16
+ span.
17
+
18
+ ## Decision
19
+
20
+ After compaction, each model request in the still-active run receives one bounded, ephemeral context
21
+ message when a checklist or applicable plan instructions exist to restore. It contains the current
22
+ request and available state. The prompt compiler assembles this content, and one Harness-owned
23
+ extension provides the same behavior to embedded sessions and the terminal. The extension state is run-scoped and clears on
24
+ settlement or branch navigation. The context message is not persisted to the native transcript or
25
+ run-event stream; existing session history, checklist events and continuation records remain the
26
+ sources of durable state. Hidden reasoning is never captured.
27
+
28
+ ## Consequences
29
+
30
+ Post-compaction requests carry a small additional context block until the active run settles. The
31
+ feature adds no storage, event, server method, tool, permission or stable-system-prompt change. Plan
32
+ mode's existing gate remains authoritative. Runs without an explicit checklist or active plan state
33
+ receive no checkpoint.
@@ -0,0 +1,47 @@
1
+ ---
2
+ id: ADR-0033
3
+ title: Keep delegation selective and research-only in plan mode
4
+ status: accepted
5
+ created: 2026-10-25
6
+ updated: 2026-10-25
7
+ related: [PROP-0032, INV-RUN-8, INV-GATE-3, INV-CAP-1]
8
+ ---
9
+
10
+ # ADR-0033 — Keep delegation selective and research-only in plan mode
11
+
12
+ ## Context
13
+
14
+ Subagents can reduce the cost of substantial independent work, but a child starts without the
15
+ parent's conversation and parallel edits create integration risk. Plan mode must remain read-only;
16
+ allowing every profile would let profile overrides expand its effective tools or plugins. Ordinary
17
+ subagent guidance also belongs in request context, not the stable system prompt.
18
+
19
+ ## Decision
20
+
21
+ - In normal mode, recommend delegation only for substantial, bounded, independent work. Prefer a
22
+ fitting specialist, give each child a self-contained brief, parallelize only independent tasks,
23
+ and keep integration and verification with the parent. Simple lookups, tiny edits and tightly
24
+ coupled work stay with the parent.
25
+ - In plan mode, allow only a named profile whose resolved tool and plugin allowlists are explicit
26
+ and confined to read-only research. The tool gate checks each spawn's arguments before approvals;
27
+ the subagent service checks again before launch.
28
+ - A research child inherits plan mode and can use only the intersection of its profile, the parent's
29
+ frozen capabilities and the plan research set. It has no connectors, user questions, writes,
30
+ commands or nested subagents. Its evidence returns to the parent, which verifies and synthesizes it.
31
+ - Put normal delegation guidance in the generated Subagents prompt section and the spawn tool's
32
+ description. Keep plan instructions request-scoped. Do not expand the stable system prompt or add
33
+ a second capability, approval, storage or plan-file mechanism.
34
+
35
+ ## Consequences
36
+
37
+ Normal runs receive concise advice at the point where profile choices are listed. Plan runs expose
38
+ only eligible profiles and enforce the same restriction at both the gate and launch service. The
39
+ terminal and hosts share the Harness behavior. Plan outcomes remain in the existing run history and
40
+ PlanCard handoff; no protocol or stored-data migration is introduced.
41
+
42
+ ## Verification
43
+
44
+ Harness tests cover normal prompt guidance, plan-mode profile eligibility and overrides, pre-approval
45
+ refusals, child tool/plugin narrowing, plan inheritance and non-nesting. The versioned Evaluation
46
+ suite adds one case where research delegation helps and one small case where it should be skipped.
47
+ Live model comparisons require explicit authorization and credentials.
@@ -0,0 +1,25 @@
1
+ # ADR-0034 — First-class Harness terminal commands
2
+
3
+ Status: Accepted (PROP-0036, 2026-10-25).
4
+ Supersedes: The plugin-command ownership rule of ADR-0031 only for the Harness terminal commands listed below.
5
+ Related: [PROP-0036](../architecture/proposals/PROP-0036-first-class-harness-commands.md),
6
+ [06 Plugins and capabilities](../architecture/06-plugins-and-capabilities.md),
7
+ [11 Terminal](../architecture/11-terminal.md),
8
+ [14 Surfaces and parity](../architecture/14-surfaces-and-parity.md),
9
+ [ADR-0031](ADR-0031-plugin-command-owner-namespaces.md).
10
+
11
+ ## Context
12
+
13
+ The required `subagents`, `history`, `doctor` and `worktrees` feature modules currently contribute terminal commands through `BuiltinPluginRegistry`. These features are not optional plugin toggles, and their terminal pages are first-class Harness surfaces. `/history` already means session-branch navigation, while `/mcp` already opens the Connections browser as a core command.
14
+
15
+ ## Decision
16
+
17
+ The terminal's core command registry owns these canonical routes: `/doctor`, `/worktree`, `/trajectory [run-id]`, `/subagents` and `/agents`. `/history` and `/tree` continue to navigate session branches. `/mcp` and `/connections` continue to open Connections.
18
+
19
+ The underlying required feature descriptors, tools, server methods and services remain registered by `BuiltinPluginRegistry`; they no longer own the terminal command metadata for these routes. Other plugin-owned built-in and Pi extension commands remain `/<owner-prefix>:<command>`, including the annotations namespace.
20
+
21
+ For one release, the prior owner-qualified and `/plugin:<command>` routes for the promoted commands remain accepted as hidden compatibility aliases. They are omitted from help and completion. Core command names remain reserved under the normal collision policy.
22
+
23
+ ## Consequences
24
+
25
+ The core terminal command table owns help, completion, highlighting and dispatch for the promoted routes. Plugin namespace collision checks and extension command ownership are unchanged. No server method, capability contract or stored data changes.
@@ -0,0 +1,40 @@
1
+ ---
2
+ id: ADR-0035
3
+ title: One launch and command contract for every front end
4
+ status: Accepted
5
+ created: 2026-11-19
6
+ proposal: PROP-0040
7
+ ---
8
+
9
+ # ADR-0035: One launch and command contract for every front end
10
+
11
+ ## Decision
12
+
13
+ The owner approved Complete App parity and Solo tabs; accepted
14
+ [PROP-0040](../architecture/proposals/PROP-0040-one-launch-path.md) records the Harness contract.
15
+ The foundation owns one interactive launch policy, not a policy per renderer:
16
+
17
+ - `Launch.standard` supplies shared role, working-folder and connection guidance. Hosts add only
18
+ attributed context, policy and declared add-ons through existing extension points.
19
+ - `commands/registry.ts` defines core commands; `commands.list` adds effective plugin commands,
20
+ prompts and skills. Session history controls use `sessions.*`; live-session operations use
21
+ `SessionRunInput.command` and the normal run lifecycle.
22
+ - Both surfaces load Harness-selected trusted resources and first-party hooks. Pi CLI built-ins
23
+ and the terminal-only question alias are excluded; attended planning uses `ask_user`.
24
+ - Interactive defaults (roots, neutral variant scope and credential backend), shared session
25
+ references, browser schemas and frozen capability discovery belong to Harness. Explicit choices
26
+ and historical pins win. Existing profiles are never silently merged or credentials copied.
27
+ - Optional `surfaces` metadata describes a host preference and a terminal fallback or unavailability;
28
+ it neither creates a plugin category nor grants permission.
29
+
30
+ ## Consequences and alternatives
31
+
32
+ Separate front-end defaults and command implementations make identical labels promise different
33
+ behavior. A shared contract permits richer presentation without a second loader, gate or state owner.
34
+ Host-backed tools must refuse unsupported semantics honestly. Matching roots alone does not share
35
+ credentials when the backends differ.
36
+
37
+ This is an accepted design, not release or acceptance evidence. Shared-default adoption, actual
38
+ runtime pins, native browser behavior, actionable command pages and cross-surface session discovery
39
+ remain subject to implementation and both-surface acceptance. The [parity register](../parity.md)
40
+ tracks source status and gaps; compiler-only equality tests do not establish runtime parity.
@@ -0,0 +1,54 @@
1
+ ---
2
+ id: ADR-0036
3
+ title: Enforce workspace writes at the filesystem operation
4
+ status: Accepted
5
+ created: 2026-10-26
6
+ proposal: PROP-0039
7
+ ---
8
+
9
+ # ADR-0036: Enforce workspace writes at the filesystem operation
10
+
11
+ ## Decision
12
+
13
+ The Harness owns one filesystem-effect boundary shared by SDK sessions and the terminal. Each run
14
+ pins a non-root `workspaceRoot`; model-controlled native writes and default-scope Bash are confined
15
+ to that root by operating-system enforcement, not by prompt guidance or pathname checks alone.
16
+
17
+ - Native `write` and `edit` calls revalidate the authorized canonical target at mutation and use
18
+ atomic replacement under an OS-enforced write scope. Outside file targets need a one-call
19
+ approval; replacing a directory entry rather than truncating an inode prevents an outside
20
+ hard-link alias from being changed.
21
+ - Before default-scope Bash, Harness scans the workspace for regular-file inodes with links outside
22
+ it and blocks the command if the scan is incomplete or an external alias may exist. The OS
23
+ sandbox prevents the command from creating a cross-boundary hard link. On macOS it denies
24
+ hard-link creation entirely, including within the workspace.
25
+ - Bash outside scope is an explicit `workspaceWriteScope: 'outside'` request, or terminal
26
+ `!outside <command>`. The complete quoted command is shown in approval details. Approval happens
27
+ before execution, is human-only and nonrememberable, and runs the exact command once. An
28
+ undersized terminal denies the request. stdout/stderr never proves a sandbox denial and never
29
+ triggers approval or replay.
30
+ - Full Permission remains the existing explicit boundary bypass for eligible interactive runs; it
31
+ remains subject to hard policy, authorization, read-only, plan, unattended and refinement rules.
32
+ - The filesystem root is not a valid workspace. A same-user peer process outside Harness remains
33
+ outside this boundary. Atomic replacement preserves ordinary file mode where possible but does
34
+ not guarantee ACL or extended-attribute preservation.
35
+
36
+ ## Rationale
37
+
38
+ Canonical path checks alone do not contain effects through hard links and are vulnerable to
39
+ pathname changes between authorization and mutation. Atomic replacement avoids writing through a
40
+ shared inode, while an OS scope constrains the helper if a path changes. Shell output is controlled
41
+ by the command and cannot authenticate a denial event; explicit pre-execution scope requests avoid
42
+ replaying partial side effects and make broad writes an intentional decision.
43
+
44
+ ## Consequences and alternatives
45
+
46
+ Workspaces with external hard-link aliases cannot use default Bash until repaired. Unsupported or
47
+ incomplete sandbox enforcement fails closed. Outside writes remain possible through the explicit
48
+ one-call approval path. A private copy-on-write workspace would also isolate against same-user peer
49
+ processes, but changes visibility, writeback, conflict handling, metadata and performance; it remains
50
+ out of scope unless that threat must be covered.
51
+
52
+ This decision is not release evidence. See [PROP-0039](../architecture/proposals/PROP-0039-workspace-boundary-hardening.md),
53
+ [the gate contract](../architecture/07-tools-and-gate.md), [terminal parity](../architecture/11-terminal.md)
54
+ and [the release note](../releases/1.2.0.md) for implementation and validation status.
@@ -0,0 +1,44 @@
1
+ # ADR-0037: Usage analytics read a derived ledger; provider limits are read, never guessed
2
+
3
+ **Status:** Accepted (2026-10-26)
4
+
5
+ ## Context
6
+
7
+ Token and cost figures already reach the harness per model request, but only inside run events,
8
+ and the index stores no usage. Provider limits have a schema and health rules but no producer.
9
+ A person needs both answers without reading run folders, and a subscription's plan window must
10
+ not be presented as a made-up percentage.
11
+
12
+ ## Decision
13
+
14
+ - **The usage ledger is derived.** `usage_requests` and `usage_indexed` live in `index.sqlite`,
15
+ are written from `usage.updated` events, and are rebuilt from `runs/` like recall. No run file,
16
+ folder or schema changes.
17
+ - **Analytics are bounded and scope-first.** One scope, one period, top-ten breakdowns, an
18
+ unpriced-request count, and cost estimates marked separately from provider figures.
19
+ - **Limits have a stated source.** Response headers first (free and fresh), then a small registry
20
+ of documented provider endpoints read with the credential `Models.getAuth` resolves, then an
21
+ explicit unavailable reason. Undocumented endpoints are off unless `experimentalProbes` is on.
22
+ - **Freshness is a person's choice.** Readings are cached for a configurable interval and
23
+ refreshed on page open or `r`, never by a background timer.
24
+ - **One built-in plugin owns the capability.** `usage` contributes the methods and the two
25
+ namespaced terminal commands; the public method table still comes from `server/methods.ts`.
26
+
27
+ ## Consequences
28
+
29
+ The index carries one more derived table set, with an append-only migration. `AccountState`
30
+ gains an optional readout list and `Limit` optional absolute fields, both additive. A page open
31
+ may make a read-only provider request with the account's existing credential; nothing is written
32
+ to a credential file and no model request is made. Hosts can present the same methods later; the
33
+ terminal is the reference surface.
34
+
35
+ ## Alternatives rejected
36
+
37
+ Reading raw run logs per page (unbounded), a new data folder (duplicates derived data), scraping
38
+ dashboards (no stable API), inventing percentages (misleading), background polling (surprise
39
+ egress).
40
+
41
+ ## References
42
+
43
+ [PROP-0042](../architecture/proposals/PROP-0042-usage-and-provider-limits.md), architecture pages
44
+ 02, 06, 08, 09, 10, 11, 12 and 14.
@@ -0,0 +1,29 @@
1
+ ---
2
+ id: ADR-0038
3
+ title: Plan-mode Bash is read-only by OS sandbox, not by command parsing
4
+ status: Accepted
5
+ created: 2026-10-26
6
+ proposal: PROP-0043
7
+ ---
8
+
9
+ # ADR-0038: Plan-mode Bash is read-only by OS sandbox, not by command parsing
10
+
11
+ ## Decision
12
+
13
+ Plan runs may use Bash, and each call runs in the workspace sandbox's `read-only` scope: no writes
14
+ except a discarded per-call temp folder, no network, no signals outside the sandbox, and (macOS) no
15
+ mach services. Nothing widens that scope in plan mode. A small denylist of system-acting commands is
16
+ checked by the gate as defence in depth only.
17
+
18
+ ## Rationale
19
+
20
+ Whether a shell command writes cannot be decided reliably from its text; many read-looking commands
21
+ can run others or write through flags. The Harness already depends on `sandbox-exec` and `bwrap` for
22
+ the workspace boundary (ADR-0036), so the same mechanism with no write roots gives a kernel-enforced
23
+ read-only guarantee at little cost.
24
+
25
+ ## Consequences
26
+
27
+ Plans can inspect with real commands. Commands that need to write, such as most builds and tests,
28
+ fail in plan mode. Platforms without a sandbox refuse plan Bash. See
29
+ [PROP-0043](../architecture/proposals/PROP-0043-read-only-plan-bash.md).