@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
@@ -13,6 +13,7 @@ records everything the run does as validated events.
13
13
  |---|---|---|
14
14
  | `Sessions` | `sessions/sessions.ts` | Starts a turn: `run(input: SessionRunInput)`, `provide(provider)`. Builds the gate. |
15
15
  | `runTurn`, `piSessionFactory`, `loadResources` | `sessions/runner.ts` | Opens the Pi session, loads resources in a fixed order, streams events, settles the run. |
16
+ | compaction checkpoint | `sessions/compactionCheckpoint.ts` | Restores the active request's bounded goal, checklist and plan instructions to model context after compaction. |
16
17
  | `RunRegistry`, `ActiveRun` | `sessions/registry.ts` | Live runs: `emit`, `requestApproval`, `requestAnswer(s)`, `onInterrupt`, `onSteer`, `finish`; `listen` for observers. |
17
18
  | run store functions | `sessions/runStore.ts` | `saveRun`, `updateRun`, `persistEvent`, `appendEvent`, `readEvents`, `markInterruptedRuns`, `runFamily`, `listRuns`. |
18
19
  | `RunRecord`, `RunEvent`, schemas | `sessions/contracts.ts` | The one contract every session shares. |
@@ -27,15 +28,18 @@ records everything the run does as validated events.
27
28
 
28
29
  1. Validate the input (`run.id`, `sessionId`, `text`; an `initialBashCommand` only on a new
29
30
  interactive user turn).
30
- 2. Select the native history (`selectHistory`) and resolve the account (`accounts.resolve`).
31
+ 2. Resolve a continuation when requested; reuse its pinned root or derive one from its recorded
32
+ working directory. For a new run, resolve and pin the root from the effective `cwd` (after
33
+ worktree selection). Select the native history (`selectHistory`) and resolve the account
34
+ (`accounts.resolve`).
31
35
  3. Resolve the caller's capabilities, intersect with any narrower requested set, and **freeze**
32
36
  them for the run (`capabilities/runSnapshot.ts::freezeRun`).
33
37
  4. **Pin** the variant once (`variant/materialise.ts::materialise`).
34
38
  5. `saveRun` with status `queued`, the account, `harnessScope` and `harnessVersion`.
35
39
  6. Compile the prompt (`sessionLaunch`, page 04): a stable system prompt and an optional context
36
40
  update.
37
- 7. `runs.start` the live run; build the tool list (broker, harness tools, the plan-eligible
38
- `ask_user`, subagent tools, provider tools) and the gate.
41
+ 7. `runs.start` the live run; build the tool list (broker, Harness tools including read-only
42
+ `get_capabilities`, the plan-eligible `ask_user`, subagent tools, provider tools) and the gate.
39
43
  8. Open Pi through the `SessionFactory`, with resources loaded in order: disk extensions chosen by
40
44
  the harness, variant extensions, inline extensions, and the gate last.
41
45
  9. Record `nativeSessionId`, register the run with `Subagents`, start `runTurn`, and return
@@ -54,6 +58,14 @@ A session that cannot open ends its run as `failed` and returns `nativeSessionId
54
58
  `reaction.added`, `agent.message`, `checklist.updated`, `artifact.added`, `file.changed`, `usage.updated`,
55
59
  `account.status`, `status.changed`, `failed`, `completed`. Each has a payload schema in
56
60
  `runEventPayloadSchemas`.
61
+ - **Workspace root** (`RunRecord.workspaceRoot`) is the canonical root whose filesystem effects
62
+ native writes and default-scope Bash may modify: the nearest Git repository or worktree above its
63
+ effective working directory, otherwise that canonical directory. New runs pin it; continuations
64
+ reuse it. Older records may omit it, in which case a continuation derives it from the recorded
65
+ `workingDirectory`. The filesystem root itself cannot be selected as a workspace, because it
66
+ would collapse the write boundary. Native writes revalidate the target and atomically replace it
67
+ inside an OS write scope; Bash scans for external hard-link aliases and runs in the same pinned
68
+ boundary (page 07).
57
69
  - **Question sketches** (PROP-0025, ADR-0026; originally PROP-0024, ADR-0025). `question.requested` may include `uiSketch: true`
58
70
  and `sketch` text on each choice. If enabled, every choice must have a sketch; otherwise no
59
71
  choice may carry one. A sketch is 1–24 LF-separated lines, at most 80 printable ASCII characters
@@ -93,11 +105,28 @@ A session that cannot open ends its run as `failed` and returns `nativeSessionId
93
105
  carries `agent` and any `unavailableTools`. A run that may spawn lists every profile in its
94
106
  `subagents` section.
95
107
  - **Plan mode** (`plan: true`). The stable system prompt is unchanged. An attended plan run may
96
- include `ask_user`, subject to the caller's frozen capabilities; the gate refuses everything
97
- outside `PLAN_READ_ONLY_TOOLS`, provider tools marked `readOnly`, and calls a provider marks
98
- `planModeSafe`. The plan instruction arrives as a dynamic section; for UI-related plans it asks
99
- the model to explore distinct design directions, compare trade-offs and recommend one, using
100
- contextual `uiSketch` questions when helpful. The reply ends with one `<proposed_plan>` block.
108
+ include `ask_user` and `spawn_subagent` / `wait_subagents`, subject to the caller's frozen
109
+ capabilities. Delegation is limited to named profiles whose effective tool and plugin allowlists
110
+ qualify for read-only research; the child inherits plan mode and cannot write, run commands, ask
111
+ the user, use connectors or spawn children. The gate refuses unsafe calls before approvals and the
112
+ subagent service rechecks profile eligibility before launch. Other calls must be in
113
+ `PLAN_READ_ONLY_TOOLS` (including `get_capabilities` when frozen for the run), be provider tools
114
+ marked `readOnly`, or be explicitly classified by `planModeSafe`. Model-issued `bash` is allowed
115
+ and runs in the sandbox's read-only scope: no writes except a discarded temp folder, no network,
116
+ nothing that widens the scope; the gate refuses `outside` requests and `PLAN_BASH_DENIED` commands
117
+ (PROP-0043). The dynamic plan instruction
118
+ requires an opening `Goal` or `Summary`, evidence-grounded findings when useful, and task-scaled
119
+ steps and verification. A small fix stays concise without omitting its
120
+ outcome or check; cross-cutting work uses ordered steps. It uses alternatives only when requested
121
+ or materially useful; contextual `uiSketch` questions clarify real choices.
122
+ The reply ends with one `<proposed_plan>` block.
123
+ - **Compaction checkpoint** (PROP-0033, ADR-0032). After compaction, an active run's model context
124
+ receives a bounded, ephemeral checkpoint with the current user request and the latest validated
125
+ checklist or applicable plan instructions, when either is available to restore. It is re-added to
126
+ each request while that run continues;
127
+ its state is cleared when the run settles or the session branch changes. The message changes no
128
+ tools, permissions, run events or native history. An interrupted run still uses the durable
129
+ checklist and normal continuation path.
101
130
  - **Continuation** (PROP-0007). `continuationOf(root, runId, sessionDir?)` (`runs.continuation`)
