@zihanw/pi-forge 0.5.4 → 0.5.6

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 (237) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +67 -103
  3. package/README.zh-CN.md +67 -95
  4. package/assets/pi-forge-header-concept-1.png +0 -0
  5. package/assets/readme/PROVENANCE.md +95 -0
  6. package/assets/readme/en/capability-tools.gif +0 -0
  7. package/assets/readme/en/context-composition.gif +0 -0
  8. package/assets/readme/en/context-toggle.gif +0 -0
  9. package/assets/readme/en/draft-diff.png +0 -0
  10. package/assets/readme/en/edit-draft-diff.gif +0 -0
  11. package/assets/readme/en/editor-overview-v3.png +0 -0
  12. package/assets/readme/en/editor-overview.png +0 -0
  13. package/assets/readme/en/mode-tools.gif +0 -0
  14. package/assets/readme/en/regex-transforms.gif +0 -0
  15. package/assets/readme/en/tool-selection.gif +0 -0
  16. package/assets/readme/tui-quickstart.gif +0 -0
  17. package/assets/readme/zh-CN/capability-tools.gif +0 -0
  18. package/assets/readme/zh-CN/context-composition.gif +0 -0
  19. package/assets/readme/zh-CN/context-toggle.gif +0 -0
  20. package/assets/readme/zh-CN/draft-diff.png +0 -0
  21. package/assets/readme/zh-CN/edit-draft-diff.gif +0 -0
  22. package/assets/readme/zh-CN/editor-overview-v3.png +0 -0
  23. package/assets/readme/zh-CN/editor-overview.png +0 -0
  24. package/assets/readme/zh-CN/mode-tools.gif +0 -0
  25. package/assets/readme/zh-CN/regex-transforms.gif +0 -0
  26. package/assets/readme/zh-CN/tool-selection.gif +0 -0
  27. package/dist/active-state.d.ts +148 -0
  28. package/dist/active-state.d.ts.map +1 -0
  29. package/dist/active-state.js +374 -0
  30. package/dist/active-state.js.map +1 -0
  31. package/dist/agent-profile.d.ts +1 -0
  32. package/dist/agent-profile.d.ts.map +1 -1
  33. package/dist/agent-profile.js +20 -14
  34. package/dist/agent-profile.js.map +1 -1
  35. package/dist/capabilities.d.ts +39 -0
  36. package/dist/capabilities.d.ts.map +1 -0
  37. package/dist/capabilities.js +160 -0
  38. package/dist/capabilities.js.map +1 -0
  39. package/dist/capability-anchors.d.ts +45 -0
  40. package/dist/capability-anchors.d.ts.map +1 -0
  41. package/dist/capability-anchors.js +263 -0
  42. package/dist/capability-anchors.js.map +1 -0
  43. package/dist/capability-command.d.ts +4 -0
  44. package/dist/capability-command.d.ts.map +1 -0
  45. package/dist/capability-command.js +164 -0
  46. package/dist/capability-command.js.map +1 -0
  47. package/dist/capability-events.d.ts +79 -0
  48. package/dist/capability-events.d.ts.map +1 -0
  49. package/dist/capability-events.js +478 -0
  50. package/dist/capability-events.js.map +1 -0
  51. package/dist/capability-projection.d.ts +22 -0
  52. package/dist/capability-projection.d.ts.map +1 -0
  53. package/dist/capability-projection.js +273 -0
  54. package/dist/capability-projection.js.map +1 -0
  55. package/dist/capability-protocol.d.ts +21 -0
  56. package/dist/capability-protocol.d.ts.map +1 -0
  57. package/dist/capability-protocol.js +15 -0
  58. package/dist/capability-protocol.js.map +1 -0
  59. package/dist/capability-state.d.ts +74 -0
  60. package/dist/capability-state.d.ts.map +1 -0
  61. package/dist/capability-state.js +43 -0
  62. package/dist/capability-state.js.map +1 -0
  63. package/dist/capability-tool.d.ts +11 -0
  64. package/dist/capability-tool.d.ts.map +1 -0
  65. package/dist/capability-tool.js +25 -0
  66. package/dist/capability-tool.js.map +1 -0
  67. package/dist/capability-web-host.d.ts +11 -0
  68. package/dist/capability-web-host.d.ts.map +1 -0
  69. package/dist/capability-web-host.js +105 -0
  70. package/dist/capability-web-host.js.map +1 -0
  71. package/dist/codecs/capability.d.ts +71 -0
  72. package/dist/codecs/capability.d.ts.map +1 -0
  73. package/dist/codecs/capability.js +363 -0
  74. package/dist/codecs/capability.js.map +1 -0
  75. package/dist/codecs/prompt-stack.d.ts +1 -1
  76. package/dist/codecs/prompt-stack.d.ts.map +1 -1
  77. package/dist/codecs/prompt-stack.js +140 -13
  78. package/dist/codecs/prompt-stack.js.map +1 -1
  79. package/dist/command-contribution/index.d.ts +21 -0
  80. package/dist/command-contribution/index.d.ts.map +1 -0
  81. package/dist/command-contribution/index.js +14 -0
  82. package/dist/command-contribution/index.js.map +1 -0
  83. package/dist/compile-cycle.d.ts +1 -0
  84. package/dist/compile-cycle.d.ts.map +1 -1
  85. package/dist/compile-cycle.js.map +1 -1
  86. package/dist/compiler.d.ts.map +1 -1
  87. package/dist/compiler.js +81 -17
  88. package/dist/compiler.js.map +1 -1
  89. package/dist/forge-command.d.ts +10 -0
  90. package/dist/forge-command.d.ts.map +1 -0
  91. package/dist/forge-command.js +106 -0
  92. package/dist/forge-command.js.map +1 -0
  93. package/dist/index.d.ts +2 -2
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +58 -5
  96. package/dist/index.js.map +1 -1
  97. package/dist/json-fingerprint.d.ts +11 -0
  98. package/dist/json-fingerprint.d.ts.map +1 -0
  99. package/dist/json-fingerprint.js +66 -0
  100. package/dist/json-fingerprint.js.map +1 -0
  101. package/dist/lifecycle.d.ts +14 -0
  102. package/dist/lifecycle.d.ts.map +1 -1
  103. package/dist/lifecycle.js +167 -67
  104. package/dist/lifecycle.js.map +1 -1
  105. package/dist/payload-command.d.ts +2 -2
  106. package/dist/payload-command.d.ts.map +1 -1
  107. package/dist/payload-command.js +80 -26
  108. package/dist/payload-command.js.map +1 -1
  109. package/dist/payload-state.d.ts +1 -0
  110. package/dist/payload-state.d.ts.map +1 -1
  111. package/dist/payload-state.js +1 -0
  112. package/dist/payload-state.js.map +1 -1
  113. package/dist/policy.d.ts +2 -1
  114. package/dist/policy.d.ts.map +1 -1
  115. package/dist/policy.js +3 -0
  116. package/dist/policy.js.map +1 -1
  117. package/dist/preset-command.d.ts +1 -0
  118. package/dist/preset-command.d.ts.map +1 -1
  119. package/dist/preset-command.js +94 -36
  120. package/dist/preset-command.js.map +1 -1
  121. package/dist/preview-text.d.ts +5 -0
  122. package/dist/preview-text.d.ts.map +1 -0
  123. package/dist/preview-text.js +27 -0
  124. package/dist/preview-text.js.map +1 -0
  125. package/dist/preview.d.ts +18 -1
  126. package/dist/preview.d.ts.map +1 -1
  127. package/dist/preview.js +132 -97
  128. package/dist/preview.js.map +1 -1
  129. package/dist/profile-command.js +68 -9
  130. package/dist/profile-command.js.map +1 -1
  131. package/dist/regex.d.ts.map +1 -1
  132. package/dist/regex.js +5 -0
  133. package/dist/regex.js.map +1 -1
  134. package/dist/render-helpers.d.ts.map +1 -1
  135. package/dist/render-helpers.js +2 -0
  136. package/dist/render-helpers.js.map +1 -1
  137. package/dist/repositories/capability.d.ts +53 -0
  138. package/dist/repositories/capability.d.ts.map +1 -0
  139. package/dist/repositories/capability.js +294 -0
  140. package/dist/repositories/capability.js.map +1 -0
  141. package/dist/runtime/capability-runtime.d.ts +93 -0
  142. package/dist/runtime/capability-runtime.d.ts.map +1 -0
  143. package/dist/runtime/capability-runtime.js +987 -0
  144. package/dist/runtime/capability-runtime.js.map +1 -0
  145. package/dist/runtime/profile-runtime.d.ts.map +1 -1
  146. package/dist/runtime/profile-runtime.js +10 -9
  147. package/dist/runtime/profile-runtime.js.map +1 -1
  148. package/dist/runtime/prompt-stack-runtime.d.ts +7 -4
  149. package/dist/runtime/prompt-stack-runtime.d.ts.map +1 -1
  150. package/dist/runtime/prompt-stack-runtime.js +47 -11
  151. package/dist/runtime/prompt-stack-runtime.js.map +1 -1
  152. package/dist/runtime/tool-policy-runtime.d.ts +10 -0
  153. package/dist/runtime/tool-policy-runtime.d.ts.map +1 -1
  154. package/dist/runtime/tool-policy-runtime.js +171 -30
  155. package/dist/runtime/tool-policy-runtime.js.map +1 -1
  156. package/dist/session-adapter.d.ts +13 -0
  157. package/dist/session-adapter.d.ts.map +1 -1
  158. package/dist/session-adapter.js +110 -0
  159. package/dist/session-adapter.js.map +1 -1
  160. package/dist/session-usage.d.ts +65 -0
  161. package/dist/session-usage.d.ts.map +1 -0
  162. package/dist/session-usage.js +134 -0
  163. package/dist/session-usage.js.map +1 -0
  164. package/dist/subagent/fingerprints.d.ts +3 -15
  165. package/dist/subagent/fingerprints.d.ts.map +1 -1
  166. package/dist/subagent/fingerprints.js +5 -69
  167. package/dist/subagent/fingerprints.js.map +1 -1
  168. package/dist/subagent/index.d.ts +2 -0
  169. package/dist/subagent/index.d.ts.map +1 -1
  170. package/dist/subagent/index.js +2 -0
  171. package/dist/subagent/index.js.map +1 -1
  172. package/dist/subagent-host.d.ts +2 -2
  173. package/dist/subagent-host.d.ts.map +1 -1
  174. package/dist/subagent-host.js +13 -1
  175. package/dist/subagent-host.js.map +1 -1
  176. package/dist/types.d.ts +6 -1
  177. package/dist/types.d.ts.map +1 -1
  178. package/dist/types.js.map +1 -1
  179. package/dist/web-editor/client-script.generated.d.ts.map +1 -1
  180. package/dist/web-editor/client-script.generated.js +1 -1
  181. package/dist/web-editor/client-script.generated.js.map +1 -1
  182. package/dist/web-editor/client-styles.generated.d.ts.map +1 -1
  183. package/dist/web-editor/client-styles.generated.js +1 -1
  184. package/dist/web-editor/client-styles.generated.js.map +1 -1
  185. package/dist/web-editor/server.d.ts.map +1 -1
  186. package/dist/web-editor/server.js +152 -4
  187. package/dist/web-editor/server.js.map +1 -1
  188. package/dist/web-editor/styles.d.ts.map +1 -1
  189. package/dist/web-editor/styles.js +573 -165
  190. package/dist/web-editor/styles.js.map +1 -1
  191. package/dist/web-editor/types.d.ts +81 -1
  192. package/dist/web-editor/types.d.ts.map +1 -1
  193. package/dist/web-host.d.ts +9 -0
  194. package/dist/web-host.d.ts.map +1 -1
  195. package/dist/web-host.js +210 -11
  196. package/dist/web-host.js.map +1 -1
  197. package/dist/workspace.d.ts +11 -0
  198. package/dist/workspace.d.ts.map +1 -1
  199. package/dist/workspace.js +63 -5
  200. package/dist/workspace.js.map +1 -1
  201. package/docs/README.md +4 -0
  202. package/docs/design/README.md +3 -1
  203. package/docs/design/architecture-0.5.md +22 -0
  204. package/docs/design/archive/2026-09-12-system-update-design.md +229 -0
  205. package/docs/design/pi-forge-system-update-design-notes.md +157 -229
  206. package/docs/development/release.md +20 -20
  207. package/docs/development/roadmap.md +21 -2
  208. package/docs/development/scoped-global-profiles-stacks.md +1 -1
  209. package/docs/development/setup.md +1 -1
  210. package/docs/getting-started.md +1 -1
  211. package/docs/guides/delegation.md +22 -20
  212. package/docs/guides/migrating-to-0.5.md +29 -0
  213. package/docs/guides/use-cases.md +6 -2
  214. package/docs/guides/web-editor.md +74 -5
  215. package/docs/reference/active-state.md +77 -0
  216. package/docs/reference/capabilities.md +259 -0
  217. package/docs/reference/commands.md +52 -24
  218. package/docs/reference/configuration.md +4 -2
  219. package/docs/reference/features.md +70 -4
  220. package/docs/reference/provider-support.md +67 -0
  221. package/docs/reference/public-api.md +49 -3
  222. package/docs/reference/session-cache.md +112 -0
  223. package/docs/reference/stack-schema.md +18 -4
  224. package/docs/reference/subagent-host-port.md +8 -0
  225. package/docs/zh-CN/README.md +3 -0
  226. package/docs/zh-CN/getting-started.md +1 -1
  227. package/docs/zh-CN/guides/delegation.md +22 -10
  228. package/docs/zh-CN/guides/migrating-to-0.5.md +29 -0
  229. package/docs/zh-CN/guides/web-editor.md +75 -7
  230. package/docs/zh-CN/reference/capabilities.md +259 -0
  231. package/docs/zh-CN/reference/commands.md +61 -33
  232. package/docs/zh-CN/reference/provider-support.md +67 -0
  233. package/docs/zh-CN/reference/session-cache.md +112 -0
  234. package/examples/capabilities/review.json +12 -0
  235. package/examples/capabilities/write-tools.json +15 -0
  236. package/examples/read-first-worker-prompt-stack.json +49 -0
  237. package/package.json +16 -9