102
131
  says whether a stopped run can be continued: `resumable`, a `reason` when not (`not_found`,
103
132
  `active`, `completed`, `no_history`, `history_missing`), the exact native history to reopen,
@@ -120,18 +149,52 @@ A session that cannot open ends its run as `failed` and returns `nativeSessionId
120
149
  - **Usage** (`usage.updated`). `inputTokens` is uncached input; `cacheReadTokens` and
121
150
  `cacheWriteTokens` are separate; `outputTokens` includes `reasoningTokens`. Totals are
122
151
  cumulative for the run, not the native session. `contextTokens` is the latest request's size.
152
+ The derived usage ledger (`usage_requests`, page 08) copies these requests for
153
+ `/usage:analysis`; run events remain the only source of truth.
154
+ - **Limit snapshots** (`account.status.limits`). A provider reading carries a window or balance,
155
+ its reset, and its source. It is produced by the usage limits service from provider response
156
+ headers or a documented provider endpoint (page 09), stored in the account's state, and never
157
+ inferred from a made-up cap.
123
158
  - **Provider reasoning summaries.** For supported Responses API families, when reasoning is
124
159
  enabled the runner requests a `detailed` summary and records only the provider's explicit summary
125
160
  (bounded to 4,000 characters) in request-level usage. This does not change reasoning effort or
126
161
  guarantee a provider will return a summary; raw reasoning is not used as a substitute.
127
162
 
163
+ ## Session controls and command runs (PROP-0040)
164
+
165
+ `commands/registry.ts` is the browser-safe core command catalogue, shared by front ends.
166
+ `commands.list` combines it with effective plugin, extension, prompt and skill routes (page 10).
167
+ A listed route is not proof that its surface passes acceptance.
168
+
169
+ `sessions/control.ts` owns saved-history operations: tree, navigation without a summary, fork,
170
+ clone, rename, last reply and JSONL export. A `SessionRef` names `sessionDir`, optional
171
+ `sessionFile` and optional `cwd`; the resolved JSONL must be inside that directory. Navigation
172
+ records a `xingular-navigation` entry; choosing a user message returns its text for editing and
173
+ moves before it. Fork and clone create history in an absolute `targetDir`.
174
+
175
+ `SessionRunInput.command` (also `TurnParams.command`) is one of:
176
+
177
+ - `{ name: 'compact', instructions? }`
178
+ - `{ name: 'navigate', entryId, summarize: boolean, instructions? }`
179
+ - `{ name: 'export', path }` (absolute `.html` path)
180
+
181
+ It replaces prompt submission and skips the normal context update. It requires saved history and
182
+ cannot combine with plan mode, continuation, automatic execution, initial Bash or images. It uses
183
+ normal account/variant/capability pins, events and cancellation. Front ends must fence history
184
+ mutations against active work; the current raw saved-history helpers do not themselves establish
185
+ that fence. Stable native entry IDs must connect host transcript projection to navigation/fork,
186
+ not positional message counts. A host can route `/history` to Harness `sessions.navigate` and
187
+ navigate the same saved session; it must not substitute a fork. Shared session catalog registration
188
+ is approved but remains in progress; no completed cross-surface catalog is implied (PAR-008).
189
+
128
190
  ## Invariants
129
191
 
130
- - **INV-RUN-1** A run pins one account, one variant version and one capability set when it
131
- starts. None of them changes during the run. A pinned account that cannot be used fails the
132
- run; it is never swapped (`INV-ACC-2`). A side-question run is the explicit exception for the
133
- variant: it pins no variant and uses the fixed Harness instruction plus a redacted main-run
134
- context projection (PROP-0019).
192
+ - **INV-RUN-1** A run pins one account, one variant version, one capability set and one canonical
193
+ `workspaceRoot` when it starts. None changes during the run. A continuation reuses the prior
194
+ root, deriving one from `workingDirectory` only for a legacy record without the field. A pinned
195
+ account that cannot be used fails the run; it is never swapped (`INV-ACC-2`). A side-question
196
+ run is the explicit exception for the variant: it pins no variant and uses the fixed Harness
197
+ instruction plus a redacted main-run context projection (PROP-0019).
135
198
  - **INV-RUN-2** A run's capability set is written once (`freezeRun`). A resumed or duplicate launch
136
199
  with a different set fails with `capabilities_frozen`. A child run's set is the intersection of
137
200
  its parent's, its profile's tools and plugins, and what it asked for; it never widens.
@@ -141,8 +204,11 @@ A session that cannot open ends its run as `failed` and returns `nativeSessionId
141
204
  - **INV-RUN-4** A restarted harness never restarts model work. On `open`, runs left active are
142
205
  marked `interrupted` unless another live process owns them (`owner.json`). Recovery of a
143
206
  subagent needs the same account, caller, working folder and history branch. Continuing is always
144
- a new run the application asks for with `continueFrom`; it pins the earlier run's account, variant
145
- version and capability set, and the earlier run's record is never reopened.
207
+ a new run with `continueFrom`; it pins the earlier run's account, variant version and capability
208
+ set, and the earlier run's record is never reopened. The application asks for it, or the earlier
209
+ run's work-slice policy does when the reply stopped at its budget (PROP-0041); a run started
210
+ without a person (`automatic`), a plan, a subagent, a session command or a worktree never
211
+ continues itself.
146
212
  - **INV-RUN-5** Unattended runs (`automatic: true`) load no extension found on disk, read no
147
213
  context files, and never ask a person: anything that would need approval is refused. A variant's
148
214
  own trusted plugins still load, except for side-question runs, which have no variant or plugin
@@ -156,39 +222,75 @@ A session that cannot open ends its run as `failed` and returns `nativeSessionId
156
222
  - **INV-RUN-7** Token accounting follows the usage contract above. Changing what a field means is
157
223
  an architecture change.
158
224
  - **INV-RUN-8** Plan mode keeps the stable system prompt but has mode-specific tools: an attended
159
- plan may include `ask_user`, subject to the frozen caller capabilities; newly resolved normal,
160
- automatic, probe, improver and reflection runs omit it. The terminal activates `ask_user` and
161
- `ask_user_question` only for a plan request, then restores its normal set. Plan refusals happen
162
- before approvals and no approval can lift them. Provider-cache effects of the tool-list difference
163
- are not assumed (page 04, `INV-PRM-8`).
225
+ plan may include `ask_user`, `spawn_subagent` and `wait_subagents`, subject to the frozen caller
226
+ capabilities; newly resolved normal, automatic, probe, improver and reflection runs omit
227
+ `ask_user`. Plan-mode spawning requires a named profile whose effective explicit tool and plugin
228
+ allowlists qualify for read-only research. The child inherits plan mode and is narrowed to the
229
+ intersection of the parent's frozen capabilities, the profile and the plan research set; it has no
230
+ connectors, user questions, writes, commands or nested subagents. The plan run itself may run
231
+ Bash only in the OS-enforced read-only scope (PROP-0043, ADR-0038). The gate refuses unsafe calls
232
+ before approvals, and the service rechecks before launch. The terminal activates plan-eligible
233
+ tools only for a plan request, then restores its normal set. Provider-cache effects of the tool-list
234
+ difference are not assumed (page 04, `INV-PRM-8`).
164
235
  - **INV-RUN-9** Subagent results reach the parent at native turn boundaries, at most once
165
236
  automatically (typed receipts in native history). A parent joins outstanding children before it
166
237
  completes.
167
238
  - **INV-RUN-10** An initial Bash command is allowed only on a new interactive user turn (not
168
- automatic, plan, resumed, continuing, relation, probe or improver runs), must match the leading `!` command
169
- in the text, and passes the same gate as model-issued Bash.
239
+ automatic, plan, resumed, continuing, relation, probe or improver runs), must match the leading `!`
240
+ command in the text, and passes the same gate as model-issued Bash. It is workspace-scoped by
241
+ default; the explicit terminal syntax `!outside <command>` requests the same human-only,
242
+ pre-execution outside-scope approval as the Bash tool and runs the command at most once.
170
243
  - **INV-RUN-11** A side-question run is independent, tool-free and temporary. It receives only a
171
244
  bounded, redacted projection of the main run's effective prompt and public events plus the
172
245
  surface's visible transcript and selected references. It excludes hidden reasoning and attachment
173
246
  bytes, never reads the main native history, and never changes or steers the parent. Its own run
174
247
  record and events use the normal store contract (PROP-0019).
248
+ - **INV-RUN-13** A reply is a bounded work slice (PROP-0041). The foundation defaults are 160 tool
249
+ calls, 40 minutes and 320 000 new tokens, whichever comes first. The token count covers new input,
250
+ cache writes and output, never the cached prefix: a few large reads pass any small cap before
251
+ useful work, so the cap is a safety net, not the bound. At the limit the guard ends the slice and
252
+ refuses the next tool call with a request for `checkpoint_write`; a handoff call is never refused,
253
+ including when it is the first call past the limit, and another tool ends the reply. A reply that ends this way is `interrupted` with terminal reason `slice_complete`, and one
254
+ the stall guard ends is `stalled`. The stall guard identifies a call by its name and every
255
+ argument, names a repeated call once and ends the reply on the next repeat, and counts afresh in
256
+ each reply, so a checklist a reply rewrites as its steps complete is not a repeat. A new run
257
+ continues it only up to the policy's slice cap, only
258
+ after a slice that changed a file, passed a check or recorded a finished item, and never after a
259
+ checkpoint that is `blocked`. A run may turn the guard off with `workSlice.enabled: false`, and a
260
+ launch may set `XINGULAR_WORK_SLICE=off` for every run in that process; with it off a reply has no
261
+ boundary — no checkpoint, no continuation, no stall notes — and ends only when the model stops or a
262
+ person interrupts.
263
+ - **INV-RUN-14** A run event stream carries `checkpoint` (the handoff `checkpoint_write` records or
264
+ the harness derives from the run's own evidence when the model wrote none) and `heartbeat`
265
+ (steps, elapsed time, last tool, compactions, slice, and the call budget). A heartbeat older than
266
+ five minutes while the owner process lives marks the run `interrupted` with reason `stalled`; a
267
+ run that never emitted one keeps the 30-minute silence rule. The watchdog never restarts work.
268
+ - **INV-RUN-12** A post-compaction checkpoint is a bounded context projection of the active request,
269
+ the latest validated checklist and applicable plan instructions. It is compiled by
270
+ `prompt/compile.ts`, supplied only to model requests in the active run, and never persists hidden
271
+ reasoning or changes permissions, stable prompt text, native history or run-event schemas. Its
272
+ in-memory state is cleared when the run settles or the active session branch changes
273
+ (PROP-0033, ADR-0032).
175
274
 
176
275
  ## Data
177
276
 
178
- `<data root>/runs/<id>/`: `run.json`, `events.jsonl`, `owner.json`, `subagents.json`,
179
- `context.json`, `caller.json`, `capabilities.json`, `mcp-context.json`. Native history lives in
277
+ `<data root>/runs/<id>/`: `run.json` (including the optional, legacy-compatible `workspaceRoot`),
278
+ `events.jsonl`, `owner.json`, `subagents.json`, `context.json`, `caller.json`, `capabilities.json`,
279
+ `mcp-context.json`. Native history lives in
180
280
  the session directory the caller passes (`sessionDir`), owned by Pi. Side-question native history
181
281
  uses a one-use OS temporary directory, removed when the run settles; its tracked run and events
182
282
  remain in the ordinary run store.
183
283
 
184
284
  ## Source map
185
285
 
186
- `src/sessions/*.ts`, `src/capabilities/runSnapshot.ts`, `src/tools/spawnSubagent.ts`.
286
+ `src/sessions/*.ts`, `src/capabilities/runSnapshot.ts`, `src/core/workspaceBashTool.ts`,
287
+ `src/core/workspaceSandbox.ts`, `src/core/workspaceWriteOperations.ts`, `src/tools/spawnSubagent.ts`.
187
288
  Decisions: [ADR-0002](../decisions/ADR-0002-native-subagent-lifecycle.md) (superseded),
188
289
  [ADR-0003](../decisions/ADR-0003-shared-asynchronous-subagents.md),
189
290
  [ADR-0022](../decisions/ADR-0022-plan-only-question-tools.md),
190
291
  [ADR-0023](../decisions/ADR-0023-contextual-side-question-runs.md),
191
- [ADR-0026](../decisions/ADR-0026-contextual-question-wireframes.md).
292
+ [ADR-0026](../decisions/ADR-0026-contextual-question-wireframes.md),
293
+ [ADR-0033](../decisions/ADR-0033-plan-mode-research-delegation.md).
192
294
 
193
295
  ## Native session names (PROP-0010)
194
296
 
@@ -16,11 +16,13 @@ keeps provider caching effective across turns.
16
16
  | `render(env, pack)` → `string` | One string for a session that takes a single prompt. |
17
17
  | `sessionLaunch(env, caller, launch, historyFile, variant)` → `{ system, update, pack }` | SDK sessions: stable prefix plus an update with only the dynamic sections that changed since the last delivered snapshot. |
18
18
  | `deliveredSnapshot(sessionFile)` | Reads the last delivered snapshot on the active history branch; a compaction forces a fresh one. |
19
+ | `renderCompactionCheckpoint` | Compiles a bounded current-request, checklist and plan-state message for post-compaction model requests. |
19
20
  | `saveForSession`, `copyToRun` | Terminal sessions keep the pack they started with and copy it to each run. |
20
21
  | `record`, `load`, `packPath` | The pack each run was given, at `runs/<id>/context.json`. |
21
22
  | `toolGroups`, `fit`, `clip` | The tool manifest by group; cutting text to a budget at a paragraph boundary. |
22
23
 
23
- All in `prompt/compile.ts`. A `ContextPack` holds `sections: Section[]` (each with `id`, `title`,
24
+ Assembly is in `prompt/compile.ts`; shared standard-section data is in `prompt/standard.ts`.
25
+ A `ContextPack` holds `sections: Section[]` (each with `id`, `title`,
24
26
  `body`, `sources`, `budget`, `size`, `cut`), `toolGroups`, `harness` (scope and fingerprint),
25
27
  `profile`, `compiledAt` and `delivery` (`systemSectionIds`, `updatedSectionIds`).
26
28
 
@@ -29,14 +31,22 @@ All in `prompt/compile.ts`. A `ContextPack` holds `sections: Section[]` (each wi
29
31
  `SECTION_ORDER`: `harnessIdentity` 5, `system` 25, `guardrails` 40, `memory` 45, `references`
30
32
  50, `subagents` 64, `scripts` 66, `wiki` 80, `tools` 90, `reporting` 95. Application sections
31
33
  sit among them by their `order`; plan mode uses `plan` at 25. Ties keep insertion order.
32
- When a run may spawn, `subagents` lists every agent profile it can start (built-in, global,
33
- project and the variant's c5 names) and replaces the variant's own list (PROP-0029). The `tools`
34
+ When a run may spawn, the dynamic `subagents` section lists every profile it can start (built-in,
35
+ global, project and variant c5 names) and replaces the variant's own list (PROP-0029). Normal-mode
36
+ guidance in that section explains when delegation is worth its overhead, profile selection,
37
+ self-contained briefs, independent parallel work, and parent ownership of integration. Plan mode
38
+ lists only effective profiles eligible for read-only research and requires a named profile; keeping
39
+ this section dynamic leaves the stable system prompt unchanged across modes. The `tools`
34
40
  section has a Progress paragraph whenever `checklist_write` is available: it asks for a checklist
35
- early and kept current, because the person follows work from it (PROP-0006).
41
+ early and kept current, then requests a final update before the reply. Only fully completed work is
42
+ marked completed; unfinished or blocked steps remain accurate (PROP-0006).
36
43
 
37
44
  The fixed `harness-identity` section says `You are an AI agent running in <productName>.` and uses
38
- `env.productName` (default `Xingular Agent`). Host-provided identity and role sections remain
39
- separate, so an embedding application can still describe its agent and task.
45
+ `env.productName` (default `Xingular Agent`). `Launch.standard: { cwd, project?: { name } | null }` requests shared role and working-folder
46
+ sections from `prompt/standard.ts`; the compiler supplies common connections, file and annotation
47
+ guidance for the effective tools. Terminal preparation defaults to standard sections (explicit
48
+ `standard: null` opts out); the host interactive path requests the same sections. Hosts may add
49
+ attributed add-on and task context, not a duplicate standard role or connection policy (PROP-0040).
40
50
 
41
51
  Budgets (characters): variant sections 30,000 each (already capped at save, so never cut here);
42
52
  references 12,000; Wiki pointer 2,500; tools 8,000; reporting 2,000; application sections their
@@ -69,17 +79,34 @@ compile ──► pack ──► stable sections ──► system prompt (+ "Con
69
79
  `dynamic: true`.
70
80
  - An update lists only the dynamic sections that changed, and announces a withdrawn one.
71
81
  `references` is always resent when present, because it belongs to the current request.
82
+ - After successful compaction, the foundation checkpoint extension detects the compacted context and
83
+ uses `renderCompactionCheckpoint` to append one hidden, ephemeral context message to each model
84
+ request while the active run continues, when a checklist or applicable plan instructions exist.
85
+ It contains only the current request and available state. It is not written to native history, and its state is
86
+ discarded when the run settles or the branch changes.
72
87
  - The session appends the update as a hidden custom message and saves its snapshot in the
73
88
  message details (`xingularContext`); recording a pack does not mark it delivered.
74
89
  - Terminal sessions use `compile` + `render` once, pass it as `--append-system-prompt`, and keep
75
90
  that pack for the life of the session.
76
- - Read-only native file guidance states that absolute and `~` paths work when the operating system
77
- permits access, names the configured data root, and describes the working folder as a default
78
- location. Edits and commands remain subject to the gate.
91
+ - Native file guidance states that absolute and `~` paths work for reads when the operating
92
+ system permits access, names the configured data root, and describes the working folder as a
93
+ default location. Native writes and Bash filesystem effects default to the run's pinned project
94
+ or worktree root; outside file targets require one-call approval, and Bash is sandboxed unless
95
+ the eligible user explicitly enables Full Permission. Edits and commands remain subject to the
96
+ gate.
79
97
  - A side-question launch attributes its fixed no-tool instruction to the `side-questions` plugin.
80
98
  Its launch suppresses tool guidance entirely. The main run's effective prompt and current context
81
99
  update are included in the separately bounded, redacted context message (PROP-0019).
82
100
 
101
+ ## Launch parity status (2026-11-19)
102
+
103
+ Shared section generation is present in source; `surfaceParity.test.ts` checks compiler equality.
104
+ That does not verify actual prepared launches, registered schemas, credentials, variant pins or
105
+ provider-cache reuse. PROP-0040 also requires one Harness-owned interactive default for roots,
106
+ variant scope and credential backend. Adoption and runtime acceptance remain in progress; explicit
107
+ roots/scopes and continuation pins take precedence. Matching `--data` alone cannot reconcile a
108
+ file SecretStore with a host Keychain. See [parity](../parity.md).
109
+
83
110
  ## Invariants
84
111
 
85
112
  - **INV-PRM-1** The prompt is assembled only in `src/prompt/compile.ts`. Nothing else builds model
@@ -99,9 +126,20 @@ compile ──► pack ──► stable sections ──► system prompt (+ "Con
99
126
  - **INV-PRM-8** Whether a prompt or callable tool-list change keeps the provider's cache is measured
100
127
  with real requests from the application, only with the account owner's permission. Tests do not
101
128
  stand in for it.
102
- - **INV-PRM-9** Native file guidance describes the existing OS-permission boundary accurately:
103
- absolute and `~` paths are valid for read-only file tools; the working folder is not a sandbox.
104
- Side-question runs with no callable tools omit tool guidance.
129
+ - **INV-PRM-9** Native file guidance describes the boundary accurately: absolute and `~` paths
130
+ are valid for read-only file tools, which remain bounded by OS permissions rather than the
131
+ workspace root. Native file writes and Bash filesystem effects are workspace-scoped by default;
132
+ the working folder itself is not the boundary. Side-question runs with no callable tools omit
133
+ tool guidance.
134
+ - **INV-PRM-10** Compaction checkpoints are assembled by this compiler, bounded, and delivered only
135
+ as ephemeral context while their active run continues. They do not change the stable system prompt
136
+ or native history, and do not carry hidden reasoning (PROP-0033, ADR-0032). A work-slice handoff is
137
+ the same kind of bounded projection: the `checkpoint` event is rendered into the continuation
138
+ section by `continuationBody`, never into the system prompt (PROP-0041).
139
+ - **INV-PRM-11** The Progress group names `checkpoint_write` when it is in the frozen capability
140
+ set, with the same budget as the other tool guidance, and says only that a bounded reply records a
141
+ handoff when its budget is reached. It does not describe the limits themselves, which the guard
142
+ states at the boundary.
105
143
 
106
144
  ## Relation to DESIGN-0001
107
145
 
@@ -17,7 +17,7 @@ callable result for one run.
17
17
  | `Plugins` (`loading`, `loaded`) | `plugins/plugins.ts` | Chooses the paths a session or terminal loads; records what each extension registered. |
18
18
  | discovery | `plugins/discover.ts` | Finds items with Pi's own resolver, which runs no extension code. |
19
19
  | attachments (`load`, `save`, `attached`, `decided`, `set`) | `plugins/attachments.ts` | The one store of what is switched on, per scope. |
20
- | `BuiltinPluginRegistry`, `BuiltinPluginModule` | `plugins/builtinRegistry.ts`, `plugins/builtin.ts` | Registers built-in modules and their descriptors, world parts, server handlers and terminal command metadata. |
20
+ | `BuiltinPluginRegistry`, `BuiltinPluginModule` | `plugins/builtinRegistry.ts`, `plugins/builtin.ts` | Registers built-in modules and their descriptors, world parts, server handlers and plugin-owned terminal commands. Harness core commands live in `commands/registry.ts`, shared by both surfaces. |
21
21
  | `HARNESS_PLUGINS` | `plugins/builtin.ts` | Descriptor view of the registered Harness parts and feature plugins. |
22
22
  | `FIRST_PARTY_PLUGINS` | `plugins/firstParty.ts` | `output-trim`, `budget-guard`: inline extensions shipped switched off. `lessons`: switched off; its contribution turns on reflection after a run (page 08, PROP-0008) and loads no Pi code. |
23
23
  | `Hooks` | `plugins/hooks.ts` | Runs a package's hooks file after a write. |
@@ -29,14 +29,26 @@ callable result for one run.
29
29
  ## Contracts
30
30
 
31
31
  - **Loading.** `Plugins.loading({ cwd, agentDir, caller, contributions, install? })` returns
32
- `extensionPaths`, `skillPaths`, `promptPaths`, `themePaths`, `builtins` (terminal only), `hooks`,
32
+ `extensionPaths`, `skillPaths`, `promptPaths`, `themePaths`, `builtins` (legacy shape; no Pi CLI built-ins selected), `hooks`,
33
33
  `found`, `detached`, `projectCode`, `packageCode`. A session opens Pi with its own discovery off
34
34
  and only these paths; a terminal receives them as `--extension`, `--skill`,
35
35
  `--prompt-template` and `--theme` after `--no-extensions --no-skills --no-prompt-templates
36
36
  --no-themes`. Pi still reads `AGENTS.md` and `SYSTEM.md` itself.
37
+ - **Surface metadata (PROP-0040).** `PluginDescriptor.surfaces` and `Plugin.surfaces` optionally
38
+ carry `{ preferred: 'host', terminal: 'fallback' | 'unavailable', note: string }`.
39
+ `plugins/surfaces.ts` formats the note and badge. Inspection explains what the host adds and
40
+ what the terminal does instead; this is not a category, attachment switch or permission.
41
+ - **Shared resources (PROP-0040).** Both surfaces load Harness-selected resources and enabled
42
+ first-party hooks, including output trim and budget guard. Terminal preparation exposes these
43
+ through its protected bridge. Pi CLI-only llama.cpp, codemode, tool-search and MCP built-ins are
44
+ not selected or advertised as extra terminal capabilities. This does not remove Harness MCP.
37
45
  - **Attachments.** `config/plugin-attachments.json` holds `global`, `agents`, `projects` (each
38
46
  `{ on, off }`) and `settings` per plugin. The narrowest scope that says something wins: agent,
39
47
  then project, then everywhere. With no entry an item keeps its default.
48
+ - **Package metadata.** A Pi package may declare `"harness": { "extends": [...], "hooks": "./hooks.mjs", "commandPrefix": "my-tools" }`.
49
+ `commandPrefix` is optional; in the terminal, commands otherwise use a normalized package name as
50
+ their owner prefix. Prefixes use lowercase letters, digits and hyphens, starting with a letter;
51
+ `plugin` and `skill` are reserved. See page 11 for the command route and collision rules.
40
52
  - **Hooks.** A package declares `"harness": { "extends": [...], "hooks": "./hooks.mjs" }`. Events:
41
53
  `wiki.page.saved`, `history.run.ended`. A hook has 30 seconds; its errors are kept in
42
54
  `plugin-hooks.json`. `ctx.call(connection, tool, args)` reaches only a connected app that is on
@@ -48,7 +60,10 @@ callable result for one run.
48
60
  capability. Automatic or refinement-triggered continuations with it are rejected rather than
49
61
  weakening the frozen set. The `side-questions` descriptor owns the fixed instruction for the
50
62
  restricted side-question run; it has no tools, providers, connectors, subagents or variant code
51
- (PROP-0019).
63
+ (PROP-0019). In plan mode, delegation is limited to research children: the resolved profile must
64
+ have explicit tool and plugin allowlists confined to the research set, and the child's frozen
65
+ capabilities are intersected with the parent and profile. A profile name or its instructions alone
66
+ never grants eligibility (INV-RUN-8).
52
67
 
53
68
  ## Invariants
54
69
 
@@ -78,9 +93,11 @@ callable result for one run.
78
93
  parts are required and refuse to be switched off; switching something on grants no permission.
79
94
  A side-question run also has no variant plugin code or tool guidance (PROP-0019).
80
95
  - **INV-PLG-10** Every Harness-managed built-in feature is registered by `BuiltinPluginRegistry`.
81
- Its descriptor, world tools, server handlers and terminal command metadata share one owner. A
82
- module's world part releases resources through the existing session and Harness lifecycle. This
83
- registry does not load third-party Pi code or bypass its trust checks.
96
+ Its descriptor, world tools, server handlers and any plugin-owned terminal commands share one
97
+ owner. First-class Harness terminal commands are registered by the terminal command registry and
98
+ do not depend on a feature plugin's command metadata or attachment state. A module's world part
99
+ releases resources through the existing session and Harness lifecycle. This registry does not
100
+ load third-party Pi code or bypass its trust checks.
84
101
  - **INV-PLG-11** Built-in plugin categories are a documentation ownership taxonomy. They are not a
85
102
  `PluginDescriptor` field, a public response field or a second attachment scope.
86
103
  - **INV-CAP-1** A capability set only narrows. `intersect` never adds access; a run's set is
@@ -119,7 +136,7 @@ metadata or availability:
119
136
  | Memory | Wiki, history and trajectory | Storage and run records remain Foundation-owned services |
120
137
  | Automation | Subagents, ask-user, checklist, lessons and refinement | Run lifecycle, capability narrowing and approval gate remain Foundation-owned |
121
138
  | Integrations | Connected apps/MCP and Hub | Connector credentials and plugin installation remain behind Foundation services |
122
- | Operations | Doctor and worktrees | Diagnostics and repository operations remain Harness-managed services |
139
+ | Operations | Doctor, worktrees and usage | Diagnostics, repository operations and usage analytics remain Harness-managed services |
123
140
 
124
141
  ## Terminal resource inspection
125
142
 
@@ -4,7 +4,9 @@
4
4
 
5
5
  A run's tools come from four places: the harness's own tools, the connected-apps broker,
6
6
  subagent tools, and the application's tool providers. Every call, whatever its source, passes one
7
- per-call gate that asks the application's policy, then standing approvals, then the person.
7
+ per-call gate. Native writes also obey the run's pinned workspace boundary, with shell writes
8
+ contained by an OS sandbox; the gate then applies application policy, standing approvals and the
9
+ person without letting remembered per-tool grants widen that boundary.
8
10
 
9
11
  ## Components
10
12
 
@@ -14,39 +16,89 @@ per-call gate that asks the application's policy, then standing approvals, then
14
16
  | `dispatch` | `tools/dispatch.ts` | One entry point for every History, Wiki and skill call, from a Pi tool, the server or a terminal. |
15
17
  | `harnessTools` | `tools/harnessTools.ts` | The definitions as `ToolSpec`s for one session. |
16
18
  | `askUserTool` (`ask_user`) | `tools/askUser.ts` | Stops an attended plan run and asks up to five related questions with choices and concise 1–2 word labels; absent from newly resolved normal runs. |
17
- | `checklistTool` (`checklist_write`) | `tools/checklist.ts` | Writes the run's whole checklist as a `checklist.updated` event (PROP-0006). Read-only for the gate, so plan mode allows it. Terminal sessions register the same tool; it reaches the run through the `checklist` telemetry call. The prompt's Progress group asks the model to keep it current because the user follows work from it. |
18
- | `subagentTools` | `tools/spawnSubagent.ts` | `spawn_subagent` (requires a short `title`, PROP-0030), `wait_subagents`. |
19
+ | `checklistTool` (`checklist_write`) | `tools/checklist.ts` | Writes the run's whole checklist as a `checklist.updated` event (PROP-0006). Read-only for the gate, so plan mode allows it. Terminal sessions register the same tool; it reaches the run through the `checklist` telemetry call. The prompt's Progress group asks the model to keep it current and write one final update before replying; unfinished work is not automatically marked complete. |
20
+ | `subagentTools` | `tools/spawnSubagent.ts` | `spawn_subagent` (requires a short `title`, PROP-0030) and `wait_subagents`; normal-mode descriptions guide selective, bounded delegation, while plan-mode spawns require a named eligible read-only research profile. |
19
21
  | `annotationsPart` (`page_notes_list`, `page_notes_read`, `page_notes_mark_fixed`) | `annotations/part.ts` | World tools over the page notes a person shared with the session or agent (PROP-0017). `page_notes_mark_fixed` records the agent's claim that it fixed a note it was sent; it never resolves a note or changes grants (PROP-0018). |
20
22
  | `webTools` | `web/tools.ts` | `web_search` (Brave, key from the secret store), `web_fetch` (checked public reads). An application provides them as a provider. |
21
23
  | gate | `sessions/sessions.ts::gate`, `sessions/runner.ts` | `ToolGate { ask, check }`, loaded as the last extension (`xagent-approvals`). |
22
24
  | `Approvals`, `decide`, `grant`, `approvalKey` | `sessions/approvals.ts` | Standing approvals. |
23
25
 
24
- Harness tools: `history_search`, `history_read`, `trajectory_search`, `trajectory_read`,
25
- `wiki_search`, `wiki_read`, `wiki_list`, `wiki_write` (not in the default set), `skill_load`,
26
- `ask_user` (attended plan runs and pinned continuations only), `checklist_write`. Broker:
26
+ Harness tools: `get_capabilities` (read-only current-run discovery), `history_search`, `history_read`,
27
+ `trajectory_search`, `trajectory_read`, `wiki_search`, `wiki_read`, `wiki_list`, `wiki_write`
28
+ (not in the default set), `skill_load`, `ask_user` (attended plan runs and pinned continuations only),
29
+ `checklist_write`. Broker:
27
30
  `mcp_list_tools`, `mcp_call_tool`. Native Pi tools: `read`, `grep`, `find`,
28
31
  `ls`, `edit`, `write`, `bash`.
29
32
 
33
+ ## Cross-surface registration (PROP-0040)
34
+
35
+ The terminal registers Harness History/trajectory, Wiki-read and skill tools through
36
+ `Terminals.ownTools` and dispatches them over the session socket; they are model tools, not merely
37
+ shell commands. Enabled first-party hooks load through the protected `firstParty.list` bridge and
38
+ `terminal/piExtension.ts`. Attended planning uses the shared `ask_user`; the terminal-only question
39
+ alias is removed.
40
+
41
+ `get_capabilities` is implemented as a read-only Harness tool. Structured sessions register it from
42
+ the current run's frozen capability snapshot and live Pi tool/schema inventory. The terminal
43
+ extension (`terminal/piExtension.ts`) registers the same schema from Pi's active and registered tools;
44
+ when plan mode is active it reports the plan's allowed tool set. `sessions/planMode.ts` includes it in
45
+ the shared read-only allowlist. Host sessions use this Harness tool rather than defining a duplicate.
46
+ It reports availability, never permission, and every call remains subject to the gate.
47
+
30
48
  ## Native file paths
31
49
 
32
50
  The read-only `read`, `grep`, `find` and `ls` tools accept paths relative to the working folder,
33
- absolute paths, and `~` paths. The working folder is a default location, not a repository boundary;
34
- the operating system's access for the Harness process decides what can be read. The compiled file
35
- guidance names the configured data root. `edit`, `write` and `bash` continue through the existing
36
- gate and policy. A side-question run has an empty capability set and no tools (PROP-0019).
51
+ absolute paths, and `~` paths. The working folder is a default location, not a read boundary; the
52
+ operating system's access for the Harness process decides what can be read. The compiled file
53
+ guidance names the configured data root. Each run pins `workspaceRoot`: the nearest Git root or
54
+ worktree above its effective working directory, or the canonical working directory when there is no
55
+ repository. The filesystem root itself is rejected because it would collapse the write boundary.
56
+ `edit` and `write` resolve targets canonically, revalidate the pinned target at mutation,
57
+ and atomically replace the directory entry in a helper process constrained by the OS sandbox. This
58
+ prevents hard-link writes from changing another alias; normal file mode is retained where possible,
59
+ but ACLs and extended attributes are not guaranteed to survive replacement. An outside target
60
+ requires a human-only one-call approval (or an eligible Full Permission answer) and is not covered by
61
+ remembered tool grants.
62
+
63
+ Bash, including an initial `!` command, runs in an OS-enforced sandbox that permits broad reads but
64
+ writes only inside the root. Before default-scope Bash, Harness scans the workspace for regular-file
65
+ inodes with links outside it; an incomplete scan or possible external alias blocks the command, which
66
+ must be repaired or copied. The sandbox also prevents its own processes from creating cross-boundary
67
+ hard links; macOS denies hard-link creation entirely, including within the workspace. A local process
68
+ outside Harness running as the same user is outside this protection.
69
+ The Bash tool's optional `workspaceWriteScope` defaults to `workspace`; `outside` requests a
70
+ human-only one-call approval before the exact command runs once with broader filesystem access. The
71
+ approval shows the full escaped command in scrollable details and fails closed if the terminal
72
+ cannot show both the details and the choices. The terminal uses `!outside <command>` for the same
73
+ explicit request on a direct initial command.
74
+ stdout/stderr never proves a sandbox denial: denial text cannot request approval, and a command is
75
+ never replayed after partial effects. Full Permission is the explicit eligible-run bypass.
76
+ Unsupported or unavailable sandbox enforcement fails closed. A side-question run has an empty
77
+ capability set and no tools (PROP-0019).
37
78
 
38
79
  ## The gate, per call
39
80
 
81
+ For native `edit` and `write`, the canonical target check is added to policy scope before approval;
82
+ the registered tool can mutate only through the policy-bound atomic helper. Bash workspace scope
83
+ scans for external hard links and then runs under the OS sandbox. An explicit outside scope is
84
+ approved before execution and runs once; stdout/stderr never triggers approval or retry. The
85
+ eligible Full Permission switch may answer the explicit request under its existing restrictions, but
86
+ a remembered tool grant cannot.
87
+
40
88
  ```text
41
89
  child run and tool outside its snapshot ─► refuse
42
- plan mode and tool not research-safe ─► refuse (before approvals)
90
+ plan mode and tool/call not research-safe ─► refuse (including ineligible spawn profiles, outside-scope or denied Bash; before approvals)
91
+ plan-mode Bash ─► read-only OS sandbox, never widened, run once
92
+ canonicalize native write target / verify pinned root
43
93
  policy.scope(call)
44
94
  not allowed and (automatic or no approval possible) ─► refuse with reason
45
95
  policy.authorize(call) throws ─► refuse
46
- allowed, not humanOnly, (automatic or not gated) ─► run
47
- trigger probe or improver ─► refuse (nobody to ask)
48
- run.requestApproval(...) ─► standing approvals may answer, else the person
49
- denied ─► refuse
96
+ outside native file target ─► human-only one-call approval; eligible Full Permission may answer
97
+ Bash workspace scope ─► hard-link scan, OS sandbox, run once
98
+ Bash outside scope ─► pre-execution human-only approval, then run once unconfined
99
+ trigger probe, improver, reflection or unattended ─► refuse anything requiring a person
100
+ run.requestApproval(...) ─► standing approvals may answer where eligible, else the person
101
+ denied ─► refuse; never replay a command
50
102
  policy.authorize(call) again ─► run or refuse
51
103
  ```
52
104
 
@@ -60,23 +112,49 @@ the switch and global tools.
60
112
 
61
113
  - **INV-GATE-1** Every tool call of every run passes the gate. No tool source bypasses it,
62
114
  including extension-registered tools (which ask before every call) and the initial Bash command.
115
+ - **INV-TOOL-9** `checkpoint_write` records one bounded handoff for the active run and is read-only
116
+ and plan-safe: no field changes files, and the record is the `checkpoint` run event (PROP-0041).
117
+ - **INV-TOOL-10** Bash keeps one command budget on every surface: 120 seconds when the call says
118
+ nothing, 300 seconds as the ceiling without a `reason`, 900 seconds as the maximum, and a timeout
119
+ returns the partial output with a narrowing instruction instead of blocking the reply.
63
120
  - **INV-GATE-2** The gate is the last extension loaded, after disk, variant and inline extensions, so
64
121
  it sees the final input of every call.
65
- - **INV-GATE-3** Order is fixed: capability and plan-mode refusals, then the policy's scope and
66
- authorize, then standing approvals, then the person, then authorize again.
122
+ - **INV-GATE-3** Order is fixed: capability and plan-mode refusals, canonicalize native file-write
123
+ targets, consult the policy's scope and authorize, combine mandatory outside-write approval with
124
+ that scope, then eligible standing approvals or the person, then authorize again. Native mutations
125
+ revalidate the authorized target and use atomic replacement inside an OS-enforced write scope.
126
+ Default Bash scans for external hard links, then runs only inside the OS sandbox. `workspaceWriteScope:
127
+ 'outside'` requests human-only approval before running that exact command once with broader access.
128
+ Command output never proves a sandbox denial and never triggers approval or replay; earlier
129
+ side-effects are not repeated. The policy may tighten or deny a call, but cannot waive the boundary.
130
+ Outside file writes and broad Bash calls cannot use remembered per-tool approval; only the eligible
131
+ Full Permission switch can answer automatically under its existing restrictions. In plan mode, Bash
132
+ runs only in the read-only scope (PROP-0043): no writes but a discarded temp folder, no network,
133
+ no signals outside the sandbox, no macOS mach services; Full Permission and `outside` never apply.
67
134
  - **INV-GATE-4** Refinement runs (`probe`, `improver`) never ask a person; what would need one is
68
135
  refused, never queued (`INV-REF-4`).
69
136
  - **INV-GATE-5** SDK tools are registered only if their names are in the run's frozen capability
70
- set (`INV-CAP-1`). The terminal may retain question-tool definitions in its private Pi inventory,
71
- but normal mode deactivates them; a plan request activates only registered questions under its
72
- plan-mode gate.
137
+ set (`INV-CAP-1`). `get_capabilities` is a read-only Harness tool on structured and terminal
138
+ surfaces; it reports the frozen callable set and does not grant access. The terminal may retain
139
+ question-tool definitions in its private Pi inventory, but normal mode deactivates them; a plan
140
+ request activates only registered questions under its plan-mode gate.
73
141
  - **INV-GATE-6** Tool attachment bytes never enter the run-event stream; only references are
74
142
  recorded.
75
143
  - **INV-GATE-7** Tool descriptions name the product with `{product}` (filled by `describeTool`),
76
144
  never an application's name (`INV-BND-2`).
77
- - **INV-GATE-8** Native read-only file access is bounded by process and operating-system
78
- permissions, not by the repository root. Absolute and `~` paths are valid inputs. This does not
79
- change the gate or permissions for writes and commands.
145
+ - **INV-GATE-8** Native reads are bounded by process and operating-system permissions, not by the
146
+ workspace root; absolute and `~` paths are valid. Every run pins its canonical `workspaceRoot`.
147
+ Native `edit` and `write` mutations revalidate the authorized canonical target and atomically
148
+ replace it under an OS-enforced scope; outside targets require human-only one-call approval and
149
+ cannot use remembered tool grants. Default Bash scans the workspace for possible outside hard-link
150
+ aliases and is OS-sandboxed to write only inside the root while retaining broad reads; the sandbox
151
+ prevents its own processes from creating cross-boundary hard links, and macOS denies all hard-link
152
+ creation in the sandbox. An incomplete scan or missing
153
+ enforcement fails closed. An explicit `workspaceWriteScope: 'outside'` request is approved before
154
+ execution and runs once; command output never triggers approval or retry. The terminal exposes the
155
+ same choice as `!outside <command>`. Full Permission is the only standing boundary bypass and
156
+ remains subject to host policy, authorization, read-only, plan, unattended, probe, improver and
157
+ reflection restrictions. The rule does not protect against a peer process running as the same user.
80
158
 
81
159
  ## Data
82
160
 
@@ -85,7 +163,9 @@ the switch and global tools.
85
163
  ## Source map
86
164
 
87
165
  `src/tools/*.ts`, `src/web/*.ts`, `src/sessions/sessions.ts`, `src/sessions/runner.ts`,
88
- `src/sessions/approvals.ts`, `src/sessions/policy.ts`.
166
+ `src/sessions/workspaceBoundary.ts`, `src/sessions/initialBash.ts`, `src/core/workspaceBashTool.ts`,
167
+ `src/core/workspaceSandbox.ts`, `src/core/workspaceWriteOperations.ts`, `src/sessions/approvals.ts`,
168
+ `src/sessions/policy.ts`. Decision: [ADR-0036](../decisions/ADR-0036-workspace-write-operation-boundary.md).
89
169
 
90
170
  ## Session naming (PROP-0010)
91
171