@@ -4,7 +4,10 @@
4
4
 
5
5
  > **Experimental:** This API and its backends may change independently of stable prompt-stack and profile behavior.
6
6
 
7
- The optional `@zihanw/pi-forge-subagents` package executes an explicitly authorized agent profile as a separate, clean, one-shot Pi process. It runs in the foreground and returns a bounded report to the parent conversation.
7
+ The optional `@zihanw/pi-forge-subagents` package executes an explicitly authorized agent profile through a selected backend. The default read-only backends use a separate, clean, one-shot Pi process; write-capable backends have different boundaries described below. The documented flow runs in the foreground and returns a bounded report to the parent conversation.
8
+
9
+ > **Default-tool compatibility:** Forge 0.5.5 provides `tools.initial`; the independently published optional subagents 0.5.3 is not compatible with this field. Until the optional package raises its Forge floor to `^0.5.5` and ships its separate fix, use matching local checkouts; see [tool-selection compatibility](../reference/subagent-host-port.md#tool-selection-compatibility). This optional compatibility work is not a main-package release gate.
10
+
8
11
 
9
12
  ## Enable a profile
10
13
 
@@ -43,7 +46,7 @@ Humans use:
43
46
 
44
47
  `plan` resolves the profile and stack, compiles and validates the exact immutable provider-bound plan, displays it, and discards it without provider transport.
45
48
 
46
- Profile selectors accept the same grammar everywhere: `reviewer` (project first), `project:reviewer`, or `global:reviewer`. When both scopes expose the same ID, the project profile keeps the concise selector and the global profile remains callable as `global:<id>`.
49
+ Profile selectors use the same grammar everywhere: `reviewer`, `project:reviewer`, or `global:reviewer`. For delegation, a bare ID selects `project:<id>`; use an explicit `global:<id>` selector for a global profile. Same-ID profiles remain separate and do not inherit delegation policy from one another.
47
50
 
48
51
  The parent model uses `forge_subagent_profiles` to discover enabled profiles and `forge_subagent` to invoke one. A restrictive parent stack must allow both tool names. Discovery is local/no-egress and reports metadata, resolution readiness, effective backend/timeout, approval mode, and whether parent tool policy permits invocation.
49
52
 
@@ -51,18 +54,20 @@ Projects with only a few frequently used profiles can set `summaryInToolDescript
51
54
 
52
55
  ## Parallel invocation
53
56
 
54
- `forge_subagent` is a parallel-execution tool: the parent model may issue several calls in one turn, and they prepare and run concurrently. Interactive approval dialogs are serialized one at a time because Pi's selector/editor UI is a single slot—a second concurrent dialog would clear the first and leave it unresolved—so each call waits its turn for the dialog and then executes immediately, letting approved runs overlap. Unattended invocation needs no dialog and is fully concurrent. Each run is an independent `pi` subprocess and provider request; a burst of parallel calls multiplies provider cost and process load, so keep the parent tool policy conservative until a configurable concurrency cap lands.
57
+ `forge_subagent` is a parallel-execution tool: the parent model may issue several calls in one turn, and they prepare and run concurrently. Interactive approval dialogs are serialized one at a time because Pi's selector/editor UI is a single slot—a second concurrent dialog would clear the first and leave it unresolved—so each call waits its turn for the dialog and then executes immediately, letting approved runs overlap. Unattended invocation needs no dialog and is fully concurrent. Fresh-process backends create an independent child process and provider request; `pi-inprocess` instead runs in the host runtime. A burst of parallel calls multiplies provider cost and process load, so keep the parent tool policy conservative until a configurable concurrency cap lands.
55
58
 
56
59
  ## Backends and precedence
57
60
 
58
- Two fresh-process backends are registered:
61
+ Matching development versions of the optional subagents package and runtime expose four backend IDs. These notes describe unfinished optional-package source, not a finalized companion release; the main Forge 0.5.5 package does not require them.
59
62
 
60
- - `pi-subprocess-readonly` is the default and uses `pi --mode text --print`.
61
- - `pi-rpc-readonly` uses `pi --mode rpc`.
63
+ - `pi-subprocess-readonly` is the default and uses `pi --mode text --print`. It exposes the `read`/`grep`/`find`/`ls` allowlist and is shared-user: the allowlist is not an OS sandbox.
64
+ - `pi-rpc-readonly` uses `pi --mode rpc` with the same shared-user read-only policy; only the process protocol differs.
65
+ - `pi-inprocess` runs in the host model runtime with the invoking user's full privileges. It is a workspace-write backend and can expose `read`/`grep`/`find`/`ls`/`edit`/`write`/`bash` when the sealed access level and stack policy allow them; it has no OS sandbox. It is the backend for extension-registered providers that cannot be used in fresh children.
66
+ - `pi-bwrap-write` is an opt-in Linux Bubblewrap backend. It provides an isolated `workspace-write` mount for the selected workspace and can expose `read`/`grep`/`find`/`ls`/`edit`/`write`/`bash` when allowed. Writes go directly to that workspace; they are not staged for a separate approval/apply step. Bubblewrap and, by default, a git workspace are required.
62
67
 
63
- Both execute the same sealed prompt and shared-user read-only policy; only their process protocol differs. There is no fallback if the selected backend is unavailable.
68
+ There is no fallback if the selected backend is unavailable. Fresh-process backends reject extension-registered providers as non-portable; use `pi-inprocess` for those providers. Do not read the development matrix or the `tools.initial` compatibility fix as a claim that the paired companion release is already published.
64
69
 
65
- Interactive backend precedence is: per-run override, project profile override, project default, user default, built-in default. Unattended model invocation is pinned to the effective configured backend and rejects a per-call override. Timeout follows profile, project, user, then the 60-second built-in default; valid values are 1,000–3,600,000 ms. Host timeout is best effort.
70
+ For human runs, backend precedence is: explicit per-run `--backend`, matching profile override, trusted project default, global default, then the built-in `pi-subprocess-readonly`. An interactive model invocation may also supply a per-call backend override; an unattended model invocation is pinned to the effective profile/configured backend and rejects that override. Timeout follows matching profile override, trusted project default, global default, then the 60-second built-in default; valid values are 1,000–3,600,000 ms. Host timeout is best effort.
66
71
 
67
72
  ## Approval and unattended invocation
68
73
 
@@ -76,28 +81,25 @@ To authorize the parent model without per-run approval:
76
81
  }
77
82
  ```
78
83
 
79
- This affects only `forge_subagent`; `/forge-agent run` remains interactive. It is ignored in untrusted projects and malformed values fail closed. Treat this project `subagents.json` as an authorization file: do not enable or commit it unless every parent agent allowed to call `forge_subagent` may send the compiled prompt and readable file contents to the selected provider without asking again.
84
+ This affects only `forge_subagent`; `/forge-agent run` remains interactive. The flag may come from global defaults or a trusted project file, with the project value taking precedence. With the unreleased companion fix, an absent flag inherits; an explicitly non-boolean value sets that layer to `false` and emits a warning. A valid boolean in a higher-priority layer still overrides normally. If an entire config file is unreadable, malformed, or not a JSON object, that file is ignored with a warning and an earlier valid layer may remain effective. An untrusted project's project settings are ignored, and the execution trust gate blocks delegation runs from that project. Treat `subagents.json` as an authorization file: do not enable or commit unattended invocation unless every permitted parent agent may send the compiled prompt and readable file contents to the selected provider without asking again.
80
85
 
81
86
  ## Child context and output
82
87
 
83
- The child receives a clean conversation, the exact profile model/thinking/stack, and the delegated task as a protected final user message. It does not automatically receive parent history.
88
+ An ordinary new child starts with a clean conversation, the exact profile model/thinking/stack, and the delegated task as a protected final user message. It does not automatically receive parent history. Explicit retained-child continuation and background execution are separate development features documented in the [companion package](https://github.com/MacroSony/pi-forge-subagents).
84
89
 
85
- Candidate tools are `read`, `grep`, `find`, and `ls`, further restricted by stack tool policy. The child loads no write/edit/shell tools, skills, prompt templates, project context files, or third-party extensions.
90
+ For `pi-subprocess-readonly` and `pi-rpc-readonly`, candidate tools are `read`, `grep`, `find`, and `ls`, further restricted by stack tool policy; these children load no write/edit/shell tools, skills, prompt templates, project context files, or third-party extensions. `pi-inprocess` and `pi-bwrap-write` can receive the write-capable tool surface described above only when their access level and stack policy allow it.
86
91
 
87
92
  The model-visible result is bounded. Expandable human details retain normalized status, a text transcript, tool events, diagnostics, usage, approval receipt, and execution report. Retained strings are bounded, base64-like text is redacted, and the transcript keeps a 512 KiB rolling tail. Inline image bytes are replaced by MIME/encoded-size metadata before retention in the parent session.
88
93
 
89
94
  ## Security boundary
90
95
 
91
- The current backends are **shared-user, not operating-system sandboxes**.
96
+ The security boundary is backend-specific:
92
97
 
93
- - “Read-only” is a model-tool policy. The process retains the invoking user's OS permissions.
94
- - Absolute paths readable by that user may be read and sent to the selected provider.
95
- - Text may be retained in parent tool-result details and Pi's on-disk session JSONL.
96
- - Timeout and cancellation are best effort.
97
- - `/tree` changes the active conversation branch; abandoned entries can remain on disk.
98
- - `/tree` cannot undo provider requests, billing, or external effects.
99
- - Removing sensitive retained text requires deleting the relevant Pi session data.
98
+ - The subprocess and RPC read-only backends are **shared-user, not OS sandboxes**. “Read-only” is a model-tool policy; the child retains the invoking user's OS permissions. Absolute paths readable by that user may be read and sent to the selected provider.
99
+ - `pi-inprocess` is also shared-user and runs in the host process with full user privileges. Its allowlist can permit write/edit/`bash`, but it provides no OS isolation.
100
+ - `pi-bwrap-write` is the isolated Linux exception: Bubblewrap exposes the selected workspace as the writable project mount, alongside read-only runtime mounts and temporary sandbox storage. It is not a staged patch/apply workflow, and it does not provide network isolation.
101
+ - Across backends, timeout and cancellation are best effort. Text may be retained in parent tool-result details and Pi's on-disk session JSONL; `/tree` cannot undo provider requests, billing, or external effects, and abandoned entries can remain on disk. Removing sensitive retained text requires deleting the relevant Pi session data.
100
102
 
101
- The default tools intentionally provide no mutation path. Do not add write, edit, or shell access to this shared-user design. OS isolation and separately approved staged writes remain future work.
103
+ Choose a read-only backend when no mutation path is intended. Treat write-capable backends as explicit authorization to modify the selected workspace, not as a promise of separately approved staged changes.
102
104
 
103
105
  For integration authors, see the [subagent host port contract](../reference/subagent-host-port.md).
@@ -101,6 +101,35 @@ Subagent execution moved out of the main package into the optional `@zihanw/pi-f
101
101
  | `@zihanw/pi-forge/src/*` aliases | removed; no replacement (internals) |
102
102
  | root loader/profile/catalog/engine re-exports | removed; no replacement (internals) |
103
103
 
104
+ ## Upstream Pi 0.87 migration
105
+
106
+ Forge 0.5.5 requires upstream Pi `>=0.87.0 <0.88.0`. Dual 0.86 runtime support is not provided (repo dev SDK is pinned to `0.87.0`, peer range `>=0.87.0 <0.88.0`). Main and optional packages release independently.
107
+
108
+ **Restart Pi after upgrading:** Updating the global installation does not replace the core in already-running processes. If the session predates the upgrade, exit and start Pi again before resuming it; extension-only `/reload` does not upgrade the running core. Use the new `/preset ui` URL; old server tokens are not retained.
109
+
110
+ ### Context hook migration (`context_with_system`)
111
+
112
+ - **Standard `context` excludes System:** In Pi 0.87, standard `context` lifecycle hooks intentionally exclude System messages. Any prior extension or custom integration that inspected, modified, or relied upon full System context must move to the full `context_with_system` hook.
113
+ - **Unified Forge pipeline:** Forge moves its entire compiler, base prompt replacement, and capability projection pipeline together to `context_with_system`, operating cleanly at the full-transcript boundary without an internal two-phase split.
114
+ - **`before_agent_start` timing:** Any forced System prompt injection via `before_agent_start` continues to execute later in Pi's lifecycle than `context_with_system`.
115
+
116
+ ### Canonical projection and session history
117
+
118
+ - **`buildSessionProjection` integration:** Runtime execution, the Preview dock, and anchor locator helpers now use Pi 0.87's `buildSessionProjection`. Turn-level `context_edit` omissions, replacements, and `sourceEntry` references are reflected in ephemeral model requests while raw session JSONL history on disk remains untouched.
119
+ - **Leading System message ordering:** The SDK's incoming leading System message always remains first in the request transcript; Forge's own prefix plain metadata anchors are inserted immediately after it, never displacing the request head or user fallback.
120
+ - **Arbitrary preceding rewrites fail closed:** Third-party extensions performing arbitrary message rewrites prior to Forge must maintain unique ordered alignment with the canonical session projection. Ambiguous or destructive preceding rewrites still fail closed.
121
+
122
+ ### Continuations and settlement lifecycle
123
+
124
+ - **`agent_end` vs. `agent_settled`:** `agent_end` offers an anchor boundary after a low-level run, provided no Forge context failure or incomplete tool batch remains. However, the compile cycle and busy fence reset only on `agent_settled`. This guarantees that `agent_before_settle` continuations preserve compiled Preset inputs and active capabilities across low-level runs without dropping prompt context.
125
+
126
+ ### Guardrails and semantics unchanged
127
+
128
+ - **Project trust:** Activations and Agent control require an explicitly trusted project (`isProjectTrusted()`); human CLI disable/reset recovery remains available.
129
+ - **`sourceRevision` stale-save guard:** Existing capability updates/deletes and binding-bearing Preset updates must match the loaded source revision; capability creation must not overwrite an existing file. Stale saves fail with `409 Conflict`.
130
+ - **Save ≠ Enable:** Saving a capability library definition updates its file only and does not enable it in the session; saving an active Preset refreshes that Preset's policy immediately (updating live tool and capability authorization policy), without replacing frozen active capability snapshots.
131
+ - **Upstream defect status:** Upstream Pi metadata chunking and semantic-cut defects are NOT patched; compaction checkpoint placement is unchanged. Existing legacy session carriers remain untouched without automatic migration, and Oh My Pi (OMP) is not supported or promised.
132
+
104
133
  ## Compatibility notes
105
134
 
106
135
  - The wire shape of the host port is additive across `FORGE_HOST_PORT_VERSION = 1`; unknown operations are rejected with a plain `{ ok: false, error }` result (`"Unknown Forge host operation: …"`), not a thrown error, and optional packages must treat any operation failure as terminal for that request.
@@ -36,7 +36,7 @@ Keep independent project files:
36
36
  translator.json
37
37
  ```
38
38
 
39
- Switch with `/preset use coder`, `/preset use writer`, or `/preset use translator`. Capture a profile when a mode also needs a specific model and thinking level.
39
+ Switch with `/preset use coder`, `/preset use writer`, or `/preset use translator`. Capture a profile when a capability also needs a specific model and thinking level.
40
40
 
41
41
  ## Read-only scout
42
42
 
@@ -44,13 +44,17 @@ Allow only `read`, `grep`, `find`, and `ls`, omit editing tools, and cap chat hi
44
44
 
45
45
  Tool policy constrains model tool calls but is not an operating-system sandbox. A normal Pi agent may still have other non-tool ways to interact with its host; do not describe a prompt stack alone as process isolation.
46
46
 
47
+ ## Read first, enable command/edit tools on demand
48
+
49
+ The [Read-first Worker pair](../reference/capabilities.md#read-first-worker) keeps the minimal-worker shape: one role block and chat history. Defaults are `read` and `ls` plus `forge_capability`; one explicitly authorized binding adds `bash` and `edit`. Follow the paired-file setup and ownership/sandbox caveats before trying it.
50
+
47
51
  ## Surgical patcher
48
52
 
49
53
  Keep the Pi mirror, require the tools needed for the workflow, strip prior assistant thinking from inserted history, and move project context near the current user turn. This reduces distracting prompt material without removing relevant repository instructions.
50
54
 
51
55
  ## Payload lab
52
56
 
53
- Include `active-model` and `date-cwd`, then add compiled regex rules for deterministic redaction or formatting. Pair the stack with `/payload next` or the web editor's capture view to audit exactly what changed.
57
+ Include `active-model` and `date-cwd`, then add compiled regex rules for deterministic redaction or formatting. Pair the stack with `/forge payload next` (or bare `/payload`) or the web editor's capture view to audit exactly what changed. Use `save="path with spaces.json"` when saving a capture; existing files require `--overwrite`.
54
58
 
55
59
  Redaction is limited to the declared patterns and supported text targets. It is useful for known shapes but is not an exhaustive credential scanner or security boundary.
56
60
 
@@ -14,7 +14,7 @@ Use `/preset ui restart` to replace its server or `/preset ui stop` to close it.
14
14
 
15
15
  The editor binds to an available `127.0.0.1` port and uses a session token. Multiple Pi projects can run editors simultaneously. Lifecycle reinitialization reuses the existing editor URL for the same project when possible.
16
16
 
17
- Reads, preview, resources, and payload inspection remain available as appropriate, but writes require Pi to trust the project. Files are constrained to Pi Forge's Preset/Profile storage. Never expose or proxy the editor URL to an untrusted network.
17
+ Reads, preview, resources, and payload inspection remain available as appropriate, but writes require Pi to trust the project. Built-in Web writes are constrained to Pi Forge's Preset/Profile, Capability, and trusted Forge configuration storage. Never expose or proxy the editor URL to an untrusted network.
18
18
 
19
19
  Choose a preferred port in `.pi/forge/config.json`:
20
20
 
@@ -36,9 +36,11 @@ The editor interface is available in English and Chinese. Use the language selec
36
36
 
37
37
  The Preset workspace provides:
38
38
 
39
- - creation from the default Pi-mirror layout;
39
+ - preset creation with template choices (Default Pi prompt, Empty, Minimal Worker);
40
40
  - an ordered **Stack** tab for Block/Slot composition;
41
- - structured metadata, policy, parameters, context, and Regex editing;
41
+ - a **Policy** tab for tool/skill allow/deny resource policy and custom default tools (`tools.initial`);
42
+ - a peer **Capability bindings** tab for associating capabilities (Preset metadata no longer contains bindings);
43
+ - structured metadata, parameters, context, and **Regex** editing;
42
44
  - drag-and-drop item order and enable/disable controls;
43
45
  - validation and a full compiled preview;
44
46
  - registered-tool and loaded-skill search with exact-name chips and wildcard patterns;
@@ -48,15 +50,82 @@ The Preset workspace provides:
48
50
  - payload arming and redacted captured-payload inspection;
49
51
  - light and dark themes.
50
52
 
51
- Existing IDs are immutable during edit. Use **More → Fork** to create a different ID without breaking Profile references or the active selection. The compact selector attached to **New preset** (default `Project`) chooses where new Presets, imports, and forks are written: `Global` targets the user-global `~/.pi/forge/prompt-stacks`, `Project` targets `.pi/forge/prompt-stacks`. Those paths keep their pre-0.5.3 names for compatibility. Less-used capture, fork, import, export, and delete actions live under **More** so the Stack and Preview/Diff panes keep the available viewport. Preset rows show a `global` badge, and save/delete routes use `global:<id>` for exact global mutations. Legacy resources remain editable in place.
53
+ ### Editing and inspecting
54
+
55
+ The resource header identifies the Preset being edited separately from whether it is active in the session. The global Session capabilities strip stays available when switching surfaces. Opening **Preset properties**, item properties, rule cards, or binding details does not modify the draft. Preset properties now opens from the resource header without pushing the editor down; its field edits remain in the same Preset draft until Save. Global Session capabilities retains a compact summary and opens the non-modal **Current session** workspace, separate from the edited resource. Effective tools, instruction excerpts, active capabilities and activation controls come first; full instruction bodies, session identity and transport details are expandable. Trust/stale/error and pending/prepared warnings remain visible. Preset status feedback sits in the footer; its Disable action still targets the active Preset, not the selected draft.
56
+
57
+ - **Stack:** Rows show a name/on-off control and one summary line, such as `user · report` or `slot · chat-history`. Full identity remains available in tooltips and **Properties**, including Copy ID. Role and slot selection sit beside Properties; content follows directly. Rows support Enter/Space selection and dragging. A quiet, direct delete button remains in the item header with confirmation. Slot option forms size to their contents rather than stretching their rows.
58
+ - **Row state and pane widths:** Disabled rows use a neutral background, muted text and gray side markers; a thin selection outline means editing, not enabling. Drag the right edge of the Presets or Stack items pane to resize it. Double-click or press Enter on the separator to restore its default; arrow keys adjust it, and Escape cancels an active drag. Width preferences stay in this browser origin, never in Preset JSON, and do not dirty or save a draft. Smaller windows temporarily constrain widths to leave editing room; narrow screens retain the stacked layout.
59
+ - **Regex:** Scan rule names, enable state, stage/effect, and pattern excerpts before expanding a rule. Pattern/replacement editing stays prominent; frequency, targets, roles, and limits are under the rule's advanced section. A dashed list-end entry adds and focuses a rule. Rule/binding removal is confirmed and remains a draft change; use the Preset's **Save** action to persist changes.
60
+ - **Policy:** Permission ceiling and default tools are separate cards. Literal/wildcard editing is advanced; skill-list visibility has its own section and is not an execution sandbox.
61
+ - **Preview / Draft diff / Run diff:** Preview is a separate workspace toggle: opening, closing, or changing editor tabs preserves the current editing page. The boundary chevron widens the sidebar; wide mode offers separate shrink and focus buttons. Leaving focus restores the previous nonfocused layout and keeps the inspection tab, without replacing the draft. Choosing another editing section while focused reveals that editor and retains the dock. The panel initially uses the sidebar. Switching Preview, Draft diff, or Run diff changes only its content and preserves the current width and focus state; focused reading is entered explicitly. Narrow windows stack work areas instead of squeezing four columns; focused reading hides the editor until you return or choose an editing section. Draft diff compares draft compilation with the saved definition; Run diff compares captured provider-turn snapshots. Inspection itself does not send a model request or establish cache hits.
62
+
63
+ Profile editing groups Provider/Model and Thinking/Preset together; immutable identity appears in the heading. Profile and Capability creation still explicitly choose ID and scope. The Advanced Preset tab places editable parameters before the collapsed extension reference catalog. Preview tool details are expandable; section statistics remain available on section-title tooltips.
64
+
65
+ Contributed **Settings** pages auto-save and show pending/saving/saved/error feedback. Failed edits remain in the form. Presets, Capabilities, and Profiles retain their explicit save controls.
66
+
67
+ ### Inspecting a Capability's context impact
68
+
69
+ The **Current session** workspace places capability controls and tools on the left, and the session projection on the right (stacked on narrow screens). It shows current effective tools and a short excerpt of each active instruction snapshot. A successful Enable/Disable/Reset in this browser also shows the observed tool additions/removals; this is not a persistent history. A Capability's configured tool adjustments are labeled separately: overlapping Capabilities can produce no net tool change.
70
+
71
+ **Locate in context** keeps the controls visible and highlights related projected update blocks, scrolling to the latest match. Normal updates do not steal the reading position. The workspace inspects the **active saved Preset + current session capabilities**, not whichever Preset is being edited. The Preset editor still supports simultaneous editing and **Preview / Draft diff / Run diff**; switching workspaces preserves its draft, selection, inspection tab and width/focus.
72
+
73
+ Background status checks are quiet and do not reload the capabilities catalog or disable otherwise usable controls on each poll. Catalog discovery occurs on workspace entry, explicit refresh and relevant mutation follow-up. Returning invalidates late reads from a previous visit without discarding in-flight mutation receipts. Projection reads follow semantic state changes, rather than a second polling loop. During same-branch refresh a labeled previous projection remains visible; stale/untrusted or changed session/branch data is invalidated.
74
+
75
+ Block numbers apply only to that preview. Native System sections and attributed user-fallback updates retain their actual roles; stopping a Capability does not erase its earlier updates. If no reliable match is visible, the inspector says so rather than guessing. This first iteration requires a trusted session and an active Preset; full event-history browsing is not included.
76
+
77
+ Inspection is a local read: it neither prepares/delivers a request nor changes tools or session history. It is not a captured provider payload or evidence of cache reuse; use Run diff/Payload and reported usage for those separate observations.
78
+
79
+ ### Tool selection and default tools editor
80
+
81
+ The **Policy** tab includes an opt-in default tools editor (`tools.initial?: string[]`):
82
+
83
+ - **Default tools picker:** A searchable, collapsible grouped tool picker organizes tools by SDK `sourceInfo` (Pi built-in tools, packages, and top-level entry points). Group checkboxes select/deselect the currently filtered group. **Enter tool names manually** preserves literal/offline-name entry, including in the Capability editor.
84
+ - **Exact names:** The picker saves concrete tool names. It does not persist package references or auto-install packages, and newly introduced package tools are not automatically added. Inactive registered tools are visible in the picker; unloaded tools are unavailable in the session, but manual saved references are preserved rather than discarded.
85
+ - **Omission vs. zero defaults:** Omitting custom defaults preserves legacy behavior (selective allow selects catalog matches; unrestricted/deny retains or filters the session baseline); setting an empty list (`[]`) sets zero active tools by default.
86
+ - **Authoritative ceiling:** The advanced literal and wildcard allow/deny policy is retained and acts as an authoritative ceiling; tools blocked by allow/deny cannot be selected as defaults.
87
+ - **Runtime behavior:** Configured defaults serve as the active base while the preset is active (not a one-time reset per turn). Disabling a capability recomputes defaults plus remaining active capabilities; disabling the preset restores the reconciled session baseline, while switching recomputes under the new Preset and remaining unbound capabilities.
88
+
89
+ ### Capability bindings tab
90
+
91
+ Capability bindings are managed in the peer **Capability bindings** tab:
92
+
93
+ - Each binding card shows one scoped capability reference and the model-activation authorization toggle. Binding ID belongs to its expandable details, not a permanently visible header field.
94
+ - **Advanced** reveals binding ID and overrides (content replace/append, tool add/remove). The live source-effective preview has its own disclosure.
95
+ - Tool overrides choose **Inherit** or **Custom override**, with integrated tool pickers matching the policy picker. A custom list may be empty: `[]` replaces that source add/remove list with zero entries, while inheritance omits the override. It does not clear every session tool or bypass other permission limits.
96
+ - Binding order is catalog order, not execution priority; the editor no longer offers up/down controls. Direct removal requires confirmation and stays in the Preset draft until Save. Expanded details follow their binding when another row is deleted.
97
+
98
+ ### Save behavior and execution impact
99
+
100
+ **Activate** uses the saved Preset. It is unavailable while the editor has unsaved changes; save explicitly first. Saving and activating are not a combined transaction.
101
+
102
+ - **Capabilities surface:** Saving a capability updates its library definition only and does not enable it in the current session.
103
+ - **Presets:** Saving an **inactive** Preset updates its configuration file without selecting or activating it. Crucially, saving the **currently active** Preset refreshes its policy immediately in the active session (synchronizing live tool and capability authorization policy), without replacing frozen active capability snapshots.
104
+
105
+ Existing IDs are immutable during edit. Use **More → Fork** to create a different ID without breaking Profile references or the active selection. The **New preset**, **Import**, and **Fork** dialogs collect name, ID, and target scope (default `Project`) before writing: `Global` targets the user-global `~/.pi/forge/prompt-stacks`, `Project` targets `.pi/forge/prompt-stacks`. Those paths keep their pre-0.5.3 names for compatibility.
106
+
107
+ The **New preset** dialog offers three initial templates:
108
+ - **Default Pi prompt:** Preserves Pi's built-in prompt layout with movable slots, tools, guidelines, docs, project context, skills, and chat history.
109
+ - **Empty:** Starts with no preset items or tool/skills policies (`items: []`). This does not mean zero context or tools; Pi automatically retains its base system prompt and history when no composed system prompt is provided.
110
+ - **Minimal Worker:** Mirrors the DeepSeek Harness minimal shape (`examples/minimal-prompt-stack.json`) with a single-line system persona ('You are a helpful software engineer assistant.'), chat history with summaries disabled, and restricted tools (`bash` and `edit` only).
111
+
112
+ Choosing a template automatically updates the suggested preset name as long as it has not been customized by the user. The **Import** and **Fork** dialogs continue to use the supplied or loaded source preset, not a creation template.
113
+
114
+ The dashed creation entry sits below the Preset list; **Add content / slot** below the item list lets you choose Block or Slot. Less-used capture, fork, import, export, and delete actions live under **More** so the Stack and Preview/Diff panes keep the available viewport. Preset rows show a `global` badge, and save/delete routes use `global:<id>` for exact global mutations. Legacy resources remain editable in place.
52
115
 
53
116
  Saves, imports, forks, and deletes reload Preset state into the current Pi session. When another surface changes a referenced Preset, returning to Profiles refreshes Profile resolution.
54
117
 
118
+ ### Compatibility
119
+
120
+ Presets configured with `tools.initial` require updated Forge. Older Forge versions may ignore `tools.initial` and revert to legacy selection behavior (selective allow selects catalog matches; unrestricted/deny retains or filters the session baseline) (not downgrade-compatible). These changes require Forge 0.5.5; the host requirement remains Pi `>=0.87.0 <0.88.0`.
121
+
55
122
  ## Agent-profile workspace
56
123
 
57
124
  The Profile list shows each Profile's ID, display metadata, model, thinking level, Preset, resolution state, auto-activation, last-applied provenance, and a `project`/`global` scope badge. Same-ID shadow pairs are marked `shadows global:<id>` or `shadowed by project:<id>`.
58
125
 
59
- Trusted projects can create Profiles in either scope: the scope selector beside **New profile** (default `project`) chooses whether to write the user-global `~/.pi/forge/agent-profiles` or the project `.pi/forge/agent-profiles`. Global Profiles can be edited, validated, saved, applied once, and deleted through explicit `global:<id>` routes; unqualified routes stay project-only. When editing a global Profile, the Preset dropdown offers only global Presets. Model choices come from Pi's model registry, thinking choices reflect model support, and Preset choices come from the shared repository. The editor rejects a second auto-activation Profile within the same scope.
126
+ Profile and Capability libraries place their dashed creation entry below the resource list. Creation editors put name first and choose scope in one place; they keep their full model/content fields and do not write incomplete resources merely to create a list row.
127
+
128
+ Trusted projects can create Profiles in either scope: the scope field in the creation editor (default `project`) chooses whether to write the user-global `~/.pi/forge/agent-profiles` or the project `.pi/forge/agent-profiles`. Global Profiles can be edited, validated, saved, applied once, and deleted through explicit `global:<id>` routes; unqualified routes stay project-only. When editing a global Profile, the Preset dropdown offers only global Presets. Model choices come from Pi's model registry, thinking choices reflect model support, and Preset choices come from the shared repository. The editor rejects a second auto-activation Profile within the same scope.
60
129
 
61
130
  The runtime/provenance card separates current runtime state, last-applied snapshot, source-definition changes, and field-level runtime drift.
62
131
 
@@ -0,0 +1,77 @@
1
+ # Active-state bus contract
2
+
3
+ [Documentation](../README.md)
4
+
5
+ Status: neutral, versioned. This is an optional appearance/session metadata seam consumed over the Pi event bus. It is not a package import entry point and has no Pi Pet-specific dependency. Consumers attach to `pi.events` (or `ctx.events`) and must treat every failure cosmetically.
6
+
7
+ ## Channels
8
+
9
+ - Snapshot/change: `@zihanw/pi-forge/active-state/v1`
10
+ - Snapshot request: `@zihanw/pi-forge/active-state/request/v1`
11
+
12
+ The production publisher is the main pi-forge extension. It registers the request listener on attach and removes it on disposal/session switch. No unbounded retries or timers are used.
13
+
14
+ ## Request
15
+
16
+ ```json
17
+ { "schemaVersion": "1", "sessionId": "raw Pi session-manager id" }
18
+ ```
19
+
20
+ `sessionId` is optional. When present, the publisher answers only if it matches the currently bound session; when omitted, it answers the current snapshot. A late subscriber can therefore request the current state after missing an earlier publish. Malformed or oversized requests are ignored.
21
+
22
+ ## Event
23
+
24
+ ```json
25
+ {
26
+ "schemaVersion": "1",
27
+ "sessionId": "raw Pi session-manager id",
28
+ "instanceId": "uuid",
29
+ "revision": 1,
30
+ "stackKey": "global:slug",
31
+ "profileKey": null,
32
+ "projectKey": "64 lower hex sha256"
33
+ }
34
+ ```
35
+
36
+ | Field | Meaning |
37
+ | --- | --- |
38
+ | `schemaVersion` | Always `"1"`. Unknown versions are rejected. |
39
+ | `sessionId` | Raw Pi session-manager ID for the bound session. |
40
+ | `instanceId` | Fresh bounded publisher instance (`^[A-Za-z0-9_-]{1,128}$`, UUID-compatible) per attach/epoch. A new instance retires the old one. |
41
+ | `revision` | Monotonic safe integer within one instance. |
42
+ | `stackKey` | Effective scoped prompt-stack key, or `null` when disabled/off. |
43
+ | `profileKey` | Compatible applied-profile provenance key, or `null` (see below). |
44
+ | `projectKey` | Opaque SHA-256 project identity, or `null`. Never a raw path. |
45
+
46
+ No other fields cross the channel. Prompt text, file paths, tokens, credentials, model configuration, tool policy, and character data are never included.
47
+
48
+ ## Identity and scope rules
49
+
50
+ - Scoped keys use `^(global|project):[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`. Invalid values become `null`; scoped IDs never cause filesystem access.
51
+ - `projectKey = sha256("pi-forge-project-v1\0" + hostname + "\0" + path.resolve(cwd))`, lower hex. Only the digest crosses the bus, so two working directories are isolated without revealing either path.
52
+ - Global resource bindings ignore `projectKey`.
53
+ - Project resource bindings **require** `projectKey`. A project-scoped `stackKey`/`profileKey` with a `null` project key fails closed. When the session `cwd` is empty or unresolvable, the publisher nulls project-scoped `stackKey`/`profileKey` rather than emit a snapshot its own validator rejects.
54
+ - `instanceId` uses the bounded UUID-compatible scalar grammar above; control characters, path separators, dots, and other punctuation are rejected. Real session IDs remain raw bounded strings (1..1024 chars).
55
+ - `profileKey` is derived from `lastAppliedProfile` only while its recorded `snapshot.promptStack` equals the current effective scoped stack (`null` equals disabled). It is cosmetic compatible-provenance identity, not a claim that the current model, thinking level, or tools still match the profile. Manual stack drift clears an unrelated profile binding.
56
+
57
+ ## Publishing lifecycle
58
+
59
+ The publisher emits on: initial session bind/restore, session start, tree navigation, compaction, explicit preset changes (`enable`, `none`/off), profile apply/update, web-editor updates, and disposal. Workspace changes publish without any provider/model call.
60
+
61
+ Rules:
62
+
63
+ 1. `instanceId` is per attach. Switching sessions emits a final cleared snapshot for the old session, then a new instance for the new session.
64
+ 2. `revision` is monotonic within an instance. At the `Number.MAX_SAFE_INTEGER` boundary the publisher rotates to a fresh instance epoch instead of wrapping the same instance back to `1`. A snapshot request repeats the current revision rather than advancing it.
65
+ 3. Disposal emits a cleared snapshot (`stackKey`/`profileKey`/`projectKey` as available, stack/profile `null`) and unregisters the request listener.
66
+ 4. Session start/tree/compact suspend publication and request replies across `restoreBranchScopedRuntime`, then bind and publish one latest coherent snapshot. A failed restore detaches/clears instead of leaving the old session bound to the new workspace.
67
+ 5. Optional listeners never alter Forge compilation, tool policy, or provider behavior; a throwing reader/listener/transport is isolated. The shutdown path attempts every teardown step even if an optional transport rejects `emit`/`on`/`unsubscribe`.
68
+ 6. The same live instance/epoch is retained across a same-session compaction; cosmetic state is not cleared/recreated needlessly.
69
+
70
+ ## Consumer expectations (normative)
71
+
72
+ - Accept only events whose `sessionId` matches the consumer's current Pi canonical session identity.
73
+ - Reject stale/duplicate revisions and snapshots from retired instances with bounded tracking; never retry unboundedly.
74
+ - Fail closed on malformed or oversized payloads, wrong schema versions, or project-scoped bindings without a `projectKey`.
75
+ - Clear context on an explicit cleared snapshot; do not treat bus traffic as an implicit identity/status/auth/Team change.
76
+
77
+ See [public API policy](public-api.md) for how this bus contract relates to the package import surfaces.