@plurnk/plurnk-service 2.0.0 → 3.0.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 (251) hide show
  1. package/.env.defaults +11 -15
  2. package/INSTALL.md +3 -2
  3. package/README.md +1 -2
  4. package/SPEC.md +329 -320
  5. package/dist/build-info.json +1 -1
  6. package/dist/content/edit-receipt.d.ts +1 -1
  7. package/dist/content/edit-receipt.d.ts.map +1 -1
  8. package/dist/content/edit-receipt.js +1 -1
  9. package/dist/content/edit-receipt.js.map +1 -1
  10. package/dist/content/read-projector.d.ts.map +1 -1
  11. package/dist/content/read-projector.js +3 -12
  12. package/dist/content/read-projector.js.map +1 -1
  13. package/dist/core/AdministrativeLoop.d.ts +1 -1
  14. package/dist/core/AdministrativeLoop.d.ts.map +1 -1
  15. package/dist/core/AdministrativeLoop.js +2 -6
  16. package/dist/core/AdministrativeLoop.js.map +1 -1
  17. package/dist/core/BudgetReadout.d.ts +1 -0
  18. package/dist/core/BudgetReadout.d.ts.map +1 -1
  19. package/dist/core/BudgetReadout.js +12 -0
  20. package/dist/core/BudgetReadout.js.map +1 -1
  21. package/dist/core/CapabilityPolicies.d.ts +2 -4
  22. package/dist/core/CapabilityPolicies.d.ts.map +1 -1
  23. package/dist/core/CapabilityPolicies.js +3 -23
  24. package/dist/core/CapabilityPolicies.js.map +1 -1
  25. package/dist/core/CapabilityResolver.d.ts +9 -2
  26. package/dist/core/CapabilityResolver.d.ts.map +1 -1
  27. package/dist/core/CapabilityResolver.js +34 -5
  28. package/dist/core/CapabilityResolver.js.map +1 -1
  29. package/dist/core/ChannelWrite.d.ts +0 -2
  30. package/dist/core/ChannelWrite.d.ts.map +1 -1
  31. package/dist/core/ChannelWrite.js.map +1 -1
  32. package/dist/core/ClientInteractions.d.ts +3 -2
  33. package/dist/core/ClientInteractions.d.ts.map +1 -1
  34. package/dist/core/ClientInteractions.js +21 -12
  35. package/dist/core/ClientInteractions.js.map +1 -1
  36. package/dist/core/ClientInteractions.sql +8 -2
  37. package/dist/core/CoreSchemeServices.d.ts +1 -0
  38. package/dist/core/CoreSchemeServices.d.ts.map +1 -1
  39. package/dist/core/CoreSchemeServices.js +1 -0
  40. package/dist/core/CoreSchemeServices.js.map +1 -1
  41. package/dist/core/DataStatementRunner.js +1 -1
  42. package/dist/core/DataStatementRunner.js.map +1 -1
  43. package/dist/core/Dispatcher.d.ts +1 -0
  44. package/dist/core/Dispatcher.d.ts.map +1 -1
  45. package/dist/core/Dispatcher.js +12 -13
  46. package/dist/core/Dispatcher.js.map +1 -1
  47. package/dist/core/Engine.d.ts +1 -1
  48. package/dist/core/Engine.d.ts.map +1 -1
  49. package/dist/core/Engine.js +3 -2
  50. package/dist/core/Engine.js.map +1 -1
  51. package/dist/core/EnvFlags.d.ts.map +1 -1
  52. package/dist/core/EnvFlags.js +4 -3
  53. package/dist/core/EnvFlags.js.map +1 -1
  54. package/dist/core/FabricatedLog.js +1 -1
  55. package/dist/core/FabricatedLog.js.map +1 -1
  56. package/dist/core/HostPaths.d.ts +0 -1
  57. package/dist/core/HostPaths.d.ts.map +1 -1
  58. package/dist/core/HostPaths.js +0 -2
  59. package/dist/core/HostPaths.js.map +1 -1
  60. package/dist/core/KillHandler.js +1 -1
  61. package/dist/core/KillHandler.js.map +1 -1
  62. package/dist/core/LogVisibility.d.ts.map +1 -1
  63. package/dist/core/LogVisibility.js +8 -17
  64. package/dist/core/LogVisibility.js.map +1 -1
  65. package/dist/core/LoopDriver.d.ts.map +1 -1
  66. package/dist/core/LoopDriver.js +4 -6
  67. package/dist/core/LoopDriver.js.map +1 -1
  68. package/dist/core/LoopLifecycle.d.ts.map +1 -1
  69. package/dist/core/LoopLifecycle.js +4 -9
  70. package/dist/core/LoopLifecycle.js.map +1 -1
  71. package/dist/core/LoopLifecycle.sql +6 -6
  72. package/dist/core/OperatorConfig.d.ts +1 -0
  73. package/dist/core/OperatorConfig.d.ts.map +1 -1
  74. package/dist/core/OperatorConfig.js +10 -2
  75. package/dist/core/OperatorConfig.js.map +1 -1
  76. package/dist/core/PacketBuilder.d.ts +5 -7
  77. package/dist/core/PacketBuilder.d.ts.map +1 -1
  78. package/dist/core/PacketBuilder.js +48 -118
  79. package/dist/core/PacketBuilder.js.map +1 -1
  80. package/dist/core/PacketBuilder.sql +15 -0
  81. package/dist/core/ProposalLifecycle.d.ts +1 -1
  82. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  83. package/dist/core/ProposalLifecycle.js +18 -20
  84. package/dist/core/ProposalLifecycle.js.map +1 -1
  85. package/dist/core/ProposalPolicies.d.ts +6 -0
  86. package/dist/core/ProposalPolicies.d.ts.map +1 -0
  87. package/dist/core/ProposalPolicies.js +28 -0
  88. package/dist/core/ProposalPolicies.js.map +1 -0
  89. package/dist/core/ProviderInstantiate.d.ts.map +1 -1
  90. package/dist/core/ProviderInstantiate.js +3 -4
  91. package/dist/core/ProviderInstantiate.js.map +1 -1
  92. package/dist/core/ResourceSelector.js +2 -2
  93. package/dist/core/ResourceSelector.js.map +1 -1
  94. package/dist/core/ResourceTransfers.js +3 -3
  95. package/dist/core/ResourceTransfers.js.map +1 -1
  96. package/dist/core/RuntimeWorker.sql +1 -2
  97. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  98. package/dist/core/SchemeRegistry.js +4 -5
  99. package/dist/core/SchemeRegistry.js.map +1 -1
  100. package/dist/core/ToolInputSchema.d.ts +1 -0
  101. package/dist/core/ToolInputSchema.d.ts.map +1 -1
  102. package/dist/core/ToolInputSchema.js +8 -2
  103. package/dist/core/ToolInputSchema.js.map +1 -1
  104. package/dist/core/ToolResources.d.ts +1 -0
  105. package/dist/core/ToolResources.d.ts.map +1 -1
  106. package/dist/core/ToolResources.js +7 -1
  107. package/dist/core/ToolResources.js.map +1 -1
  108. package/dist/core/TurnDispositionHandler.d.ts.map +1 -1
  109. package/dist/core/TurnDispositionHandler.js +1 -7
  110. package/dist/core/TurnDispositionHandler.js.map +1 -1
  111. package/dist/core/TurnMaterialization.js +1 -1
  112. package/dist/core/TurnMaterialization.js.map +1 -1
  113. package/dist/core/TurnRunner.d.ts.map +1 -1
  114. package/dist/core/TurnRunner.js +74 -57
  115. package/dist/core/TurnRunner.js.map +1 -1
  116. package/dist/core/WorkerControlHandler.d.ts.map +1 -1
  117. package/dist/core/WorkerControlHandler.js +1 -8
  118. package/dist/core/WorkerControlHandler.js.map +1 -1
  119. package/dist/core/WorkerName.d.ts +1 -0
  120. package/dist/core/WorkerName.d.ts.map +1 -1
  121. package/dist/core/WorkerName.js +2 -0
  122. package/dist/core/WorkerName.js.map +1 -1
  123. package/dist/core/WorkerName.sql +2 -2
  124. package/dist/core/WorkerOwners.d.ts +13 -0
  125. package/dist/core/WorkerOwners.d.ts.map +1 -0
  126. package/dist/core/WorkerOwners.js +44 -0
  127. package/dist/core/WorkerOwners.js.map +1 -0
  128. package/dist/core/WorkerOwners.sql +55 -0
  129. package/dist/core/env-catalog.d.ts +3 -0
  130. package/dist/core/env-catalog.d.ts.map +1 -1
  131. package/dist/core/env-catalog.js +44 -9
  132. package/dist/core/env-catalog.js.map +1 -1
  133. package/dist/core/fork.sql +3 -4
  134. package/dist/core/packet-inject.js +0 -2
  135. package/dist/core/packet-inject.js.map +1 -1
  136. package/dist/core/packet-wire.d.ts +2 -10
  137. package/dist/core/packet-wire.d.ts.map +1 -1
  138. package/dist/core/packet-wire.js +31 -114
  139. package/dist/core/packet-wire.js.map +1 -1
  140. package/dist/core/plurnk-uri.d.ts +1 -1
  141. package/dist/core/plurnk-uri.d.ts.map +1 -1
  142. package/dist/core/plurnk-uri.js +4 -4
  143. package/dist/core/plurnk-uri.js.map +1 -1
  144. package/dist/core/scheme-types.d.ts +2 -1
  145. package/dist/core/scheme-types.d.ts.map +1 -1
  146. package/dist/core/types.d.ts +0 -1
  147. package/dist/core/types.d.ts.map +1 -1
  148. package/dist/core/unregistered-scheme.d.ts +1 -3
  149. package/dist/core/unregistered-scheme.d.ts.map +1 -1
  150. package/dist/core/unregistered-scheme.js +3 -11
  151. package/dist/core/unregistered-scheme.js.map +1 -1
  152. package/dist/evidence/EvidenceReader.d.ts.map +1 -1
  153. package/dist/evidence/EvidenceReader.js +4 -1
  154. package/dist/evidence/EvidenceReader.js.map +1 -1
  155. package/dist/evidence/digest.sql +1 -2
  156. package/dist/schemes/EffectPolicy.d.ts.map +1 -1
  157. package/dist/schemes/EffectPolicy.js +1 -10
  158. package/dist/schemes/EffectPolicy.js.map +1 -1
  159. package/dist/schemes/Exec.d.ts.map +1 -1
  160. package/dist/schemes/Exec.js +16 -23
  161. package/dist/schemes/Exec.js.map +1 -1
  162. package/dist/schemes/File.d.ts.map +1 -1
  163. package/dist/schemes/File.js +10 -16
  164. package/dist/schemes/File.js.map +1 -1
  165. package/dist/schemes/QuestionTool.d.ts.map +1 -1
  166. package/dist/schemes/QuestionTool.js +1 -3
  167. package/dist/schemes/QuestionTool.js.map +1 -1
  168. package/dist/schemes/Worker.d.ts.map +1 -1
  169. package/dist/schemes/Worker.js +6 -8
  170. package/dist/schemes/Worker.js.map +1 -1
  171. package/dist/schemes/_entry-fts.d.ts.map +1 -1
  172. package/dist/schemes/_entry-fts.js +3 -9
  173. package/dist/schemes/_entry-fts.js.map +1 -1
  174. package/dist/schemes/_entry-ops.js +1 -1
  175. package/dist/schemes/_entry-ops.js.map +1 -1
  176. package/dist/server/ConfigurationDiagnostics.d.ts +1 -0
  177. package/dist/server/ConfigurationDiagnostics.d.ts.map +1 -1
  178. package/dist/server/ConfigurationDiagnostics.js +9 -0
  179. package/dist/server/ConfigurationDiagnostics.js.map +1 -1
  180. package/dist/server/Daemon.d.ts +12 -8
  181. package/dist/server/Daemon.d.ts.map +1 -1
  182. package/dist/server/Daemon.js +51 -52
  183. package/dist/server/Daemon.js.map +1 -1
  184. package/dist/server/DrainSupervisor.d.ts +1 -4
  185. package/dist/server/DrainSupervisor.d.ts.map +1 -1
  186. package/dist/server/DrainSupervisor.js +1 -11
  187. package/dist/server/DrainSupervisor.js.map +1 -1
  188. package/dist/server/EnvFunctionality.d.ts +2 -4
  189. package/dist/server/EnvFunctionality.d.ts.map +1 -1
  190. package/dist/server/EnvFunctionality.js +3 -5
  191. package/dist/server/EnvFunctionality.js.map +1 -1
  192. package/dist/server/Functionality.d.ts.map +1 -1
  193. package/dist/server/Functionality.js +42 -13
  194. package/dist/server/Functionality.js.map +1 -1
  195. package/dist/server/FunctionalityManager.d.ts +3 -4
  196. package/dist/server/FunctionalityManager.d.ts.map +1 -1
  197. package/dist/server/FunctionalityManager.js +17 -9
  198. package/dist/server/FunctionalityManager.js.map +1 -1
  199. package/dist/server/HttpListener.d.ts.map +1 -1
  200. package/dist/server/HttpListener.js +9 -3
  201. package/dist/server/HttpListener.js.map +1 -1
  202. package/dist/server/MembersFunctionality.d.ts +2 -5
  203. package/dist/server/MembersFunctionality.d.ts.map +1 -1
  204. package/dist/server/MembersFunctionality.js +5 -3
  205. package/dist/server/MembersFunctionality.js.map +1 -1
  206. package/dist/server/WorkspaceStorage.d.ts +0 -1
  207. package/dist/server/WorkspaceStorage.d.ts.map +1 -1
  208. package/dist/server/WorkspaceStorage.js +1 -24
  209. package/dist/server/WorkspaceStorage.js.map +1 -1
  210. package/dist/server/client-input.d.ts +1 -2
  211. package/dist/server/client-input.d.ts.map +1 -1
  212. package/dist/server/client-input.js +2 -13
  213. package/dist/server/client-input.js.map +1 -1
  214. package/dist/server/dispatch-as-plurnk.js +1 -1
  215. package/dist/server/dispatch-as-plurnk.js.map +1 -1
  216. package/dist/server/drain.sql +6 -6
  217. package/dist/server/envelope.d.ts +2 -1
  218. package/dist/server/envelope.d.ts.map +1 -1
  219. package/dist/server/envelope.js +7 -17
  220. package/dist/server/envelope.js.map +1 -1
  221. package/dist/server/envelope.sql +5 -5
  222. package/dist/server/seam-proposal-list.sql +6 -4
  223. package/dist/server/workspace-storage.sql +0 -5
  224. package/dist/service.d.ts.map +1 -1
  225. package/dist/service.js +23 -33
  226. package/dist/service.js.map +1 -1
  227. package/migrations/003_loops.sql +2 -2
  228. package/migrations/006_log.sql +1 -1
  229. package/migrations/016_selection_statements.sql +1 -0
  230. package/migrations/017_skills_module.sql +1 -0
  231. package/migrations/018_worker_owners.sql +102 -0
  232. package/migrations/019_interactive_owners.sql +6 -0
  233. package/migrations/020_log_projection.sql +143 -0
  234. package/package.json +37 -41
  235. package/dist/core/LegacyHome.d.ts +0 -10
  236. package/dist/core/LegacyHome.d.ts.map +0 -1
  237. package/dist/core/LegacyHome.js +0 -170
  238. package/dist/core/LegacyHome.js.map +0 -1
  239. package/dist/core/LoopPolicies.d.ts +0 -7
  240. package/dist/core/LoopPolicies.d.ts.map +0 -1
  241. package/dist/core/LoopPolicies.js +0 -42
  242. package/dist/core/LoopPolicies.js.map +0 -1
  243. package/dist/core/LoopPolicyReader.d.ts +0 -7
  244. package/dist/core/LoopPolicyReader.d.ts.map +0 -1
  245. package/dist/core/LoopPolicyReader.js +0 -33
  246. package/dist/core/LoopPolicyReader.js.map +0 -1
  247. package/dist/core/LoopPolicyReader.sql +0 -6
  248. package/dist/index.d.ts +0 -14
  249. package/dist/index.d.ts.map +0 -1
  250. package/dist/index.js +0 -21
  251. package/dist/index.js.map +0 -1
package/SPEC.md CHANGED
@@ -63,8 +63,6 @@ flowchart LR
63
63
  | **dispatch** | Core | Routing a statement to its scheme's op handler. |
64
64
  | **AG-UI Run** | AG-UI protocol | A client request/stream envelope identified by the client's `runId`. A message or resume AG-UI Run binds to one core loop; a management-action AG-UI Run may complete without creating a core loop. |
65
65
  | **AG-UI thread** | AG-UI protocol | Conversation identity. Within an explicitly selected workspace, `threadId` resolves to one conversation worker. |
66
- | **`--run`** | Client compatibility | A compatibility-sensitive client spelling, not an internal entity. |
67
- | **session** | Retired/unqualified | Not a PLURNK lifecycle noun. Use the actual core noun; a third-party standard may use only its explicitly qualified protocol term. <!-- lexicon-allow: this row defines the retired noun --> |
68
66
 
69
67
  ### Storage terms
70
68
 
@@ -105,8 +103,8 @@ Independent axes on entries and channels. Confusion across them is a recurring s
105
103
  | **BARE inference** | One isolated child-provider model call whose response becomes an ordinary BARE log result. It has no worker, packet, tools, output grammar, or persistent child state ({§bare-inference}). |
106
104
  | **cycle** | Repeated operational inputs and observed results under {§engine-cycle-evidence}. |
107
105
  | **capability policy** | A purely subtractive `only`/`deny` selector layer over routed operation demands. Service and workspace layers compose without granting authority. |
108
- | **loop policy** | One immutable `review`, `accept`, or `reject` proposal disposition. |
109
- | **proposal** | A deferred side-effecting action. State machine: `proposed → resolved` (accept), `→ failed` (reject), or `→ cancelled` (cancel). Its core-owned disposition says whether the client or loop owns resolution ({§proposal-disposition}). |
106
+ | **approval owner** | One durable, workspace-scoped recipient for a worker's approval requests ({§worker-ownership}). |
107
+ | **proposal** | A deferred side-effecting action. State machine: `proposed → resolved` (accept), `→ failed` (reject), or `→ cancelled` (cancel). Its core-owned disposition selects owner review or automatic settlement ({§proposal-disposition}). |
110
108
  | **resolution** | A client decision delivered through a standard resume entry. Proposal resolutions accept, reject, or cancel ({§methods-proposal-resolve}); client-interaction resolutions return a payload or cancel ({§methods-client-interaction-resolve}, {§agui-proposal-resolve}). |
111
109
 
112
110
  ### §packet-terms Packet terms
@@ -169,19 +167,14 @@ none, so its daemon leaves out AG-UI ({§module-http-mounts}).
169
167
 
170
168
  ### §service-package-exports Package export surface
171
169
 
172
- | Export path | Current contract |
173
- |---------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
174
- | `@plurnk/plurnk-service` | Legacy internal exports listed below. It gains no new APIs and is not the client boundary. Extracted capabilities belong to their owning packages, without compatibility aliases. |
175
- | `@plurnk/plurnk-service/evidence` | Canonical evidence reader consumed by the separate report package ({§digest-evidence-reader}). |
176
- | `@plurnk/plurnk-service/package.json` | Supported package metadata surface. |
170
+ | Export path | Current contract |
171
+ |---------------------------------------|------------------|
172
+ | `@plurnk/plurnk-service/launch` | The daemon launcher ({§daemon-launch}). |
173
+ | `@plurnk/plurnk-service/evidence` | Canonical evidence reader consumed by the separate report package ({§digest-evidence-reader}). |
174
+ | `@plurnk/plurnk-service/package.json` | Supported package metadata surface. |
177
175
 
178
- | Root exports | Names |
179
- |--------------|-------|
180
- | Runtime | `Daemon`, `Engine`, `EnvFlags`, `Exec`, `File`, `Log`, `Mimetypes`, `Mock`, `Paths`, `SchemeRegistry` |
181
- | Types | `ChatMessage`, `EditResult`, `FlagDescriptor`, `MockAssistant`, `MockResponse`, `OpenFoldResult`, `ReadResult` |
182
-
183
- New clients use AG-UI. A new library contract belongs in its owning package or
184
- an explicitly specified subpath, not in the legacy root barrel.
176
+ These are the package's only export paths. Clients use AG-UI; a new library contract belongs in its
177
+ owning package or an explicitly specified subpath.
185
178
 
186
179
  ### §startup-admission Startup admission
187
180
 
@@ -212,12 +205,14 @@ listener on `PLURNK_HOST:PLURNK_PORT` and offers it to every exterior adapter as
212
205
  ({§application-port}). A prefix is an absolute pathname. Each request goes to the
213
206
  longest mounted prefix; a prefix claims itself and the subtree beneath it, never
214
207
  a longer sibling name; `/` is the root and receives whatever nothing more
215
- specific claimed. Until daemon activation admits it the listener answers `503
208
+ specific claimed when a module mounts it. An admitted listener returns an HTTP
209
+ `404 route-not-found` Problem for any path without a mounted handler; a root mount
210
+ is not required. Until daemon activation admits it the listener answers `503
216
211
  service-starting` to every request: the service has not admitted its client
217
212
  interface. Adapters claim their prefixes before setup and mount them at
218
213
  `start()`, after durable lifecycle recovery ({§module-http-mounts}), and
219
214
  none opens a socket of its own. The standards
220
- address by URL, never by port (#641): AG-UI mounts `/` and `/agui`, A2A the
215
+ address by URL, never by port (#641): AG-UI mounts only `/agui`, A2A the
221
216
  well-known card and its endpoint path, on the same address.
222
217
 
223
218
  §startup-admission-order After listener ownership, database admission completes
@@ -230,7 +225,7 @@ preserving the originating failure: daemon, observability, database, listener.
230
225
 
231
226
  §startup-readiness-line **Readiness is one stdout line.** After the client interface is mounted
232
227
  the service prints exactly one line, `plurnk-service agui=<url> db=<json string> route=<json string>`:
233
- the URL brackets an IPv6 host, and the database path and the route (the active model route or
228
+ the URL includes `/agui` and brackets an IPv6 host, and the database path and the route (the active model route or
234
229
  `no model`, or `invalid model configuration`) are JSON strings, so a path or route containing spaces is exact and a consumer parses
235
230
  the URL as a URL and the strings as JSON; nothing else the service prints on stdout before it has
236
231
  that prefix. Before the line the listener answers `503 service-starting`; after it,
@@ -256,6 +251,49 @@ launchers consume the same contract by reading the line themselves.
256
251
 
257
252
  ## §actor-boundary Workers and workspace boundaries
258
253
 
254
+ ### §worker-ownership Approval ownership
255
+
256
+ Every worker has one workspace-scoped approval owner. Ownership is independent of
257
+ origin, parentage, messages and connection presence. It controls approval routing,
258
+ not access to workspace resources ({§actor-boundary-no-mutex}).
259
+
260
+ | Event | Ownership |
261
+ | --- | --- |
262
+ | Root creation | Explicit owner, otherwise the runtime `_plurnk`. |
263
+ | WORK, FORK, or a fresh child | Inherit the parent's owner. |
264
+ | SEND, schedule occurrence, or other incoming message | Preserve the recipient's owner. |
265
+ | Explicit client control attachment | Claim a runtime-owned conversation and its runtime-owned descendants; never claim the runtime actor or replace another owner. |
266
+ | Observation or disconnect | No change. |
267
+
268
+ An owner declares its capability set: the client tool names it implements, and whether it
269
+ is interactive, that is, whether a person attends it. The declaration remains durable while it
270
+ is disconnected. A proposal requiring review waits for that owner; unsupported review fails at
271
+ the request boundary. Approval needs no person: a client that accepts automatically still
272
+ implements `request_approval`. Clarification and a park only a person can end need an
273
+ interactive owner. `_plurnk` implements no client tools and is never interactive. Server
274
+ approval policy may settle a proposal automatically; it cannot fabricate an interaction answer.
275
+ Kernel maintenance remains kernel work ({§actor-boundary-self-hosting}).
276
+
277
+ §worker-owner-creation Ownership, inheritance, and runtime-owner creation are
278
+ database invariants. A newly created worker cannot be left without an owner;
279
+ an unknown explicit owner is an error. Existing workers migrate to runtime ownership.
280
+
281
+ §worker-owner-resolution A proposal names its worker's owner and one disposition:
282
+ `review`, `accept`, or `reject`. Review routes to that owner, not to the sender,
283
+ the current observer, or whichever ancestor has a live Run. A decision is accepted
284
+ only for that owner and the still-pending gate. Disconnect does not substitute a
285
+ different owner. Capability admission remains independent and precedes approval.
286
+
287
+ §client-interaction-routing Clarification goes to the worker's owner, like approval, but
288
+ only a person can answer it. The owner receives it while it is interactive and declares
289
+ the requested tool; otherwise the request fails at once as unsupported and never parks.
290
+ The pending interaction records that recipient and accepts a response only from it,
291
+ through the ordinary response-schema validation and settlement path. A protocol caller,
292
+ such as an A2A client, converses by message and is never a recipient
293
+ ({§a2a-worker-ownership}).
294
+
295
+ ### Worker boundaries
296
+
259
297
  ```mermaid
260
298
  flowchart LR
261
299
  child["Child worker"] -->|"durable activity<br/>environment door"| parent["Direct parent log"]
@@ -281,7 +319,8 @@ Functionality ({§module-workspace-capabilities}); runtimes, runtime
281
319
  schemes, and tools resolve by `workspaceId`. Operations journal in the
282
320
  submitting actor's `workerId` ({§connection-lifecycle}), which must belong to
283
321
  the workspace. Runtime and policy resolution require no second worker identity.
284
- Attachment remains many-to-one, non-owning, and unrelated to parentage.
322
+ Attachment remains many-to-one and unrelated to parentage. Explicit control
323
+ attachment may claim approval ownership under {§worker-ownership}.
285
324
 
286
325
  §actor-boundary-two-doors **Cross-worker arrival is limited to two doors.**
287
326
  An explicit READ is not an arrival: the reading worker deliberately addresses a
@@ -495,7 +534,7 @@ continues to decompose other authorities without treating them as mintable.
495
534
 
496
535
  §worker-read-scope **Scratch is workspace-readable.** Any actor reads and searches any named or shared scratch address. Parentage and writer identity do not change resolution. Scratch namespaces do not require a namesake Worker. A pathless actor address requires a named Worker; an unknown actor returns 404.
497
536
 
498
- §worker-write-scoping **Scratch is workspace-writable.** All workspace actors may EDIT, COPY, MOVE, or KILL entries in any named or shared scratch namespace, including generated documents. There is no creator-only, self-only, ancestor-only, or runtime-only grant. Workspace admission remains uniform. Intrinsically immutable evidence in other schemes retains its own contract ({§scheme-entry-matrix}); operation provenance and delegation lifecycle do not grant or restrict scratch access.
537
+ §worker-write-scoping **Scratch is workspace-writable.** All workspace actors may EDIT, COPY, MOVE, or KILL entries in any named or shared scratch namespace, including generated documents. Workspace admission remains uniform. Intrinsically immutable evidence in other schemes retains its own contract ({§scheme-entry-matrix}); operation provenance and delegation lifecycle do not grant or restrict scratch access.
499
538
 
500
539
  §worker-generated-subtree **Generated documents share `worker:///_plurnk/`.** Project instructions (`AGENTS.md` and subtree-scoped `instructions/**`), scheme/runtime references (`plurnk/**`), tool details (`tools/**`), and family catalogs are workspace resources. Agent Skills retain their own trees at `skill://<name>/` ({§skills-resources}).
501
540
 
@@ -550,7 +589,6 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
550
589
  own work is a fresh loop, so an inherited mid-flight loop never makes the
551
590
  branch look forever-live to the {§send-premature-terminate} gate.
552
591
  - §worker-scheme-fork-scratch **Forked scratch.** Named scratch and evidence are copied under the new name through {§machine-processes-entry-inheritance}. Parent and branch can edit either scratch namespace; their copies diverge independently.
553
- - §worker-delegation-inherits-policy **Fresh delegated loops inherit the sender's policy.** WORK, FORK, and SEND to an idle Worker carry the sender's complete loop policy, disposition and attendance alike. SEND into an active or parked loop leaves its immutable policy untouched. All workers share live workspace capability policy; delegation creates no capability snapshot or bound.
554
592
  - §worker-lifecycle-wake-requeue-not-terminal **A wake re-queue is not a terminal.** A conclusion-wake resumes a 202-blocked loop by re-queueing it (202 → 100); when that lands while the loop's own live drain is between turns, the drain **re-claims and continues** (atomic 100 → 102; the injected prompt is already the next turn). The internal re-queue is never reported as an outward terminal.
555
593
 
556
594
  - §worker-scheme-collect **Collect** — each concluded child loop reaches its direct
@@ -594,7 +632,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
594
632
  starts there too, then resets atomically when any published channel's content
595
633
  changes, including replacement or clearing. Identical writes, empty appends,
596
634
  READs, publication acknowledgements, metadata, and channel-state changes do
597
- not reset it. The clock is durable; an upgraded stream lacking that evidence
635
+ not reset it. The clock is durable; a stream lacking that evidence
598
636
  reports `output timing unknown` until its next content change, never a guessed
599
637
  timestamp. These are observations, not new timeouts or wake conditions
600
638
  ({§exec-stream}). It is orienting state, never advice: the model sees its live
@@ -855,7 +893,6 @@ cancels work or fabricates a result.
855
893
  With live work, each WAIT receipt exposes `waitSeconds`, the maximum accepted
856
894
  for that operation. It is not elapsed time or a promised sleep: an ordinary wake
857
895
  or a shorter sibling WAIT can resume the loop sooner. An idle WAIT has no bound.
858
- Historical unbounded receipts do not acquire one during projection.
859
896
 
860
897
  | Boundary | Outcome |
861
898
  |---|---|
@@ -1072,7 +1109,7 @@ boundary.
1072
1109
  | Independent arrival after cancellation | Admit a new loop above the cutoff using the ordinary worker policy. |
1073
1110
  | Slow stream teardown after new admission | Reap only subscription identities captured by cancellation; late old-scope spawns follow {§worker-lifecycle-exec-epoch-bound}. |
1074
1111
 
1075
- - §worker-lifecycle-wake-liveness **A stream conclusion always reaches its worker.** The stream first persists its terminal state. A worker **blocked on a 202 wait** for that stream ({§wait-obligation-matrix}) then **awakens that loop in place** — the blocked loop *is* the continuation, so there is no fresh loop and no summary-as-prompt fiction. An already-active worker needs no injected prompt or second wake because its next packet reads the durable terminal state. A concluded worker receives no synthetic loop from ambient stream closure. The result remains available in the stream's own state under every case.
1112
+ - §worker-lifecycle-wake-liveness **A stream conclusion always reaches its worker.** The stream first persists its terminal state. A worker **blocked on a 202 wait** for that stream ({§wait-obligation-matrix}) then **awakens that loop in place** — the blocked loop *is* the continuation. An already-active worker's next packet reads the durable terminal state. Ambient stream closure starts no loop for a concluded worker. The result remains available in the stream's own state under every case.
1076
1113
  - §worker-lifecycle-child-wake **Each child task completion notifies its parent.** Terminal-task publication, including failure and cancellation of a parked task, notifies the direct parent without injecting a prompt. Other unfinished tasks or streams in that child remain independent obligations; they cannot suppress notification. The parent's eligible waits requeue in place under {§loop-wake-identity} and the bounded {§worker-optimistic-settlement} opportunity. Durable revisioning covers completion-before-park and restart; drain teardown and whole-worker quiescence are not completion identities.
1077
1114
  - §worker-optimistic-settlement **Asynchronous settlement receives one bounded worker-local opportunity before model dispatch.** An initiating turn lets only the streams it started settle before program completion; separately, a stream conclusion, direct-child conclusion or addressed reply persists and publishes immediately but holds eligible parked loops' `202→100` requeues while another stream or direct child remains live. Both use `PLURNK_SERVICE_OPTIMISTIC_WAIT_MS`, shipped at five seconds; zero disables the opportunity. The wake hold ends as soon as no sibling obligation remains, never extends its original deadline, and coalesces arrivals within that window into at most one requeue per eligible loop. With no sibling obligation the wake is immediate; at the deadline, surviving work follows the ordinary monitored lifecycle. An arrival after provider dispatch begins retains its next wake, while poll, new-request and operator wakes never open this hold. Only packet/provider dispatch waits: durable state, client events, cancellation and the replying program do not. One redaction-safe span records elapsed time, quiescence versus deadline, and arrival count without entering the packet.
1078
1115
  - §worker-lifecycle-idle-is-concluded **Idle is not completion.** An empty WAIT continues; an eligible parameterless KILL with observed arrivals and results and no held work concludes under {§wait-obligation-matrix}, with or without an answer body. A concluded worker retains durable history; a later addressed arrival starts a new loop.
@@ -1198,9 +1235,9 @@ retains only the current provider state; the next completed exchange notices
1198
1235
  `provider_recovered`. Recovery is bounded by `PLURNK_SERVICE_PROVIDER_RECOVERY`; when it
1199
1236
  is spent the turn completes as `202` and the loop parks exactly like a
1200
1237
  WAIT ({§worker-lifecycle-wake-requeue-not-terminal}), resuming on the
1201
- next prompt or wake with its log intact — **unless the run is unattended
1202
- ({§loop-attendance}), in which case the loop concludes on the provider's exact failure
1203
- instead, because parking stops the execution clock and no wake would ever arrive.** Only a client cancel, the execution allowance
1238
+ next prompt or wake with its log intact — **unless the worker's owner is not interactive
1239
+ ({§worker-ownership}); then the loop concludes on the provider's exact failure because no
1240
+ person attends to wake that park.** Only a client cancel, the execution allowance
1204
1241
  ({§operator-config-loop-timeout}), or a non-recoverable provider Problem (refusal,
1205
1242
  authorization, quota, an invalid response) settles a loop on a provider failure.
1206
1243
 
@@ -1317,7 +1354,7 @@ The parser owns its boundaries; core admits determinate work and exposes its fai
1317
1354
  | Lost boundary before any closed operation | Reject the attempt; outside text is no substitute for a closed operational statement. An admitted reasoning operation is one ({§reasoning-operations}). |
1318
1355
  | Outside text carrying a log-entry heading, other than an emission row's | Reject the attempt ({§fabricated-log-entry}). |
1319
1356
 
1320
- §fabricated-log-entry **Only the harness writes the log.** A line of outside response text that begins with a log-entry heading, `### log:///<loop>/<turn>/<sequence>/` ({§log-wire-format}), is the model continuing the packet's transcript instead of answering it: it writes the receipts it expects and then acts on them. The attempt is rejected under {§invalid-emission-attempts}, so neither that text nor any operation beside it runs or is stored as outside text ({§outside-text}), and its one diagnostic, at the heading's line, reads `` `### log:///2/1/5/READ` is a log entry, and only the harness writes the log. Write the operation, then wait for its receipt. `` Text inside an operation body is not examined, so a SEND or KILL may quote a receipt. A heading whose leaf is `emission` is exempt: the transcript shows it before each of the worker's own emissions ({§emission-row}), and repeating it invents no receipt, so the attempt is admitted and the heading stays outside text, counted by the digest as an echo. In 10,486 recorded emissions, 101 carried such a heading in outside text, every one a fabrication: 85 of 1,675 from deepseek-flash, 81 of them opening with one, and 16 from glm-5.3-flash, deepseek-v4-pro and qwen3.8-flash, which appended an invented `## Log` after their own operations.
1357
+ §fabricated-log-entry **Only the harness writes the log.** A line of outside response text that begins with a log-entry heading, `### log:///<loop>/<turn>/<sequence>/` ({§log-wire-format}), is the model continuing the packet's transcript instead of answering it: it writes the receipts it expects and then acts on them. The attempt is rejected under {§invalid-emission-attempts}, so neither that text nor any operation beside it runs or is stored as outside text ({§outside-text}), and its one diagnostic, at the heading's line, reads `` `### log:///2/1/5/READ` is a log entry, and only the harness writes the log. `` Text inside an operation body is not examined, so a SEND or KILL may quote a receipt. A heading whose leaf is `emission` is exempt: it names the worker's own source ({§emission-row}), and repeating it invents no receipt, so the attempt is admitted and the heading stays outside text, counted by the digest as an echo. In 10,486 recorded emissions, 101 carried such a heading in outside text, every one a fabrication: 85 of 1,675 from deepseek-flash, 81 of them opening with one, and 16 from glm-5.3-flash, deepseek-v4-pro and qwen3.8-flash, which appended an invented `## Log` after their own operations.
1321
1358
 
1322
1359
  Warnings and closer recovery ({§closer-fallback}) do not reject. `finish=length`
1323
1360
  discloses truncation and precludes completion; it is not independently a rejection.
@@ -1579,9 +1616,9 @@ Every fact names the canonical key, never the host root or an echo of the
1579
1616
  model's spelling. These classes let a caller distinguish a wrong address, an
1580
1617
  invalid range, read-only authority, and occupied hidden state without guessing.
1581
1618
 
1582
- §membership-read-refusal **A file miss speaks of membership, never of the disk.** A file is read only as a member, so every `file` miss — READ, FIND of an exact path, KILL, a pattern EDIT's source, a COPY or MOVE source — is 404 `entry-not-found`, `No member of this workspace is at '<key>'.`, its target rendered as the member key, never a `file://` URI. Recovery treats path correction with FIND, creation with EDIT, and admission with `members (add)` as alternatives; it must not presume a missing READ requires creation or admission. Admission takes a `{"glob": "<path>"}` body. The sentence is about the address and is true whether or not a file is there: it neither claims absence nor hints at presence. Beyond the root the engine does not look at the disk at all, so two reads of `../` paths differ only in the name they echo, and their recovery speaks of the address too, true under every members scope: `'<key>' is outside the project root: only a members definition under the operator's namespace scope admits it, so keep working files inside the root.` Inside the root occupancy is not secret ({§fs-write-nonmember}), so an exact-path READ of a path that exists on disk but is not a member says so instead — 404 `entry-not-member`, `'<key>' exists on disk but is not a member of this workspace.`, with admission-only recovery; a path the repository ignores, which no model definition admits, carries the members discovery verdict instead: `The repository ignores it: a client or operator members definition can include it, a model definition cannot.` Occupancy may surface there; content never does ({§membership}).
1619
+ §membership-read-refusal **A file miss speaks of membership, never of the disk.** A file is read only as a member, so every `file` miss — READ, FIND of an exact path, KILL, a pattern EDIT's source, a COPY or MOVE source — is 404 `entry-not-found`, `No member of this workspace is at '<key>'.`, its target rendered as the member key, never a `file://` URI. Recovery offers listing with FIND, creation with EDIT, and admission with `members (add)` as alternatives and presumes none ({§diagnostic-observation}): `` FIND lists paths. EDIT creates files; `members (add)` admits existing files with a `{"glob": "<path>"}` body. `` Admission takes a `{"glob": "<path>"}` body. The sentence is about the address and is true whether or not a file is there: it neither claims absence nor hints at presence. Beyond the root the engine does not look at the disk at all, so two reads of `../` paths differ only in the name they echo, and their recovery speaks of the address too, true under every members scope: `'<key>' is outside the project root: only a members definition under the operator's namespace scope admits it.` Inside the root occupancy is not secret ({§fs-write-nonmember}), so an exact-path READ of a path that exists on disk but is not a member says so instead — 404 `entry-not-member`, `'<key>' exists on disk but is not a member of this workspace.`, with admission-only recovery; a path the repository ignores, which no model definition admits, carries the members discovery verdict instead: `The repository ignores it: a client or operator members definition can include it, a model definition cannot.` Occupancy may surface there; content never does ({§membership}).
1583
1620
 
1584
- §file-directory-target **A directory is named as a directory.** Inside the root, a READ (or other exact-path read), KILL or EDIT whose target is a directory on disk — with or without a trailing slash — is refused `path-is-directory`, never as a missing or non-member file, since admitting it is not what the model needs: READ and KILL answer 404, EDIT 403. The detail is `'<key>' is a directory, not a file; <OP> reads/removes/writes one file.` and the recovery names the listing that reaches its files, `` List its files with `FIND (<key>/)`, then READ one by its path. `` (KILL: `then KILL each by its path`; EDIT: `` Name a file inside it, as `EDIT (<key>/<file>)`; list its files with `FIND (<key>/)`. ``). Beyond the root the disk stays dark and {§membership-read-refusal} holds unchanged.
1621
+ §file-directory-target **A directory is named as a directory.** Inside the root, a READ (or other exact-path read), KILL or EDIT whose target is a directory on disk — with or without a trailing slash — is refused `path-is-directory`, never as a missing or non-member file: READ and KILL answer 404, EDIT 403. The detail is `'<key>' is a directory, not a file; <OP> reads/removes/writes one file.`; a READ or KILL carries the listing as its recovery, `` `FIND (<key>/)` lists its files. ``, and an EDIT carries none. Beyond the root the disk stays dark and {§membership-read-refusal} holds unchanged.
1585
1622
 
1586
1623
  §file-find-directory **FIND recognizes existing directories without requiring a trailing slash.** An exact target naming a directory inside the workspace root resolves to the same recursive collection as its slash-suffixed spelling, including content matching and result pagination. Only workspace members appear; an empty directory or one containing only non-members yields a successful empty survey. Exact files stay exact, genuinely absent paths retain their missing-entry error, and this resolution does not inspect disk paths beyond the root. Shared glob and non-file URI semantics are unchanged.
1587
1624
 
@@ -1853,8 +1890,6 @@ operator knobs in `.env.defaults`. Search indexing performs no inference.
1853
1890
 
1854
1891
  §per-entry-channels-edit-writes-only-body EDIT writes one channel per call — the channel resolved from the path's fragment (or the scheme's `defaultChannel` when no fragment).
1855
1892
 
1856
- No stored `preview` channel — channel content is pulled on READ, never previewed.
1857
-
1858
1893
  Schemes MAY declare multiple channels (`node`: stdout/stderr; `http`: body/header; SSE: per-event-type). Each goes in `manifest.channels` with mimetype pinned; rendered independently. Execution input is control under {§exec-input}, not a stored channel.
1859
1894
 
1860
1895
  For a multi-channel streaming READ, persistence and publication are distinct: the scheme may acquire and persist auxiliary channels, but a fragmentless target publishes only the manifest's `defaultChannel`. An explicit fragment publishes that channel. Thus an ordinary HTTP READ presents the sanitized `body`; response metadata and archival DOM remain addressable implementation/diagnostic surfaces rather than ambient model context.
@@ -2031,7 +2066,7 @@ AST: `{ op: "EDIT", target, body: string | null, signal: tags | null, lineMarker
2031
2066
  - §edit-null-clears Writes the body; `body: null` clears it.
2032
2067
  - §edit-status-201-200 Returns `{ status: 201, entryId }` for a new entry and
2033
2068
  `{ status: 200, entryId }` for a content update.
2034
- - §edit-noop-304 A write that changes nothing — identical content — returns `{ status: 304, entryId }`, mirroring a scoped KILL's idempotence ({§log-kill-scope}). Its terse detail states the observed equality and the valid empty-body deletion shape; it never presumes that repetition or retrieval is the intended recovery.
2069
+ - §edit-noop-304 A write that changes nothing — identical content — returns `{ status: 304, entryId }`, mirroring a scoped KILL's idempotence ({§log-kill-scope}). Its terse detail states the observed equality, `No change: EDIT body matches the selected content.`, and nothing else ({§diagnostic-observation}).
2035
2070
  - §edit-marker-required-on-existing **A markerless EDIT is CREATE-ONLY — there is no easy-clobber path on an existing entry.** A `<L>` marker scopes an EDIT to a range; without one, the body becomes the entry's WHOLE content — legitimate and required for a fresh entry (nothing exists to scope into), but on an EXISTING entry a missing marker is refused **400**, never a silent full replace. A deliberate full rewrite states that intent explicitly: `<1,-1>` resolves through the ordinary marker math to the same whole-content replacement, so the capability is available but cannot be selected by omission.
2036
2071
  - §edit-line-anchors An anchored EDIT resolves under {§line-anchors} and carries
2037
2072
  its endpoint checks as a core-private mutation precondition. Otherwise-valid
@@ -2105,7 +2140,7 @@ READ is the one fan-out core performs ({§read-fan-out}).
2105
2140
  when that differs from the channel's own mimetype the result names the
2106
2141
  channel's as `sourceMimetype`, so a consumer can still run the channel's
2107
2142
  handlers over a whole-resource `<1,-1>` read.
2108
- - §log-range-miss-names-stream A 416 on a log execution item is the range twin of its channel miss ({§log-channel-miss-names-stream}): the coordinate addresses the row's invocation (its authored call body, often empty or one line) while the execution's output stays readable at the stream address the row records. When that stream link exists, the 416 gains it as `stream`, the detail appends where the command's streams live, and `recovery` is `READ <stream> for the command's stream`, whether the invocation's extent is empty or merely shorter than the range (#759). A 416 on a row with no recorded stream stays byte-identical to the generic slicer's.
2143
+ - §log-range-miss-names-stream A 416 on a log execution item addresses the row's invocation (its authored call body, often empty or one line), while the execution's output stays readable at the stream address the row records. When that stream link exists, the 416 carries it as the `stream` extension; its detail and recovery stay the generic slicer's, naming no cause ({§diagnostic-observation}).
2109
2144
  - §read-pattern **A pattern selects the lines a READ renders.** With a heading
2110
2145
  matcher ({§matcher-option} in the contracts SPEC) an exact-target READ stays a
2111
2146
  READ: the matcher runs over the channel's complete text — a regex anchors
@@ -2234,7 +2269,7 @@ AST: `{ op: "KILL", target, matcher: MatcherBody | null, lineMarker: TextLineMar
2234
2269
 
2235
2270
  KILL deletes context from the **log** (`log:///`, {§packet}). Without a scope it retires the selected rows from the active projection ({§log-history-projection}). With a one-line or inclusive two-line scope it removes only that body's intersecting body-relative physical lines from the readable projection, and the row stays active. An anchor may be one published on that body or one returned by READing its `log:///` coordinate ({§line-anchors}); an anchor absent from the current body selects no line, as with an out-of-bounds numeric line. Scoped KILL is one-way: intervals accumulate, the durable body is untouched, and subsequent access follows {§log-readable-projection}. A scoped KILL on a bodyless row is a friendly 200 no-op with `matched` reported. An emission row is curated whole ({§emission-row}): a scope covering every line retires it like an unscoped KILL; on its exact coordinate a narrower scope is 422 `emission-curated-whole`, whose recovery names both forms that retire it; a sweep leaves it intact. A KILL that addresses no row is 404 on an exact coordinate and 204 on a sweep ({§log-curation-folder-idiom}). Selection composes target/glob with an optional heading pattern ({§log-curation-set-selection}). Parameterless KILL instead requests completion ({§kill-conclusion}).
2236
2271
 
2237
- §log-scope-recovery A log-body scope follows the file slicer's range rule ({§range-starts-at-one} in the schemes SPEC): a range starting at 0 — `<0,-1>` included — is refused 416 `range-not-satisfiable` on every body, empty ones too, and never clamped; its detail is the slicer's own sentence, `Range <0,-1> starts at 0, which is not a line; lines are numbered from 1.`, and its recovery names the forms a log body takes — `Write <1,-1> to trim every line of the body; KILL (log:///1/9/2/READ) with no scope retires the whole row.`, or `To trim lines 1 through M, write <1,M>; …` — never the insert and append positions a body cannot take. Every other scope that names no line is 400 `curation-scope-invalid` and likewise names the model's mistake in its coordinates and the forms that work on that row: `<0>` offers `<1>`; an end below 1 offers `<L,-1>`; a backward `<5,3>` offers `<3,5>`; anything else offers `<L>`, `<L,M>` and the unscoped row KILL.
2272
+ §log-scope-recovery A log-body scope follows the file slicer's range rule ({§range-starts-at-one} in the schemes SPEC): a range starting at 0 — `<0,-1>` included — is refused 416 `range-not-satisfiable` on every body, empty ones too, and never clamped; its detail is the slicer's own sentence, `Range <0,-1> starts at 0, which is not a line; lines are numbered from 1.`, and its recovery names the forms a log body takes — `Trim one line with <L> or lines L through M with <L,M>; KILL (log:///1/9/2/READ) with no scope retires the whole row.` — never the insert and append positions a body cannot take. Every other scope that names no line is 400 `curation-scope-invalid` with the same recovery: the forms that work on that row, never a rebuild of the written scope ({§diagnostic-observation}).
2238
2273
 
2239
2274
  A READ carrying active native media is atomic: any KILL scope is ignored and the entire observation is retired, including its native context contribution ({§packet-attachment-parts}). For a model turn, native activity is the attachment selection in its actual input packet; without a model packet, a native observation is atomic by default. Text-only observations in the same selection retain ordinary scoped behavior. Neither form deletes source data or forensic evidence.
2240
2275
 
@@ -2242,7 +2277,7 @@ A READ carrying active native media is atomic: any KILL scope is ignored and the
2242
2277
 
2243
2278
  | Fact | Owner | Effect |
2244
2279
  | --- | --- | --- |
2245
- | Initial body suppression | Immutable event `initial_folded` | Packet presentation only; explicit retrieval can read an initially hidden body. An emission row's hidden body reaches the packet as its assistant message ({§emission-row}). |
2280
+ | Initial body suppression | Immutable event `initial_folded` | Packet presentation only; explicit retrieval can read an initially hidden body. An emission row remains folded; its program is separately eligible for {§previous-emission}. |
2246
2281
  | Deliberate scoped KILL | Current projection `folded`, initially empty | Packet, READ, FIND, COPY, and search omit those lines; later retrieval cannot undo trimming. |
2247
2282
 
2248
2283
  Packet display combines both masks. Other consumers use only deliberate trimming.
@@ -2280,7 +2315,7 @@ The `## Log` section is a sequence of ordinary Markdown records separated by one
2280
2315
  | facts | One strict JSON object in stable alphabetical order. | Present only when a fact exists. Asides, scopes, opaque invocation metadata and result facts belong here, not on the H3. |
2281
2316
  | body | Coordinate-prefixed lines. | Present when the row is visible. |
2282
2317
 
2283
- An emission row's body is not in its record: it follows the record as the worker's assistant message ({§packet-wire-envelope}). Patterns retain their literal spelling; a pattern containing a line break is JSON-quoted to keep the H3 on one physical line. Receipts are descriptive records, not reconstructed operation headings. Absent fields are not invented. Every physical body line retains its canonical numeric `N:` or anchored `@hash N:` coordinate, so source text cannot create a record boundary. The section contains records only, with no leading prose or enclosing fence.
2318
+ An emission row's body is not in its record ({§emission-row}); the optional previous program follows the user footer ({§previous-emission}). Patterns retain their literal spelling; a pattern containing a line break is JSON-quoted to keep the H3 on one physical line. Receipts are descriptive records, not reconstructed operation headings. Absent fields are not invented. Every physical body line retains its canonical numeric `N:` or anchored `@hash N:` coordinate, so source text cannot create a record boundary. The section contains records only, with no leading prose or enclosing fence.
2284
2319
 
2285
2320
  §log-address-metadata **Addresses name their relationship, not the row's producer.**
2286
2321
 
@@ -2362,7 +2397,7 @@ Authored `metadata` retains its opaque ordered block strings under {§scheme-met
2362
2397
  records the exact READ coordinates sent without controlling retention. Missing immutable bytes are an
2363
2398
  internal integrity failure, never silently dropped content. No ejection message or permanent teaching is
2364
2399
  added. These stable curation weights are not provider-token measurements ({§tokenomics-render-weight-budget}).
2365
- - §packet-token-accounting Every row reports one `tokens` charge on its H3 ({§log-wire-format}): its complete materialized H3, facts, visible body, selected native attachment, and the emission it delivers outside its record ({§emission-row}). The completed record is measured to a fixed point, including the accounting field itself. No `tokensBody`, `tokensMetadata`, or `tokensActive` field is serialized. Hidden text is not charged; metadata-only rows still have a reclaimable charge. Source/FIND-item `tokens` measure source content, not the observation's context footprint. A FIND's nonzero `itemsTokenTotal` weighs the complete matched set; a nonzero `returnedItemsTokenTotal` appears only when the returned page differs. All use stable curation weights, not provider tokens or dollars. Native component accounting follows {§packet-attachment-parts}; ordinary addressability and truthful errors follow {§log-wire-format}.
2400
+ - §packet-token-accounting Every row reports one `tokens` charge on its H3 ({§log-wire-format}): its complete materialized H3, facts, visible body and selected native attachment. The previous-emission section is charged separately as user-slot text ({§previous-emission}). The completed record is measured to a fixed point, including the accounting field itself. Hidden text is not charged; metadata-only rows still have a reclaimable charge. Source/FIND-item `tokens` measure source content, not the observation's context footprint. A FIND's nonzero `itemsTokenTotal` weighs the complete matched set; a nonzero `returnedItemsTokenTotal` appears only when the returned page differs. All use stable curation weights, not provider tokens or dollars. Native component accounting follows {§packet-attachment-parts}; ordinary addressability and truthful errors follow {§log-wire-format}.
2366
2401
 
2367
2402
  ### §retrieval-packet-metadata READ/FIND packet metadata
2368
2403
 
@@ -2412,9 +2447,8 @@ single line past the end, a reversed range, empty content, a command's log row
2412
2447
 
2413
2448
  ### §emission-row The emission row
2414
2449
 
2415
- Every admitted emission is announced by one row of its own turn, so the transcript carries the
2416
- worker's emissions in chronological place ({§packet-wire-envelope}) and the worker curates them
2417
- like any other row.
2450
+ Every admitted content emission is announced by one curatable row of its own turn.
2451
+ The most recent eligible program supplies {§previous-emission}; older programs remain readable.
2418
2452
 
2419
2453
  | Surface | Contract |
2420
2454
  |---|---|
@@ -2422,12 +2456,12 @@ like any other row.
2422
2456
  | Place | After the turn's inputs (arrivals, deltas, open-path READs) and before its reasoning NOTEs and operations, written after the selection snapshot ({§turn-ops-selection-snapshot}): the emission sits between what the worker had seen and what it caused. |
2423
2457
  | Row | A `_plurnk` READ of the turn's own source, `ops://<worker>/L/T`, with `attrs.kind="emission"` and the canonical leaf `/emission`: `### log:///L/T/S/emission → ops://<worker>/L/T · N`. It renders its author, the turn's producer, as `origin`, so a model's row carries none. It is no operation: no receipt, tool call or strike, and outside the op mix. |
2424
2458
  | Body | Frozen at announcement: the canonical rendering ({§statement-rendering}) of the admitted content statements, each whole in a closed fence. All heading operands, scopes, metadata, patterns and asides remain. Free text and unadmitted forms are absent; a recovered native call ({§native-tool-calls}) appears as the operation it was read as; an operation whose receipt failed stays. Reasoning operations retain their own source and normal receipts, never an assistant-content copy. No body text is inspected for nested operations. |
2425
- | Wire | Bodies are logged, never replayed (#1018). The wire shows a statement with a body as its heading and an empty closer, and one without a body as its heading closed on the same line, so the shape says whether a body was left out. A NOTE or WAIT, and a parameterless KILL or SEND, is not replayed at all, since its own row shows it whole ({§body-projection}); an emission of only those delivers nothing, its row stands, and no assistant message follows it ({§packet-wire-envelope}). Dispatch and the immutable `ops://` source keep complete bodies ({§turn-source-resources}); an explicit READ returns them, as it returns a receipt. |
2459
+ | Wire | The record appears in the log; its complete program is eligible for {§previous-emission}. No operation family or body is filtered. Dispatch and the immutable `ops://` source keep complete bodies ({§turn-source-resources}); an explicit READ returns them. |
2426
2460
  | Stability | The projection is fixed from its first appearance, never aged or resized under budget pressure. Already frozen announcements and historical request captures are not rewritten. |
2427
- | Presentation | Born folded: the record shows its header, and its body follows the record as the worker's assistant message. |
2428
- | Accounting | `tokens` charges the record and the emission the wire delivers, never what the wire leaves out ({§packet-token-accounting}). An explicit source READ has its own ordinary charge. |
2461
+ | Presentation | Born folded: the log record shows its header, never an assistant message. |
2462
+ | Accounting | The row's `tokens` charges only its rendered record. The optional previous-emission section is measured once as user-slot text ({§packet-token-accounting}); replacing it never changes older row weights. An explicit source READ has its own ordinary charge. |
2429
2463
  | Curation | Curated whole ({§log-kill-scope}): KILL retires it, and so does a scope covering every line (`<1,-1>`); on its exact coordinate a narrower scope is 422 `emission-curated-whole`, and a sweep whose scope would only trim it leaves it intact. |
2430
- | Schema | Migration 12 admits `kind="emission"` only on this shape: one per turn, the turn's newest row when written, frozen, and curated whole. A database from before version 12 keeps its rows and gains no announcement. FORK copies it with the inherited turns, still naming its writer. |
2464
+ | Schema | Migration 12 admits `kind="emission"` only on this shape: one per turn, the turn's newest row when written, frozen, and curated whole; it backfills no announcement for earlier turns. FORK copies it with the inherited turns, still naming its writer. |
2431
2465
  | Echoes | A worker that repeats the heading in its own text is tolerated ({§fabricated-log-entry}); the digest counts the echoes. |
2432
2466
 
2433
2467
  ### §reasoning-row The reasoning row
@@ -2437,8 +2471,8 @@ when it is `1`, every model turn that reasoned lands its reasoning as one row of
2437
2471
  own, `log:///<loop>/<turn>/<sequence>/reasoning → reasoning://<worker>/<loop>/<turn>`, the harness's
2438
2472
  READ of the turn's reasoning source, immediately before the turn's emission row (or its first
2439
2473
  operation or diagnostic when there is no content emission), so the transcript
2440
- reads what the model thought and then what it emitted, and the assistant message that follows stays
2441
- the canonical program ({§emission-row}, {§packet-wire-envelope}).
2474
+ reads what the model thought and where its content program can be found
2475
+ ({§emission-row}, {§packet-wire-envelope}).
2442
2476
 
2443
2477
  | Concern | Rule |
2444
2478
  |---|---|
@@ -2704,7 +2738,7 @@ Log history preserved — `log_entries` stores path tuple as text, not FK to `en
2704
2738
  matches the selected channel's content or derivation; path globs select
2705
2739
  resources through `(target)` ({§path-glob}).
2706
2740
  - §find-fulltext-selection Every matcher operates only over the candidate set selected by `(target)`; indexed matchers do not bypass that selection. `~query` passes the native FTS5 expression to SQLite and ranks matching candidates by ascending BM25, with resource identity breaking ties. Native BM25 uses the shared index's term statistics; candidate visibility, owner, channel and target filters determine which resources can be returned. The ordinary FIND pager selects resources for broad targets or match locations for exact targets: markerless search uses {§markerless-first-page}, `<N>` selects position N and `<N,M>` selects an inclusive range. Fractions are invalid result coordinates, not similarity thresholds. Results expose addressable matched text regions; neither cosine scores nor percentage similarity is invented. Native query-syntax failures return 400 with SQLite's diagnostic; database and implementation failures propagate.
2707
- - §fts-word-phrase **A word with inner punctuation is the phrase of its tokens.** FTS5 barewords hold only letters, digits, `_` and non-ASCII, so before the query reaches SQLite each word outside a quoted string or `NEAR(…)` group that is not a bareword (with optional leading `^` and trailing `*`) is quoted as a phrase: `~inherited-members` searches `"inherited-members"` — the adjacent tokens `inherited members` — instead of failing as `no such column: members`, and `c++`, `x.y`, `a/b` likewise. FTS5's own syntax passes untouched: `AND`/`OR`/`NOT`/`NEAR`, `+`, quoted phrases, parentheses, and column filters (a word containing `:`, `{` or `}`, or opening with `-`). A column-filter failure keeps SQLite's diagnostic and its recovery says what the filter is and gives the bare-word and `NOT` forms: `` `members:` and `-members` are FTS5 column filters, and the index has one column; to search for a word write it bare, as `~members`, and to exclude one write `NOT` between terms, as `~a NOT members`. `` A syntax error over parentheses or quotes, which FTS5 reads as its own syntax, recovers to the phrase and to the regex for the same characters: `` FTS5 reads parentheses and quotes as query syntax: quote the phrase, as `~"all("`, or match the characters with a regex, as `/all\(/`. `` (#1005).
2741
+ - §fts-word-phrase **A word with inner punctuation is the phrase of its tokens.** FTS5 barewords hold only letters, digits, `_` and non-ASCII, so before the query reaches SQLite each word outside a quoted string or `NEAR(…)` group that is not a bareword (with optional leading `^` and trailing `*`) is quoted as a phrase: `~inherited-members` searches `"inherited-members"` — the adjacent tokens `inherited members` — instead of failing as `no such column: members`, and `c++`, `x.y`, `a/b` likewise. FTS5's own syntax passes untouched: `AND`/`OR`/`NOT`/`NEAR`, `+`, quoted phrases, parentheses, and column filters (a word containing `:`, `{` or `}`, or opening with `-`). An FTS5 failure keeps SQLite's own message as its `diagnostic`, and its recovery is the dialect's form, never a rewrite of the query ({§diagnostic-observation}): `` An FTS5 query is barewords, "quoted phrases", and AND, OR, NOT and NEAR between them. ``
2708
2742
  - §find-scoped-isolation Workspace + scheme scoped — no cross-workspace/cross-scheme leakage.
2709
2743
  - §find-result-projection **The resolved target selection determines the result unit; result cardinality never changes it** ({§find-result-unit}). Returns `FindResult { status, content, mimetype, results, range, matchingPathCount, matchLocationCount, itemsWeightTotal, returnedItemsWeightTotal }`:
2710
2744
 
@@ -2846,6 +2880,8 @@ accounting and model-visible failure evidence remain separately owned by
2846
2880
  contains exactly one KILL without a target, scope, matcher or metadata, no hard
2847
2881
  parse error or lost boundary, and was not cut at the provider's output allowance.
2848
2882
  The operation limit must admit the entire program.
2883
+ The parser retains everything after that KILL heading as its answer region
2884
+ ({§terminal-kill}); apparent operations there are body text, not siblings.
2849
2885
  SEND, NOTE and log-targeted KILL may accompany it, as may outside text ({§outside-text});
2850
2886
  every other operation requires continuation. This tolerance is unadvertised:
2851
2887
  model teaching requests KILL alone. Reasoning-side NOTEs remain ordinary notes.
@@ -2984,21 +3020,18 @@ target scheme, before effect admission or source acquisition. Core validates the
2984
3020
  preparation result and retains its cwd; it neither parses option names nor
2985
3021
  redirects metadata to a source scheme. An executor without preparation accepts
2986
3022
  no metadata. A `script`-kind target ({§executor-invocation}) is the program: core inspects it before
2987
- anything spawns — a file is the script; a directory is refused `400 target-not-a-program`,
2988
- pointing at `[{"cwd": "…"}]`; an absent path is refused `400 target-not-found`, giving the
2989
- applicable accepted form without inferring what the model meant. When the target is a
2990
- registered tool of another executor, recovery gives that tool's exact bracketed
2991
- invocation; when it names another available executor (`sh (python3)` over a Python body, #895),
2992
- recovery names that executor's fence — `` `python3` is its own executor; use that name on the
2993
- opening fence and put the program in the body. ``; otherwise it points at an existing script or a bare shell-command body. A non-file resource
3023
+ anything spawns — a file is the script; a directory is refused `400 target-not-a-program`;
3024
+ an absent path is refused `400 target-not-found`, stating what exists and inferring nothing
3025
+ ({§diagnostic-observation}): a registered tool of another executor is named with that executor,
3026
+ another available executor as its own executor (`` `python3` is its own executor. ``), and
3027
+ anything else as a target that must name an existing program resource. A non-file resource
2994
3028
  target that cannot be read keeps the owning READ's failure identity (#163) and states
2995
- the slot contract in its recovery — the resource is the program and the body its stdin;
2996
- a command belongs beneath a targetless heading — without guessing which was meant (#425). The started receipt always
3029
+ the slot contract in its recovery: the resource is the program and the body its stdin. The started receipt always
2997
3030
  names the working directory only when it is not the project root, and then in the
2998
3031
  project-relative form ({§fs-namespace}); the default directory is omitted rather
2999
3032
  than repeated in every receipt. Native file addresses resolve from that same project
3000
3033
  directory, while absolute input retains its filesystem meaning. The `(path)` is a program — a script for an interpreter, a tool name for a tool
3001
- family — and neither a command nor a working directory is ever a target. The default
3034
+ runtime — and neither a command nor a working directory is ever a target. The default
3002
3035
  shell is written as its own fence, ```` ```sh ````; no runtime-less form exists.
3003
3036
 
3004
3037
  §exec-target-near-miss Two target near-misses have one reading each and are read
@@ -3012,15 +3045,14 @@ real stream id is always the program source.
3012
3045
 
3013
3046
  §exec-target-documentation **Generated reference is never a program.** A resource target under
3014
3047
  `worker:///_plurnk/` — the executor and scheme documentation the harness generates — is refused
3015
- at admission, 400 `target-is-documentation`, before any source is realized or run: `` `worker:///_plurnk/plurnk/sh.md` is reference documentation the harness generated, not a program; sh cannot run it. `` With a body the recovery is `Drop the target and keep the command: the opening fence line is sh alone, with the command lines beneath it.`; without one it points to READ for the documentation and to the program's own path or a targetless heading to run something. Admitting it realized the markdown as a host temporary file that a sandboxed runtime could not open (`cannot open /tmp/plurnk-exec-….md`), and ran markdown where it could.
3048
+ at admission, 400 `target-is-documentation`, before any source is realized or run: `` `worker:///_plurnk/plurnk/sh.md` is reference documentation the harness generated, not a program; sh cannot run it. `` It carries no recovery ({§diagnostic-observation}). Admitting it realized the markdown as a host temporary file that a sandboxed runtime could not open (`cannot open /tmp/plurnk-exec-….md`), and ran markdown where it could.
3016
3049
 
3017
3050
  §exec-tool-fall-through **A tool run as a shell command is named at the failure
3018
3051
  site.** A bare shell command whose program is the name of a tool published by
3019
3052
  another enabled runtime (`brave_web_search {…}` under the default shell) exits
3020
- 127; the stream's terminal receipt then says the program is not a shell command
3021
- but a tool of that runtime, gives the exact bracketed invocation with the JSON
3022
- body convention, names the tool's own document, and carries `toolRuntimes` and
3023
- `tool`. The status stays the shell's 500, nothing is rerouted, and a program the
3053
+ 127; the stream's terminal receipt then says the program is a tool of that
3054
+ runtime, names the tool's own document, and carries `toolRuntimes` and `tool`;
3055
+ it builds no invocation ({§diagnostic-observation}). The status stays the shell's 500, nothing is rerouted, and a program the
3024
3056
  registry does not know keeps the plain exit-127 receipt.
3025
3057
 
3026
3058
  | Declared target kind | Authored target | Canonical effect target | Executor realization |
@@ -3043,7 +3075,7 @@ effect classification or proposal creation.
3043
3075
  A runtime with {§executor-tool-registry} admits only the snapshot's exact
3044
3076
  literal targets. An absent target is 400; a target outside that closed enabled
3045
3077
  set is 404; neither reaches effect classification or proposal creation. The
3046
- selected entry's invocation—not the family's structural fallback—owns body
3078
+ selected entry's invocation—not the runtime's structural fallback—owns body
3047
3079
  requiredness and roles. The executor independently rejects an unregistered
3048
3080
  target at its run boundary.
3049
3081
 
@@ -3131,12 +3163,12 @@ addressed to `<runtime>:///<id>` when the id holds an execution only under anoth
3131
3163
  §exec-lifetime **How long a spawn may live is the fence's metadata, one field.**
3132
3164
  `[{"lifetime": …}]` takes a duration (`30s`, `30m`, `2h`), or one of three words;
3133
3165
  absent is `loop`. The key is one of the service's reserved metadata keys, withheld
3134
- from every owner by the framework ({§service-metadata-keys}). An execution takes no scope: a numeric coordinate on an
3135
- executor target is refused `scope-unsupported` (400), naming the field.
3166
+ from every owner by the framework ({§service-metadata-keys}). An execution takes no scope: a scope on an
3167
+ execution is refused `scope-unsupported` (400).
3136
3168
 
3137
3169
  | `lifetime` | The spawn |
3138
3170
  | ------------ | --------- |
3139
- | a duration | Aborted at the deadline — a bounded reap, polite signal then SIGKILL after `PLURNK_SERVICE_EXEC_KILL_GRACE_MS` — and the stream is stamped **504** `execution-timeout`, distinct from a deliberate kill (499) or a clean exit (200): `` Execution of '*runtime*' outlived its *lifetime* lifetime. `` Recovery: `` Run it again with a longer lifetime, such as [{"lifetime": "30m"}], or with none to let it run as long as the loop. `` |
3171
+ | a duration | Aborted at the deadline — a bounded reap, polite signal then SIGKILL after `PLURNK_SERVICE_EXEC_KILL_GRACE_MS` — and the stream is stamped **504** `execution-timeout`, distinct from a deliberate kill (499) or a clean exit (200): `` Execution of '*runtime*' outlived its *lifetime* lifetime. `` |
3140
3172
  | `loop` (absent) | Loop-life bounded: reaped on every loop terminal except 202, the background-stream behavior. |
3141
3173
  | `turn` | Reaped at the worker's next pre-turn via the registry abort, before the turn's own spawns, so it never survives into the subsequent turn; its terminal output surfaces born visible like any close ({§exec-stream}). |
3142
3174
  | `detached` | Outlives its loop's terminal, 200 included. It never binds to the loop's teardown and is nobody's obligation — completion is not gated by it, WAIT does not park on it, optimistic settlement looks past it — and it ends only by KILL, the worker's total reap, or daemon shutdown; its late conclusion surfaces without opening a loop. |
@@ -3148,7 +3180,7 @@ stream deltas. Calendar recurrence remains a schedule ({§schedule-delivery}).
3148
3180
 
3149
3181
  §exec-host-proposes **Effect-gating.** Each executor — and each scheme operation that mutates something outside this process — declares an `effect` (`pure` | `read` | `host`); the service maps it to policy (`EffectPolicy`). The declarer states the FACT, the panel decides the POLICY, and one rule covers every operation: nothing that changes the world runs on nobody's authority. A `host` runtime (subprocess; file-backed sqlite) proposes under {§proposal}, and so does an outbound request that mutates a remote resource ({§http-outbound-proposes}). Once accepted, it spawns and writes channels at its workspace execution address ({§execution-output-identity}), returning `102 Processing`. Channel state transitions (`active` → `closed`/`errored`) drive subsequent observations ({§channel-state}).
3150
3182
 
3151
- §entry-owner **Every entry belongs directly to one workspace.** Its immutable identity is `(workspace_id, scheme, authority, pathname)`. The workspace foreign key supplies lifetime; URI authority supplies the literal resource namespace. There is no entry-owner Worker, synthetic commons actor, caller-relative alias, or per-Worker access grant. Core binds one canonical coordinate through {§entry-address-resolution} for every operation and consumer. Producer and subscriber Worker ids describe causal activity, not ownership.
3183
+ §entry-owner **Every entry belongs directly to one workspace.** Its immutable identity is `(workspace_id, scheme, authority, pathname)`. The workspace foreign key supplies lifetime; URI authority supplies the literal resource namespace. Core binds one canonical coordinate through {§entry-address-resolution} for every operation and consumer. Producer and subscriber Worker ids describe causal activity, not ownership.
3152
3184
 
3153
3185
  §execution-output-identity **Execution output belongs to the workspace.**
3154
3186
  Each invocation claims a collision-checked eight-character lowercase hexadecimal
@@ -3390,10 +3422,10 @@ Core derives the contracts-owned `ProposalProjection` from the durable proposed
3390
3422
  | `target` | canonical `{ scheme, authority, pathname }` from `attrs.proposalTarget` for staged COPY/MOVE, otherwise the log row target |
3391
3423
  | `body` | proposed operation result `rx.body`; absent means the empty review body |
3392
3424
  | `attrs` | proposed log row `attrs` object |
3393
- | `policy` | validated complete persisted loop policy |
3425
+ | `owner` | the worker's durable workspace-scoped approval owner ({§worker-ownership}) |
3394
3426
  | `disposition` | {§proposal-disposition}; the same value drives automatic settlement and client presentation |
3395
3427
 
3396
- Workspace scope remains the event envelope / seam argument ({§notifications-envelope-carries-workspaceid}); it is not forged into `ProposalProjection`. Malformed durable JSON, target metadata, result envelopes, loop policy, or final projection fails at core with its cause; after insertion, core terminalizes that row as a 500 `policy_failed` before propagating the internal failure, so no waiter or durable stopped world is orphaned.
3428
+ Workspace scope remains the event envelope / seam argument ({§notifications-envelope-carries-workspaceid}); it is not forged into `ProposalProjection`. Malformed durable JSON, target metadata, result envelopes, owner registration, or final projection fails at core with its cause; after insertion, core terminalizes that row as a 500 `policy_failed` before propagating the internal failure, so no waiter or durable stopped world is orphaned.
3397
3429
 
3398
3430
  ### §client-interactions Client-owned interaction lifecycle
3399
3431
 
@@ -3402,7 +3434,8 @@ A scheme or executor may pause its current operation on one
3402
3434
  pending-only `client_interactions` row bound to one exact
3403
3435
  workspace/worker/loop/turn ownership chain, then publishes the same validated
3404
3436
  `ClientInteractionProjection` through `loop/interaction` and reconnect
3405
- discovery. The projection contains no workspace id or private upstream
3437
+ discovery. The projection names its recipient ({§client-interaction-routing}) but
3438
+ contains no workspace id or private upstream
3406
3439
  continuation state; those remain respectively in the event envelope and the
3407
3440
  awaiting operation owner.
3408
3441
 
@@ -3420,112 +3453,44 @@ composes it with the existing owner signal before registering the same waiter.
3420
3453
  Reconnect discovery intersects durable rows with live waiters; restart
3421
3454
  removes ownerless rows without fabricating cancellation, payload, or replay.
3422
3455
 
3423
- ### Loop disposition and client YOLO
3424
-
3425
- Side-effecting operations propose ({§exec}) and pause dispatch at 202 for an
3426
- authority decision ({§engine-rails}, {§methods}). Automatic acceptance has two
3427
- distinct owners:
3456
+ ### Server disposition and client YOLO
3428
3457
 
3429
- | Mechanism | Authority path | Intended use |
3430
- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
3431
- | §proposal-ownership-loop-auto **Loop disposition** | `runLoop({ policy: { proposals: "accept" } })` persists a loop-owned disposition; core resolves proposals in process without a client. | Headless automation, benchmarks, CI, fixtures, and unattended use. |
3432
- | **Client-side YOLO** (`--yolo` / `PLURNK_YOLO`) | A `proposals: "review"` loop emits the ordinary `loop/proposal`; the client returns an accepted proposal through its standard resolution path. | Interactive automatic review. |
3433
-
3434
- Core cannot distinguish client-side YOLO from a fast human acceptance and does
3435
- not need to. Loop auto keeps authority inside the loop; client-side YOLO acts
3436
- only after authority crosses the client boundary.
3458
+ | Mechanism | Authority path |
3459
+ | --- | --- |
3460
+ | Server disposition | `PLURNK_SERVICE_PROPOSALS` selects review, accept, or reject. Core settles automatic decisions through the normal proposal lifecycle. |
3461
+ | Client-side YOLO | The owning client accepts review requests through the same resolution path as human review. It cannot override server rejection or capability admission. |
3437
3462
 
3438
- §proposal-ownership-notification **The notification carries disposition, not policy inputs.** `loop/proposal` carries the core-owned `ProposalDisposition` ({§notifications}, {§proposal-disposition}). A connected client presents only `owner="client"`; it never reimplements policy from operation or attrs.
3463
+ §proposal-ownership-notification The notification carries the selected
3464
+ `ProposalDisposition`, not policy inputs. Clients present only `decision: "review"`
3465
+ addressed to their owner identity; they never reconstruct authority from the
3466
+ operation, message sender, or attrs.
3439
3467
 
3440
3468
  ### §proposal-disposition Settlement authority and precedence
3441
3469
 
3442
- §loop-attendance **A loop says whether anyone is attending, and a wait nobody could end is
3443
- never taken.** `LoopPolicy.attended` is the whole of it: `true` says an interactive partner is
3444
- present, `false` declares an unattended loop. It is one field of the loop's complete policy, so
3445
- its creator states it or leaves it to the panel ({§loop-policy-composition}); the client's
3446
- `--auto` is exactly the statement `attended: false`. A fresh delegated loop inherits it with the
3447
- rest of the policy ({§worker-delegation-inherits-policy}), so a child of a headless run is
3448
- headless too.
3449
-
3450
- Unattended, three things follow and nothing else does:
3451
-
3452
- - **A proposal is never held for review.** `{ proposals: "review", attended: false }` is not a
3453
- `LoopPolicy`: the schema refuses the pair, so no loop can persist it. The panel cannot produce
3454
- it, because attendance picks which disposition knob answers. Only a creator's own statement
3455
- can ask for it, and that is refused **400 `loop-policy-invalid`** naming the way out.
3456
- - **No interactive partner is offered.** `ClientInteractions.request` refuses **501
3457
- `loop-unattended`** instead of writing the request down and waiting. Every wiring funnels
3458
- through that one request — the `question` runtime ({§question-tool}), the execution input
3459
- bridge, the scheme interaction caps, and MCP elicitation — so one refusal covers them all. The
3460
- `question` runtime's effect is `read`, so it is never proposal-gated and a disposition does
3461
- nothing for it. The refusal is the asking executor's **own result**, never a thrown contract
3462
- violation: the model has to read why it cannot ask.
3463
-
3464
- Dispatch refuses it first, at the **loop ring** of the capability cascade: an unattended loop's
3465
- own layer denies the `interact` access class ({§worker-tool-admission}), and that 403 names the
3466
- ring, the reason and the recovery — a subtracted tool must say why it is gone and what to do
3467
- instead. Every other ring is operator configuration and speaks for itself; the loop ring is the
3468
- one the model can act on. The 501 at the interaction itself is the backstop for the paths that
3469
- do not cross dispatch (MCP elicitation raised inside a tool call, the execution-input bridge).
3470
- The ring reaches dispatch but not the reserved tree's listing, which is one artifact per
3471
- workspace: an unattended model still sees the question document and learns at dispatch that it
3472
- is refused (#770).
3473
- - **A provider-recovery park becomes a conclusion** ({§provider-recovery}), carrying the
3474
- provider's own exact Problem. Never a substituted "the model gave up".
3475
-
3476
- **A park is a promise that something will restart it.** `LoopLifecycle.park` takes the waker
3477
- by name; `null` says nothing will. Parking an unattended loop with no waker is a contract
3478
- violation and throws, so a park site added later fails loudly on its first unattended run
3479
- instead of idling until some caller's clock notices. It is a tripwire, not a fallback: the
3480
- provider-recovery path concludes before reaching it.
3481
-
3482
- Attendance never converts a legitimate wait that has a real waker — a `WAIT`, an open stream,
3483
- a delegated child — into a termination. Those have wakers; a human question
3484
- does not. A prompt prefix states a disposition, never an attendance: typing `?` asks for review,
3485
- and an unattended loop refuses that statement rather than conjuring a reviewer.
3486
-
3487
- §loop-policy-composition **A loop's policy is what its creator stated over what the panel
3488
- says.** A creator — a client's `loop.run`, a schedule definition, a transport module — states
3489
- any part of a policy as a `LoopPolicyRequest`, or nothing. The request stays exactly as stated
3490
- until a fresh loop is persisted: an omitted field is no opinion, so a fold compares only what
3491
- was said ({§methods-loop-run-fold-consistency}). `LoopPolicies.compose` then makes it whole,
3492
- once. `PLURNK_SERVICE_ATTENDED` answers an unstated attendance, and the attendance picks which
3493
- knob answers an unstated disposition: `PLURNK_SERVICE_PROPOSALS` for an attended loop,
3494
- `PLURNK_SERVICE_UNATTENDED_PROPOSALS` for an unattended one, whose vocabulary has no `review`.
3495
- Every valid panel state is therefore lawful; an invalid knob is diagnosed at startup
3496
- and refuses a loop that needs it ({§configuration-repair-path}). No code,
3497
- schema or column holds a default ({§operator-config-only-home}): `loops.policy` and
3498
- `loops.max_turns` carry none, so every insert states both. Client-authored administrative
3499
- loops use the same composition.
3500
-
3501
- §runtime-bookkeeping-policy **Runtime bookkeeping has no reviewer and cannot acquire
3502
- new authority.** Its administrative loops explicitly state
3503
- `{ attended: false, proposals: "reject" }`; this is a runtime invariant, not an
3504
- interactive default. Generated reference publication and audit narration therefore
3505
- do not depend on client policy configuration. Runtime-authored proposals do not
3506
- use effect-policy auto-admission; bookkeeping proposals settle as failures through
3507
- the ordinary proposal lifecycle, never wait for a client or auto-accept.
3508
-
3509
- §loop-policy-effective-read `loops.policy` persists one complete immutable
3510
- `LoopPolicy`; every runtime policy read validates that snapshot before use.
3511
- Missing rows or invalid values fail with the owning loop coordinate and cause.
3512
- Raw archival copies and forensic rendering do not interpret policy.
3513
-
3514
- The cascade's rings are `service`, `workspace` and, innermost, `loop`. The loop ring exists only
3515
- for an unattended run and is purely subtractive like every other layer, so it can never widen what
3516
- its workspace allows; a capability denial names the ring that refused. The operator's capability
3517
- projection and the shared reserved-document materialization take no loop coordinate on purpose:
3518
- those questions are about a workspace, not about one run.
3519
-
3520
- `ProposalDisposition` is either `{ owner: "client" }` or `{ owner: "loop", decision: "accept" | "reject", outcome? }`. The persisted loop policy determines it exactly:
3521
-
3522
- | `policy.proposals` | Disposition |
3523
- | ------------------ | ---------------------------------------- |
3524
- | `review` | client |
3525
- | `accept` | loop accept |
3526
- | `reject` | loop reject, outcome `no_review_channel` |
3527
-
3528
- Capability admission precedes this decision, so proposal disposition cannot grant a denied capability. Loop-owned settlement occurs before observational notification; observer failures are diagnosed with their cause and cannot change disposition or leave an eligible automatic proposal pending.
3470
+ | First applicable condition | Disposition |
3471
+ | --- | --- |
3472
+ | Runtime bookkeeping | reject, `runtime_bookkeeping` |
3473
+ | Server `accept` | accept |
3474
+ | Server `reject` | reject, `policy_veto` |
3475
+ | Server `review`, owner implements `request_approval` | review |
3476
+ | Server `review`, owner has no reviewer | reject, `no_review_channel` |
3477
+
3478
+ Core selects the disposition once for the pending gate. Its live event and
3479
+ reconnect projection use that same decision. Automatic settlement precedes
3480
+ observational notification; an observer failure cannot change authority or orphan
3481
+ a waiter. Invalid configuration is diagnosed at the proposal boundary without
3482
+ substituting authority ({§configuration-repair-path}). Client-authored
3483
+ administrative operations retain their client's owner.
3484
+
3485
+ §runtime-bookkeeping-policy Runtime bookkeeping cannot acquire new authority.
3486
+ Generated reference publication and audit narration have no reviewer and do not
3487
+ depend on interactive defaults. An unexpected bookkeeping proposal rejects through
3488
+ the ordinary lifecycle, even if the server would accept other operations.
3489
+
3490
+ A park requires a live obligation or an interactive owner. Connection presence
3491
+ does not determine capability. Ordinary WAIT on streams or delegated work is
3492
+ unchanged; a provider-recovery park whose owner is not interactive concludes on the
3493
+ provider failure ({§provider-recovery}).
3529
3494
 
3530
3495
  ---
3531
3496
 
@@ -3535,7 +3500,7 @@ Capability admission precedes this decision, so proposal disposition cannot gran
3535
3500
 
3536
3501
  ### §subscriptions Subscriptions
3537
3502
 
3538
- §subscriptions-subscription-registry-routes-cancellation READ on a streaming scheme is a subscription, not a one-shot. The scheme establishes its protocol-specific acquisition boundary, returns `102 Processing`, and stays alive through the `StreamSubscription` returned by `subscriptions.open()`. The service commits that initial operation result normally; later chunk and terminal work cannot rewrite it. Durable terminal truth lives on the subscription and its channels. The service records durable subscription identity and metadata in SQLite and retains the callable `SubscriptionHandle` only in its process-local live registry. Worker cancellation, turn-scoped reap, and shutdown all route through that one live registry; no handler-specific cancellation hook or database access is part of the extension contract.
3503
+ §subscriptions-subscription-registry-routes-cancellation READ on a streaming scheme is a subscription, not a one-shot. The scheme establishes its protocol-specific acquisition boundary, returns `102 Processing`, and stays alive through the `StreamSubscription` returned by `subscriptions.open()`. The service commits that initial operation result normally; later chunk and terminal work cannot rewrite it. Durable terminal truth lives on the subscription and its channels. The service records durable subscription identity and metadata in SQLite and retains the callable `SubscriptionHandle` only in its process-local live registry. Worker cancellation, turn-scoped reap, and shutdown all route through that one live registry; no handler-specific cancellation callback or database access is part of the extension contract.
3539
3504
 
3540
3505
  The durable row is lifecycle evidence and the lookup key, not a serialized callback. `subscriptions.open()` establishes both halves before yielding a composed `StreamSubscription`: an `AbortSignal` whose fused `notifyChunk` and terminal `close` methods are safe to retain without the operation's general `SchemeCtx`. `close(result, summary?, channelResults?)` validates one universal terminal producer result plus exact named channel overrides. One SQLite transition closes the subscription and installs each channel's terminal `producerResult`; its lifecycle state derives from that result. The transition then wakes the worker when appropriate and unregisters the live handle. `close_status` is a constrained relational projection of `close_result.status`, never an independent result, while `channel_results` preserves historical overrides after a later subscription replaces the channel's current evidence. A durable open row without a live handle is an explicit lifecycle failure, never a fabricated cancellation success. Channel state ({§channel-state}) + log entries ({§no-chunk-rows}) carry lifecycle.
3541
3506
 
@@ -3612,12 +3577,12 @@ No generator. SQLite-optimal: STRICT (3.37+), `INTEGER PRIMARY KEY` aliasing, ex
3612
3577
  | Concern | Rule |
3613
3578
  |---|---|
3614
3579
  | §db-schema-baseline Baseline | Versions 1–8 of `migrations/` are the released baseline, as domain chapters — `001_workspaces`, `002_workers`, `003_loops`, `004_inference`, `005_entries`, `006_log`, `007_subscriptions`, `008_interactions` — each one `MIGRATE` block whose version is the file's numeric prefix. They create the shape 1.21.1 shipped: tables, indexes, views, the constraint triggers that are a table's invariants (guards that only `RAISE`), and a view's `INSTEAD OF` write path. No migration holds an `INIT` block or a trigger that writes a row. |
3615
- | §db-migrations Evolution | A released version is frozen: only its comments may change. Every shape change is one new file at the next version (`009_effort` renames the #877 columns), applied by sqlrite above the database's `PRAGMA user_version`, ascending, each in its own transaction with its version bump. A fresh database takes the same path as an existing one. Each migration carries upgrade coverage: `test/intg/schema-baseline.test.ts` pins the released shape's fingerprint and migrates a released database, asserting its rows survive. A process trigger's change needs no migration: its `INIT` block re-declares it on the next open ({§db-process-triggers}). A table anything references cannot be rebuilt in a migration: foreign keys stay enforced inside the migration transaction, so the drop cascades through its children (`011_settled.sql`); such a table evolves by adding columns or redeclaring its guard triggers. Only an unreferenced table is rebuilt (`010_outside_text.sql`). |
3580
+ | §db-migrations Evolution | A released version is frozen: only its comments may change. Every shape change is one new file at the next version, applied by sqlrite above the database's `PRAGMA user_version`, ascending, each in its own transaction with its version bump. A fresh database takes the same path as an existing one. Each migration carries upgrade coverage: `test/intg/schema-baseline.test.ts` pins the released shape's fingerprint and migrates a released database, asserting its rows survive. A process trigger's change needs no migration: its `INIT` block re-declares it on the next open ({§db-process-triggers}). A table anything references cannot be rebuilt in a migration: foreign keys stay enforced inside the migration transaction, so the drop cascades through its children (`011_settled.sql`); such a table evolves by adding columns or redeclaring its guard triggers. Only an unreferenced table is rebuilt (`010_outside_text.sql`). |
3616
3581
  | §validation-topology Where an invariant is enforced | The SQL core owns each invariant: a chapter's CHECK constraints and guard triggers are its one statement, and a rule two tables share is the same expression over each column (`entry_channel_producer_result_contract` and `subscriptions_result_contract_update` hold the settled-result rule as one text, `011_settled`). Contracts (JSON Schema) are enforced at the gates: a scheme's result entering core (`Results` in plurnk-schemes) and the wire leaving to clients (plurnk-agui's `Validator` calls). Everything between trusts core and the gates and carries no defensive re-validation: a result read back from a row is parsed, never re-asserted. Witness: `test/intg/validation-topology.test.ts` applies one corpus of settled results to the gate, to chapter 5 and to chapter 7 and asserts the three agree on every row. |
3617
3582
  | Open failure | A missing table or column after migration means the file's shape disagrees with its version: a database from a newer release, or one from an unreleased development build. The daemon refuses to open it and names both remedies. |
3618
3583
  | §db-process-triggers Processes beside their owners | A trigger that writes rows — a cascade, a capture, an ambient event, a publication cursor, a landed curation — is a process, not shape. It is declared as an `-- INIT: <trigger name>` block in the `.sql` file beside the statements that fire it (`ambient.sql` for the ambient feed, `LoopLifecycle.sql`, `Turn.sql`, `Engine.sql` for model calls, `_entry-crud.sql`, `Log.sql`, `ChannelWrite.sql`), as `DROP TRIGGER IF EXISTS` then `CREATE TRIGGER`, so the definition is current on every open of a database whose shape is current. `MIGRATE` always precedes `INIT` and `INIT` runs on the writer only, so a process may reference any table regardless of file order and never runs on the read pool. `test/intg/schema-composition.test.ts` fails on a baseline trigger that writes, an `INIT` trigger that only guards, a block not named after its trigger or not dropping first, and a live trigger set that differs from the declared set after a first and a second open. |
3619
3584
  | §db-fk-indexes Foreign-key check paths | Every foreign-key column a delete, cascade, or parent replacement can check carries an index (partial where the column is nullable), and no registry statement's plan scans a growing table: `test/intg/schema-query-plans.test.ts` runs `EXPLAIN QUERY PLAN` over every `-- PREP` statement against the baseline and fails on a `SCAN` of a growing table, except statements that read a whole table by design (digest, startup recovery, whole-workspace listings, scheduled-loop claims). An index claim is a plan, never a grep of index names. |
3620
- | §db-index-owners Every index has an owner | An explicit index earns its place one of three ways: a registry statement's plan uses it, its leading column is a foreign key whose check it serves, or it enforces uniqueness. The same test fails on any other index, naming it: an index nobody reads is a write on every insert. Duplicates of a `UNIQUE` constraint's own index and sort-only indexes no plan selects were removed on this rule; a column no statement reads (`symbol_refs.col`, `ambient_events.created_at`) is not stored. |
3585
+ | §db-index-owners Every index has an owner | An explicit index earns its place one of three ways: a registry statement's plan uses it, its leading column is a foreign key whose check it serves, or it enforces uniqueness. The same test fails on any other index, naming it: an index nobody reads is a write on every insert. A column no statement reads is not stored. |
3621
3586
  | §db-maintenance-optimize Statistics at shutdown | The daemon's last database step before the caller closes SQLite is `PRAGMA optimize` on the writer (`maintenance_optimize`), so `sqlite_stat1` reflects tables the connection planned against, bounded by SQLite's own analysis limit; a failure there is a reported shutdown error, never silent. Retention runs before it under the operator's policy ({§retention-policy}) and ends with a WAL truncation ({§db-space-reclamation}); no periodic `ANALYZE` runs. |
3622
3587
  | §db-space-reclamation The daemon keeps its own file healthy | `PLURNK_SERVICE_AUTO_VACUUM` (`incremental` or `none`) names the mode the daemon keeps its file in. At start, before any drain, a database in another mode is converted (set the mode, one `VACUUM`, which rewrites the file and needs free disk about its size) and the journal says so with page counts before and after. Under `incremental`, every retention pass ends by stepping `PRAGMA incremental_vacuum` to completion once free pages reach `PLURNK_SERVICE_RECLAIM_MIN_FREE_BYTES` (0 = every pass), and reports `reclaimedPages`; below the floor, free pages stay for SQLite to reuse. Under `none` the file never shrinks and freed pages are reused. No operator step is involved beyond the knobs. The WAL stays bounded by SQLite's automatic checkpoint (#764). |
3623
3588
  | §content-store Every body is stored once | `contents` holds each settled body once, addressed by its SHA-256, however many channels, workspaces, forks or derivations carry it; rows are immutable. `entry_channel_rows` points a settled channel at its body and keeps an active stream's body as a private buffer until it settles, when it is interned. Every reader and writer uses the `entry_channels` view, whose `INSTEAD OF` triggers intern bodies, refuse a bound `content_hash` that is not the content's, and write each column group only when it changed, so a search attachment is never a representation write. SQLite counts no changes for a view, so a write that must know whether its channel exists returns the channel's name; an outer join cannot flatten the view, so the two statements that need one read `entry_channel_rows` and `contents` directly. `derivation_fts` is an external-content index over `derivation_texts` (a derivation joined to its body); `derivations.content_id` names the indexed text, and the triggers in `_entry-fts.sql` move the index with it and forget it on delete. A body no channel holds and no derivation indexes is collected by retention under `PLURNK_SERVICE_COLLECT_CONTENTS`. Witnesses: `test/intg/retention.test.ts`, `test/intg/entries.test.ts`, `test/intg/fulltext-index.test.ts`. |
@@ -3652,10 +3617,10 @@ When SQL becomes onerous for a specific case, retreat for that case and document
3652
3617
 
3653
3618
  ## §core-extension-composition Extension composition
3654
3619
 
3655
- The metaproject contract owns installed membership, the one-family manifest
3620
+ The metaproject contract owns installed membership, the one-kind manifest
3656
3621
  shape, and the shared pre-import trust boundary ({§extension-discovery}). Each
3657
3622
  framework owns its typed discovery result and trusted loading path.
3658
- Core owns only cross-family composition, arbitration, and operator presentation
3623
+ Core owns only cross-kind composition, arbitration, and operator presentation
3659
3624
  of skipped-package evidence.
3660
3625
 
3661
3626
  §extension-namespace-arbitration **Every addressable scheme name has one claim.**
@@ -3669,14 +3634,14 @@ claiming either name, for process-wide and workspace-scoped registrations alike.
3669
3634
  | Existing claim | Incoming claim | Outcome |
3670
3635
  |--------------------------------|---------------------------------------------------------|---------|
3671
3636
  | None | Any valid claim | Register it. |
3672
- | Same installed family/package | Rescan of the same name | No-op; retain the one registered handler. |
3637
+ | Same installed kind/package | Rescan of the same name | No-op; retain the one registered handler. |
3673
3638
  | Reserved core name | Any extension, a module included | Fail naming the reserved owner and claimant. |
3674
3639
  | Any extension | A different owner, including scheme/executor either way | Fail naming both owners. |
3675
3640
  | Module runtime | Its optional same-registration scheme facet | Compose one handler under the runtime's single claim. |
3676
3641
 
3677
3642
  Arbitration precedes host registry mutation. External scheme descriptors are
3678
3643
  arbitrated before core imports their handlers; executor packages have already
3679
- crossed their family-owned trusted loading path when core arbitrates their
3644
+ crossed their framework-owned trusted loading path when core arbitrates their
3680
3645
  output faces. A rejected claim leaves both scheme and executor registries
3681
3646
  unchanged, so registration order cannot turn a collision into precedence.
3682
3647
  Installed third-party packages enter through the same scope-agnostic npm
@@ -3687,15 +3652,15 @@ registration.
3687
3652
 
3688
3653
  ## §bundled-set Bundled Set
3689
3654
 
3690
- Family discovery ({§extension-discovery}) scans installed scoped and unscoped
3655
+ Extension discovery ({§extension-discovery}) scans installed scoped and unscoped
3691
3656
  packages carrying the applicable `plurnk.kind` declaration.
3692
3657
 
3693
3658
  §default-extension-ownership `@plurnk/plurnk-service` is the sole manifest owner
3694
3659
  of the default extensions. Frameworks own contracts, discovery, and loading;
3695
3660
  their runtime dependency graphs contain no extension of their kind. A required
3696
3661
  default extension missing from a service install is a broken install. A direct
3697
- framework consumer may intentionally omit leaves and receives that framework's
3698
- documented unavailable-capability behavior.
3662
+ framework consumer may intentionally omit extensions and receives that framework's
3663
+ documented behavior for an absent extension.
3699
3664
 
3700
3665
  `@plurnk/plurnk-tavily-plugin` is the separately installed, opt-in plugin showcase,
3701
3666
  not a service dependency. Its own tests own installation, native discovery,
@@ -3711,7 +3676,7 @@ service runtime dependency, and an optional one ({§mimetype-optional-grammars})
3711
3676
  must not be. Installation coverage loads each default grammar and verifies
3712
3677
  source definitions, references, structural projections, and teardown outside
3713
3678
  the checkout's development dependency graph, and proves each optional language
3714
- degrades by name when its leaf is absent.
3679
+ degrades by name when its grammar package is absent.
3715
3680
 
3716
3681
  §install-root-advisory-ownership **The composed service install owns
3717
3682
  third-party advisory detection.** Only that install resolves the default extensions
@@ -3719,20 +3684,19 @@ and their combined transitive tree. Its audit reports advisories at the
3719
3684
  moderate floor without blocking by default; strict mode makes the same floor a
3720
3685
  gate. A chain rooted through an `@plurnk/*` dependency routes to that package's
3721
3686
  owner, while other direct roots remain service-owned. First-party package-pin
3722
- freshness remains the owning family's concern.
3687
+ freshness remains the owning package's concern.
3723
3688
 
3724
- | Kind | Lean framework | Service-owned default extensions |
3689
+ | Kind | Lean framework | Service-owned default packages |
3725
3690
  |-----------|------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
3726
3691
  | Schemes | `@plurnk/plurnk-schemes` | `@plurnk/plurnk-schemes-http` |
3727
- | Mimetypes | `@plurnk/plurnk-mimetypes` | `application-ipynb`, `application-json`, `application-jsonl`, and `application-xml` format leaves. |
3728
- | | | `text-csv`, `text-diff`, `text-dotenv`, `text-html`, `text-ini`, `text-markdown`, and `text-plain` format leaves. |
3729
- | | | `image`, `application-pdf` (header-only, {§mimetype-pdf-facts}), and every `grammar-{slug}` package in the framework's tree-sitter registry ({§mimetype-grammar-packages}), all under `@plurnk/plurnk-mimetypes-*`. |
3730
- | Executors | `@plurnk/plurnk-execs` | `common`, `jq`, and `sqlite` leaves under the `@plurnk/plurnk-execs-*` prefix. |
3692
+ | Mimetypes | `@plurnk/plurnk-mimetypes` | `application-ipynb`, `application-json`, `application-jsonl`, and `application-xml` format extensions. |
3693
+ | | | `text-csv`, `text-diff`, `text-dotenv`, `text-html`, `text-ini`, `text-markdown`, and `text-plain` format extensions. |
3694
+ | | | The `image` and `application-pdf` (header-only, {§mimetype-pdf-facts}) extensions, and every `grammar-{slug}` grammar package in the framework's tree-sitter registry ({§mimetype-grammar-packages}), all under `@plurnk/plurnk-mimetypes-*`. |
3695
+ | Executors | `@plurnk/plurnk-execs` | `common`, `jq`, and `sqlite` extensions under the `@plurnk/plurnk-execs-*` prefix. |
3731
3696
 
3732
- The independently published `tokenizers` artifact is an opt-in leaf. Installing
3697
+ The independently published `tokenizers` artifact is an opt-in package. Installing
3733
3698
  it beside the service admits it through ordinary package resolution without
3734
- changing the service manifest. PDF extraction is not a leaf at all: a PDF is a
3735
- native attachment like an image, and the daemon does no extraction (#542).
3699
+ changing the service manifest.
3736
3700
 
3737
3701
  **Providers:** `@plurnk/plurnk-providers` resolves the Models.dev catalog,
3738
3702
  operator declarations, local adapters, and finally installed AI SDK provider
@@ -3820,8 +3784,8 @@ Node's pre-script env-file form and the executable's post-script form share the
3820
3784
  | Agent Plugin extension | `ai.plurnk/.env.defaults` | The winning daemon-wide plugin in {§agent-plugins-hosting}, a valid native declaration, and the same trust gate |
3821
3785
 
3822
3786
  Root and trust flags apply before collection. A project plugin contributes no native panel.
3823
- Non-module native panels follow their npm-only family discovery ({§extension-manifest-read});
3824
- a plain-folder declaration does not suppress an installed capability's panel.
3787
+ Non-module native panels follow their framework's npm-only discovery ({§extension-manifest-read});
3788
+ a plain-folder declaration does not suppress an installed extension's panel.
3825
3789
  Linked packages resolve panels against the same canonical root as native code;
3826
3790
  an absent panel is optional, but a panel escaping that root is rejected.
3827
3791
  The file travels with its code and is its configuration reference. All admitted files compose
@@ -3831,6 +3795,24 @@ persisting another copy or exposing effective values. Duplicate key ownership fa
3831
3795
  owners. Invalid optional native panels are diagnosed and prevent that extension from loading;
3832
3796
  they do not block the remaining floor or the repair path ({§configuration-repair-path}).
3833
3797
 
3798
+ §operator-config-undeclared-key **A key no panel declares is reported, never refused.** At boot the
3799
+ daemon checks each `PLURNK_*` key the operator's configuration files set — `$XDG_CONFIG_HOME/plurnk/.env`
3800
+ and a `--config` file, never an `--env-file*` layer or the process environment — against the
3801
+ assembled panels' declarations, live and commented alike. A declaration whose name holds a
3802
+ `<placeholder>` segment declares a family and is only ever commented. A placeholder, like the scope
3803
+ after a declared name's `_`, stands for one or more letters, digits, `_`, `.` or `-`.
3804
+
3805
+ | Key the file sets | Outcome |
3806
+ |---|---|
3807
+ | A declared name | none |
3808
+ | A declared name, `_`, and a scope (`PLURNK_PROVIDERS_EFFORT_<alias>`, `PLURNK_MODEL_<alias>`) | none |
3809
+ | A family member (`# PLURNK_MCP_<alias>=` declares `PLURNK_MCP_docs_ENABLED`) | none |
3810
+ | Another component's: the client's `PLURNK_CLIENT_*` and `PLURNK_COMPOSITION_*`; the bench's `PLURNK_BENCH*`, `PLURNK_SWEBENCH_*`, `PLURNK_PI_*` and `PLURNK_CANDIDATE_*` | none |
3811
+ | Any other `PLURNK_*` key | one `info` Notice from `engine:configuration`, kind `configuration_undeclared`, naming the `key` and the file |
3812
+
3813
+ Boot continues. The notice is the operator's information: `start`, `config` and `config check` print
3814
+ it, `config check` still passes, and it never enters a model's packet.
3815
+
3834
3816
  §operator-config-only-home **The cascading environment is the only home for a choice.** The principle and its reasons are ARCHITECTURE.md's (*Configuration authority*); this is what `scripts/env-surface-policy.mjs` enforces in `root:lint`, over the source Git tracks:
3835
3817
 
3836
3818
  | Rule | What it refuses |
@@ -3842,7 +3824,6 @@ they do not block the remaining floor or the repair path ({§configuration-repai
3842
3824
  | `tunable` | a numeric constant whose own name says duration, size, count or pacing, unless `scripts/env-surface-mechanism.json` registers why it is mechanism |
3843
3825
  | `timer-literal` | a bare number handed to a timer or a deadline; it is named or read from the panel, and has no register |
3844
3826
  | `dead-knob` | a live declaration nothing consumes |
3845
- | `retired-declared` | a retired key still declared; a retired key is named only by the code that refuses it |
3846
3827
  | `duplicate-owner` | a key two packages declare |
3847
3828
  | `test-floor` | a package that ships a panel and tests off it |
3848
3829
 
@@ -3869,7 +3850,7 @@ names the offending variable or file/entry and retains its cause.
3869
3850
 
3870
3851
  | Owner | Offline validation |
3871
3852
  |---|---|
3872
- | Core | Model selection, file-creation/effect/loop policy, members definitions and controls, skill-fetch settings and root selection |
3853
+ | Core | Model selection, file-creation/effect/approval policy, members definitions and controls, skill-fetch settings and root selection |
3873
3854
  | MCP | Whole definitions from the environment and selected files (cwd is the project for this check), future-alias controls, catalog settings, timeouts, retry pacing and registry URL |
3874
3855
  | A2A | Whole outbound definitions and controls, timeout/diagnostic bounds, configured inbound exposure |
3875
3856
  | Schedule | Whole definitions and controls, recurrence syntax, time zone and preview count |
@@ -3898,14 +3879,14 @@ Each knob's value lives on its panel and nowhere else (`plurnk-service config de
3898
3879
  |---|---|
3899
3880
  | `PLURNK_SERVICE_DB_PATH` | SQLite file path; an explicit non-empty value overrides the derived default. |
3900
3881
  | `PLURNK_SERVICE_SHARE_FOLDER` | Parent of the shares written when no folder is named ({§share-folder}); empty is `$XDG_STATE_HOME/plurnk/shares`. |
3901
- | §operator-config-shared-keys `PLURNK_HOST`, `PLURNK_PORT` | The listener's bind address and TCP port — THE client surface, the AG-UI+ listener the plurnk-agui module binds at boot; production is single-listener. **A key the daemon and its clients both read has a shared owner**: `@plurnk/plurnk-contracts` declares these two and the optional `PLURNK_AGUI_URL` on its own panel, the one package every side depends on. The daemon folds it like any installed member's, a client folds it beneath its own, and so neither holds the other's default. The service's `--host` and `--port` flags are generated from that panel. |
3882
+ | §operator-config-shared-keys `PLURNK_HOST`, `PLURNK_PORT` | The daemon-owned listener's bind address and TCP port; modules mount their paths on that one listener ({§http-host}). **A key the daemon and its clients both read has a shared owner**: `@plurnk/plurnk-contracts` declares these two and the optional `PLURNK_AGUI_URL` on its own panel, the one package every side depends on. The daemon folds it like any installed member's, a client folds it beneath its own, and so neither holds the other's default. The service's `--host` and `--port` flags are generated from that panel. |
3902
3883
  | §operator-config-git-ceiling `PLURNK_SERVICE_GIT_ALLOWED` | Hard service ceiling: only `1` admits Git membership and status; every other value denies them. |
3903
3884
  | §operator-config-file-create-scope `PLURNK_SERVICE_FILE_CREATE_SCOPE` | Hard file-creation ceiling: `none < root < namespace`. `none` denies new filesystem files, `root` admits only paths inside `project_root`, and `namespace` also admits canonical outside-root paths. Existing-member writes are unaffected. |
3904
3885
  | `PLURNK_SERVICE_FILE_MATERIALIZE_MAX_BYTES` | Byte ceiling in `1..104857600` for one workspace-file snapshot ({§membership-materialization-limit}). |
3905
3886
  | `PLURNK_SERVICE_MAX_TURNS` | Operator model-call **ceiling** — `-1` = no cap; a positive value clamps `runLoop({maxTurns})`. The durable worker-tree budget includes descendant calls, BARE, and park/resume under {§turn-cap-counts-the-tree}; non-model chronology consumes none. |
3906
3887
  | `PLURNK_SERVICE_MAX_COMMANDS` | Per-emission action ceiling; `-1` = no cap — every compiled op dispatches. Each resolved target statement counts once, whether authored separately or in a group ({§target-group}). Overflow ops drop with one durable `max-commands-exceeded` error row on the next packet. Lifecycle operations remain exempt ({§operator-config-workspace-max-commands-floor}). Tightened per workspace via `settings.maxCommands` (min wins). |
3907
3888
  | §operator-config-loop-timeout `PLURNK_SERVICE_LOOP_TIMEOUT` | Positive ms of cumulative active execution per loop ({§loop-execution-allowance}); excludes parked/queued time. Snapshotted on first execution, retained across wakes. Exhaustion aborts in-flight work and terminates `504 loop_timeout`, including a stuck provider call. |
3908
- | `PLURNK_SERVICE_PROVIDER_RECOVERY` | ms of recovery after the first recoverable provider failure; `0` disables reissue. Expiry parks attended loops, concludes unattended loops, or returns BARE's failure under {§provider-recovery}. |
3889
+ | `PLURNK_SERVICE_PROVIDER_RECOVERY` | ms of recovery after the first recoverable provider failure; `0` disables reissue. Expiry parks workers with a reviewer, otherwise concludes their loop, or returns BARE's failure under {§provider-recovery}. |
3909
3890
  | `PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF` | First recovery delay (ms); doubles per failure up to `PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF_MAX` ({§provider-recovery}). |
3910
3891
  | `PLURNK_SERVICE_MAX_STRIKES` | Consecutive turn-contract strike threshold ({§engine-rails}). |
3911
3892
  | `PLURNK_SERVICE_EMISSION_ATTEMPTS` | Completed provider responses allowed beneath one engine turn before frame admission is exhausted. Bounded interior operation errors are admitted without spending this budget. Exhaustion contributes one frame-contract strike under {§invalid-emission-attempts}. |
@@ -3945,7 +3926,8 @@ External extensions declare their own env vars in their own `.env.defaults`, ass
3945
3926
 
3946
3927
  §turn-cap-counts-the-tree **The turn ceiling is the worker tree's budget of model
3947
3928
  calls.** The owner is the current loop of the topmost ancestor-or-self worker that has
3948
- one: for a tree a client started, the root worker's loop current when this loop began (its
3929
+ one, excluding the runtime actor (origin `_plurnk`): for a tree a client started,
3930
+ the root worker's loop current when this loop began (its
3949
3931
  `max_turns`: the client's `maxTurns` clamped by the operator ceiling below); a loop with no
3950
3932
  such ancestor owns its own budget. Every model call on the owner's loop and on any later
3951
3933
  loop of the owner's descendants spends it, emission turns and BARE calls alike, open or
@@ -3954,6 +3936,10 @@ terminal ({§loop-terminals}) when the ceiling is met; a BARE beyond the budget
3954
3936
  429 `max-turns` before any provider call, so one turn cannot spend past it with a batch. A
3955
3937
  child loop inherits the value and binds the same count.
3956
3938
 
3939
+ The runtime actor supplies no ceiling and does not combine otherwise independent model
3940
+ subtrees into one allowance. Its descendants still share their own model tree's ceiling.
3941
+ The shipped ceiling is `-1` (uncapped); client and transport defaults introduce no finite cap.
3942
+
3957
3943
  §operator-config-max-turns-ceiling Enforcement is per-use-site — no central most-restrictive pass; each ceiling is checked where it bites. `PLURNK_SERVICE_MAX_TURNS` at `-1` is no cap; when an operator sets a positive value, the per-call request is `min()`-capped against it. Other termination rules remain independent ({§loop-terminals}).
3958
3944
 
3959
3945
  ### Workspace settings
@@ -4006,7 +3992,7 @@ the policy renders in exactly one packet section. Every other tier runs the
4006
3992
  test cascade, so shipped-default regressions are otherwise invisible by
4007
3993
  construction.
4008
3994
 
4009
- §operator-config-real-model-profile **Real-model gate profile.** `plurnk-core/.env.test` is committed source and is the single shared profile for live, demo, and the candidate daemon used by benchlets. Live/demo load it after operator files; the candidate daemon loads it below its inherited environment. Direct shell/benchmark overrides win in both paths. Its exact allowlist is limited to gate-wide service posture that is identical on every machine: complete catalog orientation, automatic Git membership when the operator ceiling permits Git, ambient operator-file docs/packet notes cleared, the operator's installed skills and plugins left unread ({§agent-roots}), ambient MCP/A2A and schedules default disabled, and `PLURNK_EXECS_QUESTION=0` for unattended runs. The ordinary executor switch removes the question tool and its teaching; an explicit override can opt into an attended drill. The drivers also project per-alias `ENABLED=0` overrides for named MCP, A2A, schedule, membership, and skill resources in the operator's config file. Definitions remain inspectable; explicit shell/benchmark controls win. Mock-tier bootstrap clears these ambient families before loading its fixture floor. No operator file is rewritten. Configuration with a narrower or variable owner stays outside it:
3995
+ §operator-config-real-model-profile **Real-model gate profile.** `plurnk-core/.env.test` is committed source and is the single shared profile for live, demo, and the candidate daemon used by benchlets. Live/demo load it after operator files; the candidate daemon loads it below its inherited environment. Direct shell/benchmark overrides win in both paths. Its exact allowlist is limited to gate-wide service posture that is identical on every machine: complete catalog orientation, automatic Git membership when the operator ceiling permits Git, ambient operator-file docs/packet notes cleared, the operator's installed skills and plugins left unread ({§agent-roots}), server approval set to accept ({§worker-owner-resolution}), and ambient MCP/A2A and schedules default disabled. The drivers also project per-alias `ENABLED=0` overrides for named MCP, A2A, schedule, membership, and skill resources in the operator's config file. Definitions remain inspectable; explicit shell/benchmark controls win. Mock-tier bootstrap clears these ambient families before loading its fixture floor. No operator file is rewritten. Configuration with a narrower or variable owner stays outside it:
4010
3996
 
4011
3997
  | Owner | Configuration |
4012
3998
  |---|---|
@@ -4197,14 +4183,6 @@ workspace forgets its storage key through the existing foreign key; old disk
4197
4183
  state is not reassigned or automatically purged. A database copy preserves its
4198
4184
  storage identities; the directory is operational state, not a backup of it.
4199
4185
 
4200
- §skills-storage-upgrade **Skills ownership upgrades without a second identity.**
4201
- The database migration renames the family state to `@plurnk/plurnk-skills`.
4202
- Before module setup, core renames the former skills directory within each storage root
4203
- recorded by this database. Fetched bytes are not refetched or deleted. A missing former
4204
- directory needs no work; a completed rename is repeatable. Conflicting destinations and
4205
- filesystem failures stop admission without replacing either directory. No runtime alias
4206
- recognizes the former owner.
4207
-
4208
4186
  §workspace-environment-sharing **The workspace owns its shared environment.**
4209
4187
  Workers own their logs; delegation retains its existing lifecycle.
4210
4188
  Creating, attaching, forking, cancelling, or deleting a worker does not create,
@@ -4283,12 +4261,33 @@ Retryability describes the actual failed condition, not its numeric status.
4283
4261
  | Verb | Common contract |
4284
4262
  |---|---|
4285
4263
  | `list` | Project definitions, ownership (`origin`), winning configuration source (`provenance`), enabledness, and published preparation outcome: disabled, dormant, active, unavailable with its Problem, or authorization-required. No credential values. |
4286
- | `discover` | Return inert candidates. Never install, persist, enable, or execute them. |
4264
+ | `discover` | Return inert candidates for the inputs the family serves ({§functionality-discover-advertisement}). Never install, persist, enable, or execute them. |
4287
4265
  | `add` | Admit and persist a local definition, prepare it, and enable it atomically. It may override an inherited definition. Reapplying the same local definition enables it idempotently (200); a different local definition for that alias fails 409 without replacing it. |
4288
4266
  | `enable` | Publish an available definition; retry preparation if unavailable. |
4289
4267
  | `disable` | Withdraw live capability; retain its definition and saved results. |
4290
4268
  | `remove` | Forget the locally owned definition and its enabledness override. Restore any inherited definition with its inherited enabledness. Inherited definitions cannot be removed at this scope. Saved results remain. |
4291
4269
 
4270
+ §functionality-discover-advertisement **A family's `discover` advertises exactly the inputs it
4271
+ serves.** The coordinator builds the family's discover input schema from its declared discovery
4272
+ ({§functionality-discovery-inputs}): the declared inputs with their shared
4273
+ `FunctionalityDiscoverQuery` definitions and no other property (a multi-scope family adds
4274
+ `scope`), and, unless a request naming none lists everything, at least one of them: the one
4275
+ input required, or any of several. The client actions, the manager's tool documents and their
4276
+ schema documents carry that schema. Any other input, or a request naming none, is refused by the
4277
+ shared schema check as 400 `arguments-invalid`, its `errors` naming each field; no family mints
4278
+ its own refusal for an input it does not serve. A family without discovery has no `discover`
4279
+ action, and its manager teaches none. A value of a served input the family cannot use remains the
4280
+ family's own refusal.
4281
+
4282
+ | Family | Inputs | A request naming none |
4283
+ |---|---|---|
4284
+ | `skills` | `source` ({§skills-functionality}) | Refused |
4285
+ | `schedule` | `source` ({§schedule-clock}) | Refused |
4286
+ | `mcp` | `query`, while a registry is configured; otherwise no discovery ({§mcp-registry-discovery}) | Refused |
4287
+ | `a2a` | `source`, `configuration` ({§a2a-functionality}) | Refused |
4288
+ | `env` | `query`, `source` ({§env-functionality}) | The whole catalog |
4289
+ | `members` | `query` ({§members-functionality}) | Refused |
4290
+
4292
4291
  §configuration-definition-resolution **Named resource definitions replace whole;
4293
4292
  independent behavior controls remain independent.** Source readers and scope
4294
4293
  overlays apply the same boundary:
@@ -4328,9 +4327,8 @@ composition boundaries, not arbitrary exceptions:
4328
4327
  | Optional startup integration (hooks, hosted A2A, observability) cannot be configured | Withhold that integration and retain its exact configuration diagnostic. Activate the client interface and unrelated capabilities; never invent a replacement setting. |
4329
4328
  | Invalid default model or child selector | Retain its diagnostic at startup; keep client inspection and explicit selection available. A request relying on the invalid selector fails with `daemon:configuration/configuration-invalid` (503), naming its key. Never substitute another model or silently inherit a child model. |
4330
4329
  | Model construction or endpoint verification fails | Reject selection/use before inference or committing the selection. Keep the client available; a later selection can retry or choose another route. |
4331
- | Invalid effect, file-creation, or loop-default policy | Retain the startup diagnostic. Reject the affected operation or unresolved loop policy as `daemon:configuration/configuration-invalid` (503); no guessed admission policy, external effect, or orphan approval wait. Independent operations and explicitly supplied valid loop policies remain usable. |
4330
+ | Invalid effect, file-creation, or server approval policy | Retain the startup diagnostic. Reject the affected operation as `daemon:configuration/configuration-invalid` (503); no guessed admission policy, external effect, or orphan approval wait. Independent operations remain usable. |
4332
4331
  | Invalid retention configuration | Withhold collection and automatic storage conversion, including shutdown collection. Preserve stored evidence and expose the diagnostic; do not substitute a deletion policy. |
4333
- | Invalid packet configuration or retired capacity knobs | Retain the startup diagnostic and keep client inspection available. Reject affected packet construction before inference with `daemon:configuration/configuration-invalid` (503). Never guess a capacity or projection setting. |
4334
4332
  | Invalid executor construction/probe configuration | Keep the installed declaration and its diagnostic, but no executable instance or output scheme. Other executors and ordinary READ/EDIT remain usable. Invoking the unavailable tag returns its exact 503 configuration Problem. |
4335
4333
  | Invalid execution scheduling, input, or scratch configuration | Diagnose at startup and validate on the affected execution path before admission, stream creation, or external effects. Scratch configuration applies only to resource-backed execution; inline programs remain independent. Never substitute concurrency, timeout, or directory settings. |
4336
4334
  | Invalid model-alias catalog | Fail catalog inspection explicitly; omit its unavailable snapshot rather than report an empty catalog. Exact provider/model selection does not depend on aliases. |
@@ -4346,7 +4344,7 @@ composition boundaries, not arbitrary exceptions:
4346
4344
  Client discovery and passive synchronization report startup diagnostics through the
4347
4345
  existing Notice channel, even without a usable model. Each is a Notice from `engine:configuration`
4348
4346
  of kind `configuration_unavailable` that names the failed setting's `key` and its `owner`: the
4349
- setting's reader (such as `packet`, `model` or `executor:<runtime>`), a family, `extensions` for an
4347
+ setting's reader (such as `retention`, `model` or `executor:<runtime>`), a family, `extensions` for an
4350
4348
  extension declaration, or a module as `module:<package>`. The first turn of a drain, and a changed diagnostic
4351
4349
  thereafter, reports unresolved configuration to both client and model. An
4352
4350
  unchanged diagnostic is not repeated every turn. Operation failures remain Problems.
@@ -4461,8 +4459,9 @@ set any name it lists or none of them — it answers which names have a **consum
4461
4459
  a Worker learns the name of a value only the operator can supply. The catalog projects
4462
4460
  declarations, never the host environment, so a credential the operator has filled in appears by
4463
4461
  name with its documentation and an empty value ({§exec-env-scoped}: referred to by name, never
4464
- read). `configuration` is refused: a client's own environment contributing candidates would be a
4465
- second door past the ceiling.
4462
+ read). `source` selects one declaring package, and a request naming neither input is the whole
4463
+ catalog. A client's own environment is no input: contributing candidates, it would be a second
4464
+ door past the ceiling.
4466
4465
 
4467
4466
  The family publishes no process runtimes. Its values are read at the spawn that uses them,
4468
4467
  not from a live process or a cached worker environment.
@@ -4475,11 +4474,12 @@ than capability — with worker overrides above workspace defaults. The adapter
4475
4474
  coordinator keys durable state and the locally-owned `origin`
4476
4475
  by it: a workspace-scoped family's own entries carry origin `workspace`, a worker-scoped
4477
4476
  family's carry `worker`. Origin names ownership, never scope, so a projection never claims the
4478
- workspace set a value one Worker set for itself. Nothing else in the contract varies: the six
4479
- verbs, the two projections, enabledness, and the service-baseline rules are one implementation
4477
+ workspace set a value one Worker set for itself. Nothing else in the contract varies but the
4478
+ discovery inputs a family serves ({§functionality-discover-advertisement}): the verbs, the two
4479
+ projections, enabledness, and the service-baseline rules are one implementation
4480
4480
  across every family, which is what keeps their idioms from drifting apart.
4481
4481
 
4482
- A family projects `<scope>.<family>.<verb>` for each supported scope. The action's
4482
+ A family projects `<scope>.<family>.<verb>` for each verb it serves, in each supported scope. The action's
4483
4483
  context binds that scope; a worker-scoped action also names the Worker. Its durable
4484
4484
  value is the same shape per (worker, family) in `worker_module_state`, read
4485
4485
  at each verb and at each spawn rather than held in the workspace snapshot. Its `list` and
@@ -4514,9 +4514,9 @@ Admission, tools, documents, Turn 0, and client status consume that publication.
4514
4514
  The coordinator's synchronous `publish` participates in the registry commit;
4515
4515
  its returned undo restores the previous view before rollback reconciles documents.
4516
4516
  Neither callback performs fallible I/O. Publication failure restores the preceding
4517
- configuration/runtime snapshot; it cannot roll back effects performed by an
4518
- external installer. Installer failures retain their cause and must not be
4519
- reported as successful configuration changes.
4517
+ configuration/runtime snapshot; it cannot roll back an external effect of preparation,
4518
+ such as a fetched skill source. A failed external effect retains its cause and must not be
4519
+ reported as a successful configuration change.
4520
4520
  `settleFunctionality` joins queued
4521
4521
  publications before inspection or shutdown.
4522
4522
 
@@ -4529,7 +4529,8 @@ definitions add no hot-path teaching. Their exact state and Problem remain
4529
4529
  available through `list`.
4530
4530
 
4531
4531
  §functionality-model-projection **Each family has one workspace manager
4532
- executor.** The six verbs use their actual coordinator schemas and the ordinary
4532
+ executor.** Its verbs — `discover` only where the family serves discovery
4533
+ ({§functionality-discover-advertisement}) — use their actual coordinator schemas and the ordinary
4533
4534
  tool-document machinery. `list`/`discover` are read effects; mutations are host
4534
4535
  effects and use normal proposals. Summary, signatures, and deep docs derive
4535
4536
  from the same registry ({§tools-resource-materialization},
@@ -4581,10 +4582,10 @@ Core's behavior behind them.
4581
4582
  |---------------------------------------------------|----------|---------------|
4582
4583
  | §methods-event-subscribe Events | `subscribeToEvents(handler) -> unsubscribe` | Subscribes to the raw event source in {§notifications}. A subscriber failure is logged and cannot re-enter engine control flow. |
4583
4584
  | §proposal-list Proposals | `pendingProposals(workspaceId)` | Intersects durable proposed rows with the lifecycle owner's live resolution waiters, then returns their validated {§proposal-projection}; persistence alone cannot advertise an unresolvable client interrupt. |
4584
- | §methods-proposal-resolve Proposals | `resolveProposal(logEntryId, resolution)` | Validates and delivers one accept, reject, or cancel decision to the engine. An unknown or already-resolved id fails; the client protocol owns how the decision arrived. |
4585
+ | §methods-proposal-resolve Proposals | `resolveProposal(logEntryId, resolution, owner)` | Validates the workspace-scoped owner before delivering one accept, reject, or cancel decision. Unknown, mismatched, and already-resolved identities fail without consuming the gate; the adapter authenticates its owner identity. |
4585
4586
  | §client-interaction-list Client interactions | `pendingClientInteractions(workspaceId)` | Intersects durable interaction rows with their live operation waiters and returns the contracts-owned projection; a row alone is not a resumable interaction. |
4586
- | §methods-client-interaction-resolve Client interactions | `resolveClientInteraction(interactionId, resolution)` | Validates and delivers one resolved payload or cancellation. Unknown, ownerless, and already-resolved identities fail before affecting an operation. |
4587
- | §methods-loop-run Loops | `runLoop({ workspaceId, workerId, prompt, source?, maxTurns?, policy?, openPaths?, selector?, childSelector? })` | Validates a model worker and complete loop policy, persists it with the effective turn ceiling, then returns an immediate status-100 acknowledgement with `loopId` and `action`. A trusted adapter may identify the prompt's causal actor with one canonical `source`; ordinary clients cannot author it through their protocol surface. The exact terminal result arrives only through `loop/terminated`; parking and resuming do not replace the loop. |
4587
+ | §methods-client-interaction-resolve Client interactions | `resolveClientInteraction(interactionId, resolution, respondent, message?)` | Validates the recorded recipient before delivering one resolved payload or cancellation. Unknown, mismatched, and already-resolved identities fail before affecting an operation. |
4588
+ | §methods-loop-run Loops | `runLoop({ workspaceId, workerId, prompt, source?, maxTurns?, openPaths?, selector?, childSelector? })` | Validates a model worker and persists the request with the effective turn ceiling, then returns an immediate status-100 acknowledgement with `loopId` and `action`. A trusted adapter may identify the prompt's causal actor with one canonical `source`; ordinary clients cannot author it through their protocol surface. Messages carry no approval authority. The exact terminal result arrives only through `loop/terminated`; parking and resuming do not replace the loop. |
4588
4589
  | §methods-loop-cancel Loops | `cancelDrain(workerId, reason?)`; `cancelWorker({ workspaceId, workerId, reason? })` | `cancelDrain` begins durable structured cancellation and reports whether process-local work existed when called; queued or parked durable work is still terminalized when it is `false`. The ownership-bounded `cancelWorker` awaits that same tree cancellation and stream reap, so an exterior protocol can project the settled durable result without polling or fabricating state. |
4589
4590
  | §methods-op-mirror Client dispatch | `dispatchClientAction({ workspaceId, workerId, statements })` | Dispatches already-parsed grammar statements as one client action in one administrative loop in the client worker, executing in the workspace's Functionality ({§actor-boundary-attached-functionality}). Every statement is an ordered client/operation turn, and every committed `log/entry` is emitted before the action promise resolves; a proposal may keep its turn, loop, and action promise open until resolution. Core exposes no per-op method family. |
4590
4591
  | Client observation | `look({ workspaceId, workerId, statement, perspectiveWorkerId? })` | Runs an already-parsed READ through the full resolver in the workspace's Functionality without a log row. A non-READ statement is rejected ({§op-look}). |
@@ -4596,7 +4597,10 @@ Core's behavior behind them.
4596
4597
  | §methods-workspace-create Workspace lifecycle | `createWorkspace({ name?, projectRoot?, settings? })` | Validates `settings` through {§operator-config-workspace-settings}, creates the world and its client envelope, and emits global `workspace/created`. Creation and attachment are passive: neither starts derivation nor activates workspace Functionality. `projectRoot` is established here or the workspace remains headless. |
4597
4598
  | §methods-workspace-attach Workspace lifecycle | `attachWorkspace({ workspaceId, workerId?, workerName? })` | Validates ownership and returns a client envelope for an existing world. It does not retain caller or transport binding state in core. |
4598
4599
  | §methods-model-worker Workspace lifecycle | `ensureModelWorker(workspaceId)` | Returns the workspace's stable default model worker, creating it on first use. A durable default-conversation role identifies it independently of worker name and root creation order. Repeated and concurrent calls return the same root; fresh conversations and forks do not replace it. |
4599
- | §methods-conversation-worker Workspace lifecycle | `createConversationWorker({ workspaceId, name? })` | Creates a distinct model-origin root worker with empty history: a fresh conversation over the same world, not a fork or the stable default. |
4600
+ | §methods-conversation-worker Workspace lifecycle | `createConversationWorker({ workspaceId, name?, parentWorkerId?, owner? })` | Creates a distinct model-origin worker with empty history. A root may name a registered owner; a child names its parent and inherits ownership. Supplying both parent and owner is invalid ({§worker-owner-creation}). |
4601
+ | Worker ownership | `registerWorkerOwner(workspaceId, { address, tools, interactive })` | Declares or replaces an owner's supported client-tool names and whether a person attends it, in that workspace; `_plurnk` is reserved. |
4602
+ | Worker ownership | `claimWorkerOwner({ workspaceId, workerId, owner })` | Claims only runtime-owned work under {§worker-ownership}, returning its effective owner. The owner must already be registered. |
4603
+ | Runtime actor | `ensureRuntimeWorker(workspaceId)` | Returns the workspace's `_plurnk` actor, creating it if absent; it always retains runtime ownership. |
4600
4604
  | Workspace lifecycle | `forkWorker({ workspaceId, workerId, name? })` | Creates a child worker that branches the source worker's history while sharing workspace state. |
4601
4605
  | §methods-workspace-rename Workspace metadata | `renameWorkspace(workspaceId, name)` | Changes only the world's unique mutable name; workers, log, and membership remain intact. |
4602
4606
  | §methods-workspace-prompts Workspace metadata | `listPrompts(workspaceId, limit?, workerId?)` | Returns nonempty loop-seed prompts a client addressed to the workspace's model workers, newest-first; `workerId` narrows to one worker. Authorship is the seed message's address ({§message-arrival}): a worker-issued seed (WORK, FORK, SEND to a worker) has none and is never history, whichever worker it seeded; a client prompt at a forked conversation worker is. An omitted limit is `PLURNK_SERVICE_PROMPTS_PAGE`. |
@@ -4616,7 +4620,6 @@ already durable on the loop remains authoritative:
4616
4620
  |-----------------------|------------------------------------------------------|--------------------------------|--------------------------------------|
4617
4621
  | Provider/model | The resolved request selection must still agree. | Fold. | 409 provider conflict. |
4618
4622
  | `maxTurns` | Keep the durable ceiling. | Fold. | 409 turn-ceiling conflict. |
4619
- | `policy` request | Keep the complete durable loop policy. | Fold. | 409 policy conflict. |
4620
4623
 
4621
4624
  The conflict names both selections and directs the caller to cancel or conclude
4622
4625
  the loop before changing configuration. A newly enqueued loop instead persists
@@ -4675,7 +4678,8 @@ independent demands, and harness-authored initialization obeys workspace policy.
4675
4678
  `workspaces.settings.capabilities` is the one mutable access policy under the
4676
4679
  service ceiling. Every actor, including an existing child, uses the current
4677
4680
  workspace policy; neither creation nor delegation snapshots authority. Workers
4678
- own their log and scratchpad, not tools or resource grants. Intrinsic source
4681
+ own their log and scratchpad, not tools or resource grants; their owner decides only
4682
+ which interaction runtimes they are offered ({§owner-interaction-ring}). Intrinsic source
4679
4683
  mutability, workspace separation, and proposal approval remain distinct
4680
4684
  contracts. Input is validated before persistence; malformed stored policy fails
4681
4685
  at its reader with the workspace coordinate and cause.
@@ -4689,7 +4693,8 @@ or observation, existing workers reconcile generated references against it. It r
4689
4693
  conversation worker and returns the complete fresh projection.
4690
4694
 
4691
4695
  §question-tool **The native request-user-input tool.** Core registers one
4692
- in-process `question` runtime at boot. Its body is the MCP2 2026-07-28
4696
+ in-process `question` runtime at boot and offers it only to a worker whose owner
4697
+ would receive it ({§owner-interaction-ring}). Its body is the MCP2 2026-07-28
4693
4698
  form-elicitation shape verbatim — `{ message, requestedSchema }`. An
4694
4699
  optional literal target is a descriptive label only: it neither routes the
4695
4700
  question nor changes the body or recipient. The
@@ -4718,6 +4723,20 @@ weights, and catalog text agree. Turn0 surveys that same catalog. Dispatch evalu
4718
4723
  same descriptor and policy cascade at the operation boundary, never at
4719
4724
  registration; there is no separate per-tool availability system.
4720
4725
 
4726
+ §owner-interaction-ring **An interaction tool is offered only where an answer can come
4727
+ from.** An asking worker adds one ring inside that cascade, derived live from its owner
4728
+ and never stored. It denies each interaction runtime whose tool the owner would not
4729
+ receive under {§client-interaction-routing}; an interaction runtime asks under its own
4730
+ tag, as `question` does.
4731
+
4732
+ | Surface | With the ring |
4733
+ | --- | --- |
4734
+ | Turn 0 survey and the worker's FIND of the shared reserved tree | The runtime's documents are absent, from counts and folder summaries too. |
4735
+ | Dispatch | 403 `capability-denied` naming scope `owner`, with the recovery pinned in {§pinned-wording-core}. |
4736
+ | Workspace capability projection and reserved-tree materialization | Unaffected: they take no worker. |
4737
+
4738
+ A FIND or READ by exact path is not filtered.
4739
+
4721
4740
  §model-catalog **Model discovery is a bounded local projection, not provider
4722
4741
  activity.** Core composes the release-pinned Models.dev snapshot with
4723
4742
  provider-owned `{§model-catalog-readiness}` and {§provider-effort}.
@@ -4913,25 +4932,28 @@ flowchart LR
4913
4932
 
4914
4933
  ### §packet-wire-envelope The wire envelope
4915
4934
 
4916
- The packet reaches the provider as a transcript, under the roles the model was tuned on, its bytes unchanged:
4935
+ The packet reaches the provider as two messages. No emission is replayed under `assistant`.
4917
4936
 
4918
4937
  | Message | Role | Content |
4919
4938
  |:--|:--|:--|
4920
4939
  | 1 | `system` | the system slot, as rendered |
4921
- | 2, 4, … | `user` | the user slot's text up to and including the record of the next placed emission that delivers text ({§emission-row}); the first opens with `## Log` |
4922
- | 3, 5, … | `assistant` | that row's frozen emission projection less its NOTE and WAIT blocks, as the worker's own message ({§emission-row}) |
4923
- | last | `user` | the records after the last emission delivered, then the remaining user sections in {§packet-cache-monotone} order; native parts ride here ({§packet-attachment-parts}) |
4924
-
4925
- Only role boundaries are added: joined by blank lines, the user messages are the user slot's
4926
- bytes, in record order. An emission is placed exactly when its row is present in the final log
4927
- section, so curation governs the transcript: a KILLed emission row, or one a trusted transform
4928
- removed ({§packet-extension-transform}), takes its emission with it, and a log without emission rows
4929
- is one user message. An emission of only NOTE and WAIT delivers nothing, so its record runs on
4930
- into the next user message. The Worker block and the status clump always follow the log, so a
4931
- request never ends on an emission, and the projection refuses one that would. The digest's packet
4932
- artifacts record the sections, and `.wire.json` reconstructs their text-message
4940
+ | 2 | `user` | the user sections in {§packet-cache-monotone} order; native parts ({§packet-attachment-parts}) precede the optional final {§previous-emission} section |
4941
+
4942
+ The digest's packet artifacts record the sections, and `.wire.json` reconstructs their text-message
4933
4943
  envelope ({§share-packet-names}); it is not a serialized HTTP capture.
4934
4944
 
4945
+ ### §previous-emission Complete, optional continuity
4946
+
4947
+ | Concern | Contract |
4948
+ |---|---|
4949
+ | Selection | Only the immediately preceding completed model inference turn in this loop. Its admitted content program must have no syntax errors and its emission row must remain active. Never search backward for an older eligible program. Runtime failures do not disqualify it. |
4950
+ | Absence | No preceding model turn, rejected or syntactically partial emission, empty or reasoning-only turn, or retired emission row means no section. Initialization and other loops supply none. |
4951
+ | Content | The frozen canonical admitted content program, whole: every body, NOTE, WAIT and reply retained. No placeholders, redaction, preview or nested-body interpretation. Reasoning operations remain in their own source and receipts. |
4952
+ | Placement | `## Previous Emission`, singular, is the final user section and final transmission content, after native parts when present. Nothing follows it. It is replaced each turn, never inserted into the reusable log prefix. |
4953
+ | Authority | Core attaches this section after trusted section transforms; the extension seam does not rewrite the frozen program. The active emission row owns its curation lifetime. |
4954
+ | Capacity | At the hard context wall, omit the section whole before suppressing any result body or native part ({§context-own-rows-fit}). Omission lasts through rebuilds of that request; the next request decides afresh. No source, receipt, or stored historical packet is changed. |
4955
+ | Accounting | Its text is charged once within the user slot; the emission row charges only its record. |
4956
+
4935
4957
  ### §packet-cache-monotone Default order and cache locality
4936
4958
 
4937
4959
  Conditional absence never reorders the surviving default sections.
@@ -4950,12 +4972,13 @@ Conditional absence never reorders the surviving default sections.
4950
4972
  | 10 | user | `budget` | `Context Curation`; omitted when capacity is unknown. |
4951
4973
  | 11 | user | `messages` | `Open Messages`: immutable message addresses and causal sources ({§message-arrival}). |
4952
4974
  | 12 | user | `recap` | Optional authored operational recap. |
4975
+ | 13 | user | `previous-emission` | Complete previous program, if eligible and within capacity ({§previous-emission}); always last. |
4953
4976
 
4954
4977
  The order favors prefix-cache locality where semantics permit: the definition
4955
4978
  and privileged policy lead operator notes, while the append-mostly
4956
- log leads the volatile user-status clump. An emission sits at the row that announces it
4957
- ({§packet-wire-envelope}), so the reusable prefix runs through every retained emission, and
4958
- retiring one breaks the prefix at its row like any other curation. It does **not** claim that every system byte is
4979
+ log leads the volatile user-status clump. The previous program follows that clump, so replacing it
4980
+ does not rewrite the log prefix. Retiring an emission row breaks the prefix at its row like any
4981
+ other curation. It does **not** claim that every system byte is
4959
4982
  immutable or that the complete packet is globally monotone in volatility:
4960
4983
  operator notes and policies can change. Trust is a separate
4961
4984
  admission rule. The system slot contains trusted control-plane material;
@@ -4971,8 +4994,8 @@ Each initial or returned list passes the schemes-owned validator, including
4971
4994
  unique-name enforcement, before the next transformer or renderer. Each
4972
4995
  transformer may inspect the section content and add, remove, or reorder
4973
4996
  sections. It receives no separate engine, database, actor, or request context.
4974
- Emission placement reads the final log section, so a transform that removes an
4975
- emission row's record removes its emission from the transcript ({§packet-wire-envelope}).
4997
+ Core then attaches the optional frozen previous program ({§previous-emission}); transforms own
4998
+ section drafts, not the worker's source evidence.
4976
4999
 
4977
5000
  This is strictly a trusted in-process seam, admitted through the common extension
4978
5001
  trust gate; an external client action cannot invoke it. Whole-list transformation is
@@ -4986,23 +5009,39 @@ time of measurement.
4986
5009
 
4987
5010
  | Fact | Owner and unit | Time | Contract |
4988
5011
  |:-----|:---------------|:-----|:---------|
4989
- | Core curation weight | `contentWeight = ceil(chars/2)` over channel content, canonical log bodies, rendered packet slots, and placed emissions | Write/build | Stable, model-independent measure; never a tokenizer claim. The model meets it as `tokens`. |
5012
+ | Core curation weight | `contentWeight = ceil(chars/2)` over channel content, canonical log bodies, rendered packet slots (including the optional previous emission) | Write/build | Stable, model-independent measure; never a tokenizer claim. The model meets it as `tokens`. |
4990
5013
  | §tokenomics-context-envelope-admission Provider input capacity | Provider model limits, the configured output reservation and the output floor, in provider tokens | Before every logical request | The reservation `min(maxInputTokens, contextWindow − outputBudget)` sizes the room; the wall `min(maxInputTokens, contextWindow − outputFloor)` decides admission ({§provider-capacity-admission}). The provider alone measures the complete request and admits it — with the grant flexed when the prompt is over the reservation — defers it, or rejects it at the wall. |
4991
5014
  | Provider generation envelope | Provider response grant and optional reasoning subset, in provider tokens | Before every logical request | The reservation includes hidden reasoning; its strict reasoning subset is never additive. The response grant follows {§provider-flexed-allowance}. |
4992
5015
  | Provider usage and cost | Provider-reported input/output/cache/reasoning tokens and monetary evidence | After every physical request | Durable physical-request forensics under {§provider-usage}; never curation state or a preflight estimate. |
4993
5016
 
4994
- - §context-budget **One room.** The budget is the provider's input capacity converted to curation weight under {§tokenomics-calibrated-readout}, and it bounds the whole packet — teaching, catalog, log and gauge together. Nothing on the service panel sizes a part of it: no projection share, no preview page, no pressure threshold. Within a loop the room never shrinks: a packet's budget is the greater of the calibrated capacity and the loop's high-water budget for that input capacity, so a refined conversion can enlarge the room but never turns a packet the model already answered into an overflow.
5017
+ §context-admission **One request through the room.** Core fits the packet to the wall on its estimate, marks it over budget, then has the provider measure the exact request before it is sent. Shedding is the same at both measures, and only a packet that cannot fit even as receipts ends its loop.
5018
+
5019
+ ```mermaid
5020
+ flowchart TD
5021
+ build["Build the packet at the budget"] --> wall{"Estimate over the wall?"}
5022
+ wall -- yes --> shed["Shed: the previous program whole, then the newest rows bodiless"]
5023
+ shed --> wall
5024
+ wall -- no --> over{"Over budget, no row yet?"}
5025
+ over -- yes --> row["One over-budget row at the head of the turn"]
5026
+ row --> wall
5027
+ over -- no --> assess{"The provider assesses the exact request"}
5028
+ assess -- "admit or defer" --> send["Send"]
5029
+ assess -- "exact refusal" --> shed
5030
+ shed -. "nothing left to take" .-> overflow["window-overflow 413, no inference request"]
5031
+ ```
5032
+
5033
+ - §context-budget **One room.** The budget is the provider's input capacity through the current calibration, `floor(inputCapacity / factor)` ({§tokenomics-calibrated-readout}), and it bounds the whole packet — teaching, catalog, log and gauge together. Nothing on the service panel sizes a part of it: no projection share, no preview page, no pressure threshold. The budget follows the calibration both ways; a room that shrinks under the model is announced by the over-budget row ({§context-over-budget-row}), never hidden by keeping a larger figure.
4995
5034
  - §context-gauge **One gauge.** Every packet carries one `## Context` section holding one JSON object: `tokens`, the packet's weight as rendered; `budget`, the room; and `largest`, the `PLURNK_SERVICE_BUDGET_LARGEST_ITEMS` heaviest retained log rows as `{path, tokens}` ranked by weight then path. Each log row heading carries its own `tokens`. The gauge is state: it never presents `budget − tokens` as response allowance, it carries no mandate, and it reads the same at one percent as at ninety-nine; over budget, `largest` is as complete as under it, since those rows are the handles the row asks the model to use ({§context-over-budget-row}).
4996
- - §context-fit **One rule: whole if it fits, otherwise as much as fits, then its size and address.** A result the model asked for — a READ without a scope, a FIND, an exec result delivered on close — arrives complete when its weight fits within `budget − tokens` as of that turn, less the room the turn still owes — one receipt's measured weight for each result still to land in it and the head of the emission that answers it, so that the receipts of a batch and the answer always fit. Otherwise the complete result stays where it was read from and the row is a `413` receipt naming the result's size, its weight, the remaining budget, and the two verbs: `READ` a range, or `KILL` first. A READ of lines carries, above its receipt, the longest prefix of those lines whose row fits that room — the lines with their anchors, the returned range closed on the last line delivered, and the receipt naming the lines delivered; a result whose first line does not fit, a FIND, a stream page and a byte view are bodiless receipts. An arrival the model did not ask for — an inbound `SEND`, a child's conclusion — takes the same bodiless receipt as a fact, not an error: its size and its address. Explicit scopes (`<a,b>`, a pattern, `<1,-1>`) are exact and take the same test. Three cuts are automatic and each is named where it happens: the first page of what came back unasked in size ({§markerless-first-page}), the cut at the room in the receipt, and an emission row's head ({§emission-row}); nothing else is previewed, projected, withheld or paged on the model's behalf.
5035
+ - §context-fit **One rule: whole if it fits, otherwise as much as fits, then its size and address.** A result the model asked for — a READ without a scope, a FIND, an exec result delivered on close — arrives complete when its weight fits within `budget − tokens` as of that turn, less the room the turn still owes — one receipt's measured weight for each result still to land in it and the emission record that answers it, so that the receipts of a batch and the answer always fit. Otherwise the complete result stays where it was read from and the row is a `413` receipt naming the result's size, its weight, the remaining budget, and the two verbs: `READ` a range, or `KILL` first. A READ of lines carries, above its receipt, the longest prefix of those lines whose row fits that room — the lines with their anchors, the returned range closed on the last line delivered, and the receipt naming the lines delivered; a result whose first line does not fit, a FIND, a stream page and a byte view are bodiless receipts. An arrival the model did not ask for — an inbound `SEND`, a child's conclusion — takes the same bodiless receipt as a fact, not an error: its size and its address. Explicit scopes (`<a,b>`, a pattern, `<1,-1>`) are exact and take the same test. Result cuts are named where they happen: the first page of what came back unasked in size ({§markerless-first-page}) and the cut at the room in the receipt. The optional previous program is whole or absent ({§previous-emission}), never a cut.
4997
5036
  - §context-verbs **The verbs are the curation.** `READ` with a range takes a piece of what did not fit ({§read-selection-projection}); `NOTE` keeps what the model learned; `KILL` retires rows, ranges and bodies ({§log-kill-scope}) and may carry what it keeps of them as its own NOTE ({§log-kill-distillation}). The daemon adds no other mechanism.
4998
5037
  - §context-over-budget-row **Over budget is a row and a request, not an ending.** A packet whose weight exceeds `budget` is still submitted when it fits the window ({§context-wall}), and it carries one `_plurnk` error row at the head of its turn, `engine/context/packet-exceeds-budget` 413: "Context exceeds budget. YOU MUST ONLY KILL, MOVE or NOTE this turn." The row is the mandate and carries no figure: the gauge is the one home for `tokens` and `budget` ({§context-gauge}), and it shows the packet over with the row in it. The row recurs on every packet that is over, and nothing is hidden to make the packet smaller; the response grant is the window's remainder above the provider's output floor ({§provider-output-floor}). The limit protects a loop; it never ends one.
4999
- - §context-wall **Only the window ends a loop.** The wall is the provider's physical input limit: `maxInputTokens`, and the context window less the output floor ({§provider-output-floor}); the packet's tokens are its weight through the calibration factor ({§tokenomics-calibrated-readout}), so an uncalibrated ruler only overestimates. A packet over the wall first lands its newest rows bodiless ({§context-own-rows-fit}); a packet that cannot fit the wall even as receipts terminalizes with the exact `engine/context/window-overflow` 413 Problem without provider I/O. An unknown window has no wall ({§tokenomics-window-unpollable-deliberate}).
5000
- - §context-own-rows-fit **The model's own rows take the same receipt.** When a packet would exceed the wall, its newest rows that still carry a body or a native part — NOTEs, receipts, delivered results, pictures — render bodiless in the fit rule's receipt shape, newest first and only as many as it takes for the packet to fit: the heading, the facts, and `size`, the lines and tokens of what the row stood for. An emission row is never taken, so its head-cut program keeps the turn legible, and a row whose receipt would weigh no less than its body stays whole. Every body stays stored and readable at its address, the verbs restore the room ({§context-verbs}), and each packet decides afresh from its own newest rows. The daemon chooses nothing to summarize and hides nothing silently.
5001
- - §context-output-selection **Retired, column kept.** Migration 6's `output_admission_turn_id` recorded first-presentation admission under the former withholding rule; the rule is gone and the column stays in the schema unread, as released migrations are frozen ({§db-migrations}).
5038
+ - §context-wall **Only the window ends a loop.** The wall is the provider's physical input limit: `maxInputTokens`, and the context window less the output floor ({§provider-output-floor}). A request over the wall sheds ({§context-own-rows-fit}) and is measured again ({§context-wall-measure}); a packet that cannot fit even as receipts ends its loop with the exact `engine/context/window-overflow` 413 Problem, and no inference request is made. An unknown window has no wall ({§tokenomics-window-unpollable-deliberate}).
5039
+ - §context-wall-measure **The wall measures the request that will be sent.** The estimate is the packet's weight through its calibration factor ({§tokenomics-calibrated-readout}) and is never assumed to overestimate. A packet that fits by the estimate is assessed by the provider as the exact wire request ({§provider-capacity-admission}): an admission sends it; an exact refusal is the wall's verdict, and the request sheds by the refusal's excess and is assessed again. A provider that can only estimate defers, and the upstream provider is then the capacity oracle: its refusal ends the loop with the provider's own 413.
5040
+ - §context-own-rows-fit **The model's own rows take the same receipt.** When a request is over the wall by either measure ({§context-wall-measure}), its newest rows that still carry a body or a native part — NOTEs, receipts, delivered results, pictures — render bodiless in the fit rule's receipt shape, newest first and only as many as it takes for the request to fit: the heading, the facts, and `size`, the lines and tokens of what the row stood for. The optional previous program has already been omitted whole ({§previous-emission}); an emission record has no body to take. A row whose receipt would weigh no less than its body stays whole. Every body stays stored and readable at its address, the verbs restore the room ({§context-verbs}), and each packet decides afresh from its own newest rows. The daemon chooses nothing to summarize and hides nothing silently.
5002
5041
  - §tokenomics-weight-stored-at-write **Curation weight, stored at write.** `entry_channels.weight` weighs the complete channel content. `log_entries.weight` weighs the complete canonical `LogBody` content before coordinate and packet presentation; persistence `tx`/`rx` envelopes contribute nothing merely by existing, and proposal settlement recomputes the value when the canonical result changes. Bodyless rows therefore weigh zero. The stored number is a stable content-depth measurement, not a provider-token prediction. `entry_channels.lines` is the channel's line count beside it, a stored generated column SQLite keeps on every write as the persisted mirror of {§logical-line-count} (a trailing newline terminates the last line; empty content has none), so a catalog lists extent without reading bodies.
5003
- - §tokenomics-render-weight-budget **Packet curation budget.** `tokens` measures the *complete assembled packet* after section transforms and readout substitution, its rendered slots and the emissions it places ({§emission-row}); it is not a sum of stored row weights ({§tokenomics-weight-stored-at-write}). Core measures minimum-width probes, monotonically expands fields that do not fit, then right-aligns final values into those widths; final substitution is length-invariant and the displayed total equals the stored request weight. Receipt, FIND-item, pressure-inventory, total, and ceiling figures all use the same curation ruler. A `SUM` of stored content weights measures a different artifact and cannot substitute for packet render weight.
5004
- - §tokenomics-calibrated-readout **Convert capacity, never content costs.** Before packet assembly, Core obtains the answering model's last five settled emission responses pairing a measured packet weight with a provider-reported prompt count. The conversion factor is `sum(reported) / sum(weight)`; fewer than three samples use 1. `floor(inputCapacity / factor)` converts provider capacity into curation units; a loop's budget is that figure or its high-water for the same capacity, whichever is greater ({§context-budget}). Zero means no whole curation unit fits; unknown input capacity remains `null`. The built packet captures this allowance once for its readout, pressure inventory, overflow admission, and persisted client gauge. Later responses cannot change that packet's allowance. Samples are model-keyed, not worker-local; a model with no samples starts at 1. Calibration never changes stored weights, rendered receipt costs, or the immutable request history ({§tokenomics-agnostic-ruler}).
5005
- - §tokenomics-window-partition **One capacity derivation; no service-side token budget.** The provider owns model limits, the configured output reservation, and each call's response grant. Its resolved `inputCapacity` supplies the physical denominator exposed to clients and the boundary conversion into curation units ({§tokenomics-calibrated-readout}). Core shapes context in curation units; provider request-shaped evidence alone admits or rejects physical I/O. `PLURNK_SERVICE_PROMPT_BUDGET`, `PLURNK_SERVICE_SAFETY`, and the additive reasoning/completion reserve knobs are retired; local and custom deployments tune context window, total output budget, optional reasoning subset, and prompt-projection percentage at their owning layers.
5042
+ - §tokenomics-render-weight-budget **Packet curation budget.** `tokens` measures the *complete assembled packet* after section transforms and readout substitution, its rendered slots (including {§previous-emission}) and native attachments; it is not a sum of stored row weights ({§tokenomics-weight-stored-at-write}). Core measures minimum-width probes, monotonically expands fields that do not fit, then right-aligns final values into those widths; final substitution is length-invariant and the displayed total equals the stored request weight. Receipt, FIND-item, pressure-inventory, total, and ceiling figures all use the same curation ruler. A `SUM` of stored content weights measures a different artifact and cannot substitute for packet render weight.
5043
+ - §tokenomics-calibrated-readout **Convert capacity, never content costs.** Before packet assembly, Core obtains the answering model's last five settled emission responses pairing a measured packet weight with a provider-reported prompt count. The conversion factor is `sum(reported) / sum(weight)`; fewer than three samples use 1. `floor(inputCapacity / factor)` converts provider capacity into curation units, the budget ({§context-budget}); the same factor converts the packet's weight into the wall's estimate ({§context-wall-measure}). Zero means no whole curation unit fits; unknown input capacity remains `null`. The built packet captures this allowance and its factor once for its readout, pressure inventory, wall estimate, and persisted client gauge. Later responses cannot change that packet's allowance. Samples are model-keyed, not worker-local; a model with no samples starts at 1. Calibration never changes stored weights, rendered receipt costs, or the immutable request history ({§tokenomics-agnostic-ruler}).
5044
+ - §tokenomics-window-partition **One capacity derivation; no service-side token budget.** The provider owns model limits, the configured output reservation, and each call's response grant. Its resolved `inputCapacity` supplies the physical denominator exposed to clients and the boundary conversion into curation units ({§tokenomics-calibrated-readout}). Core shapes context in curation units; provider request-shaped evidence alone admits or rejects physical I/O. Local and custom deployments tune context window, total output budget and optional reasoning subset at their owning layers.
5006
5045
  - §tokenomics-window-unpollable-deliberate **Unknown provider capacity stays unknown.** When the provider cannot derive `inputCapacity`, there is no budget: the gauge carries `tokens` alone, every result arrives whole, and the provider remains the capacity oracle ({§provider-capacity-admission}). Core never invents a stand-in.
5007
5046
 
5008
5047
  §tokenomics-client-gauge **Clients receive curation and physical occupancy as separate pairs.** `loop/terminated.usage` carries latest packet-bearing model-turn `curationWeight`/`curationBudget` and latest-emission-call `contextTokens`/`contextCapacity`; each unknown fact is `null`. The curation pair is the packet's measured weight and captured allowance, exactly as displayed to the model, not a recalculation using newer usage evidence. Packetless chronology cannot erase an assembled-request gauge. Both physical facts bind to that same call: a preflight rejection may report capacity while its absent physical request leaves `contextTokens=null`, never borrowed from an earlier call. Clients never divide provider-reported physical tokens by Core curation weight. `providers.list` exposes each instantiated alias's `inputCapacity`. A model switch replaces the latest-turn facts together; aggregate provider accounting remains cardinal monetary evidence, not a gauge input.
@@ -5029,7 +5068,7 @@ execute, enter model history, or become a later request's successful output.
5029
5068
  | 200 | Messages answered, results observed, held work settled |
5030
5069
  | 499 | Worker-scope or client cancellation |
5031
5070
  | 429 | Turn allowance exhausted |
5032
- | 413 | A result that does not fit ({§context-fit}), a packet over the budget ({§context-over-budget-row}: a row; the request still goes), a packet over the window ({§context-wall}), or the provider's own rejection of an exact prompt over its wall |
5071
+ | 413 | A result that does not fit ({§context-fit}), a packet over the budget ({§context-over-budget-row}: a row; the request still goes), a packet that cannot fit the window even as receipts ({§context-wall}), or an upstream provider's own refusal ({§context-wall-measure}) |
5033
5072
  | 500 / 508 | Strike threshold or invalid-emission exhaustion / crossing strike caused by a cycle |
5034
5073
  | 504 | Loop timeout or exec-timeout restamp |
5035
5074
 
@@ -5147,9 +5186,10 @@ their boundaries ({§log-wire-format}).
5147
5186
  | `errors` | user | JSON status/log-path pointers | {§operation-results} |
5148
5187
  | `notices` | user | Terse observation bullets | {§notice-drain-on-read} |
5149
5188
  | `git` | user | Working-tree state in a NOTE blockquote | {§packet-cache-monotone} |
5150
- | `budget` | user | JSON curation usage and ceiling; pressure guidance when needed | {§context-gauge} |
5189
+ | `budget` | user | JSON curation usage and ceiling | {§context-gauge} |
5151
5190
  | `messages` | user | JSON pointers to the loop's unanswered immutable messages, path and source | {§message-arrival} |
5152
5191
  | `recap` | user | Optional authored operational recap | {§recap} |
5192
+ | `previous-emission` | user | Complete preceding content program, whole or absent; always last | {§previous-emission} |
5153
5193
 
5154
5194
  §packet-stored-shape **A model packet preserves the rendered request and, only
5155
5195
  when an emission is admitted, its response.** Core assembles and measures the
@@ -5162,7 +5202,7 @@ leaves the request-only record, while rejected exchanges remain in their
5162
5202
  | Turn state | `turns.packet` (the bag) + `turn_sections` rows |
5163
5203
  | ----------------------------- | ----------------------------------------------- |
5164
5204
  | No admitted model request (including initialization and local capacity rejection) | SQL `NULL`, no rows |
5165
- | Request assembled | `{ weight, attributions }` + the sections as items; the emissions it placed stay in their rows ({§emission-row}) |
5205
+ | Request assembled | `{ weight, attributions }` + the sections as items; the complete previous-emission section, or its empty content, is stored with the request ({§previous-emission}) |
5166
5206
  | Response admitted | `{ weight, attributions, assistant, assistantRaw }` + the sections as items |
5167
5207
 
5168
5208
  §packet-items **Sections are rows over content-addressed items; the bag never holds them.**
@@ -5208,24 +5248,6 @@ of section weights for the rendered request weight.
5208
5248
 
5209
5249
  §definition-table-projection The authored `plurnk.md` remains human-aligned. Its `definition` section deterministically removes Markdown table-cell padding and shortens separator cells to three dashes before extension transforms, measurement, storage, and wire rendering; alignment colons survive, while fenced blocks and all non-table whitespace remain exact.
5210
5250
 
5211
- §lexicon **Vocabulary follows the standard of its audience.**
5212
-
5213
- | Layer | Rule |
5214
- |--------------------------|------|
5215
- | Operator, wire, storage | Use the applicable industry term. Provider quantities follow the OpenAI vocabulary where it is standard: `contextWindow`, `reasoning`, `completion`, `finish_reason`, and usage nouns. |
5216
- | Core lifecycle | Use the exact Workspace → Worker → Loop → Turn → Op hierarchy in {§lifecycle-terms}. An AG-UI Run or thread is always protocol-qualified. |
5217
- | Model-facing packet | Use the model's training distribution: operations mirror HTTP and shell, while log records use ordinary Markdown headings, strict JSON metadata, and text coordinates. Renaming this vocabulary to internal API terminology would discard useful resonance for a standard the model never sees. |
5218
-
5219
- | PLURNK-native term | Why it remains |
5220
- |--------------------------------|----------------|
5221
- | `worker` / `loop` / `turn` | The process hierarchy in {§lifecycle-terms}; unqualified `run` names no internal entity. |
5222
- | `packet` | The assembled address space, a kernel concept rather than merely a provider request. |
5223
- | `costUsd` | No standard cost field exists; the explicit currency avoids implied units, and the value remains an exact decimal string. |
5224
- | `curationWeight` / `curationBudget` | Explicitly distinguish Core's model-independent context-shaping facts from physical provider tokens. |
5225
- | the `chars/2` curation ruler | Model-agnostic by design ({§tokenomics-agnostic-ruler}); it is never presented as a tokenizer. |
5226
-
5227
- Retired terms stay retired: the lexicon guard rejects `thinking`, the unqualified `session` noun, `contextSize`, `decodeBudget`, and moved partition-knob names. <!-- lexicon-allow: this sentence enumerates the retired terms -->
5228
-
5229
5251
  §body-projection **One full body, one readable view, one packet projection.** Every durable log row has one canonical full body resolved from its stored tx/rx envelope by `LogBody`. READ and FIND over `log:///`, persistent search derivation, and packet rendering apply the same deliberate trimming under {§log-readable-projection}. Packet rendering additionally applies initial suppression and these presentation bounds:
5230
5252
 
5231
5253
  | row producer | ordinary visible projection |
@@ -5239,7 +5261,7 @@ Retired terms stay retired: the lexicon guard rejects `thinking`, the unqualifie
5239
5261
  | a row the wall took | its size and address, no body and no native part ({§context-own-rows-fit}) |
5240
5262
  | bodyless row | heading and any facts; no coordinate lines; `tokens` includes any selected native part |
5241
5263
 
5242
- §markerless-first-page **What the model did not ask for in size arrives as its first page.** A READ or FIND without a scope, a stream delivered when a command closes, a peer worker's message, an environment delta, an extension's body and a rejected emission shown back all arrive as their first page: `PLURNK_SERVICE_PREVIEW_LINES` lines and at most `PLURNK_SERVICE_PREVIEW_CHARS` characters, in the projection's own units — lines of text, bytes of a byte view ({§read-bytes}), results of a FIND — with one fact in one notation stating the page and the whole, `<1,100> of 4812 lines` (`range` on a retrieval, `preview` on a stored body, {§packet-extent-metadata}). What the model authored stands whole: a NOTE, a WAIT, the body of its SEND, the prompt of its WORK or FORK, and the command it handed an executor. Its own reasoning, when it lands as a row, stands whole only when it fits the row's page and otherwise takes its last page ({§reasoning-row}). What it asked for exactly stands whole: an explicit scope — `<a,b>`, `<1,-1>`, a pattern — and an EDIT receipt, bounded by its own rule ({§edit-result-receipt-truth}). The operator's message keeps its own rule ({§message-projection}) and an emission row its head ({§emission-row}); those are the only exceptions, so the model can reason about every page the same way. A page lands under {§context-fit} like any result: whole when it fits the remaining budget, otherwise the prefix and receipt. Retrievals take their page at the source, before storage, and the packet applies no second cut to a stored result; a stored body takes its page in the packet, and the rest stays readable at the row's address. No operation carries a page size of its own.
5264
+ §markerless-first-page **What the model did not ask for in size arrives as its first page.** A READ or FIND without a scope, a stream delivered when a command closes, a peer worker's message, an environment delta, an extension's body and a rejected emission shown back all arrive as their first page: `PLURNK_SERVICE_PREVIEW_LINES` lines and at most `PLURNK_SERVICE_PREVIEW_CHARS` characters, in the projection's own units — lines of text, bytes of a byte view ({§read-bytes}), results of a FIND — with one fact in one notation stating the page and the whole, `<1,100> of 4812 lines` (`range` on a retrieval, `preview` on a stored body, {§packet-extent-metadata}). What the model authored stands whole: a NOTE, a WAIT, the body of its SEND, the prompt of its WORK or FORK, and the command it handed an executor. Its own reasoning, when it lands as a row, stands whole only when it fits the row's page and otherwise takes its last page ({§reasoning-row}). What it asked for exactly stands whole: an explicit scope — `<a,b>`, `<1,-1>`, a pattern — and an EDIT receipt, bounded by its own rule ({§edit-result-receipt-truth}). The operator's message keeps its own rule ({§message-projection}) and emission rows remain folded ({§emission-row}); those are the only exceptions, so the model can reason about every page the same way. A page lands under {§context-fit} like any result: whole when it fits the remaining budget, otherwise the prefix and receipt. Retrievals take their page at the source, before storage, and the packet applies no second cut to a stored result; a stored body takes its page in the packet, and the rest stays readable at the row's address. No operation carries a page size of its own.
5243
5265
 
5244
5266
  READ and FIND own their range or pagination before packet rendering; the packet never applies a second hidden substring bound to their selected result. Reasoning arrives through ordinary scoped READs ({§reasoning-history}). When a visible projection differs from its canonical body, metadata carries `preview` under {§packet-extent-metadata}; complete and fully suppressed bodies omit it. ```` ```READ (log:///<coordinate>/<OP>) ```` selects untrimmed lines in original coordinates under {§log-readable-projection}; the unsuffixed exact shorthand and authoritative suffix behavior are defined by {§log-coordinate-hierarchy}. ```` ```FIND (log:///...) ```` and search match that same readable view. System/policy sections are not log bodies. Notices are transient non-log observations; they share the ordinary line/character bounds but have no durable body or recovery URI.
5245
5267
 
@@ -5290,7 +5312,7 @@ message — an AG-UI message UUID, an A2A address — stays the durable `path` t
5290
5312
  delivery and reply accounting use, and remains addressable in its own scheme; the short form is an
5291
5313
  additional alias. Answering either reaches the same message.
5292
5314
 
5293
- §message-causal-source **Message authorship and delivery are distinct facts.** The harness publishes every arrival row; the row's `source` carries the canonical address of the causal actor. Native WORK, FORK, and directed worker SEND derive `worker://<sender>` from the authenticated sender worker ID. A trusted exterior adapter supplies its own canonical actor address through {§methods-loop-run}: the AG-UI bridge names the client's message under `agui://` ({§agui-run-source}), the inbound A2A adapter under `a2a://`. An absent source is the operator. Attribution persists with the message through the inbox, parking, orphan recovery, restart, and later log projection; model syntax cannot author it. The wire renders the row's `source` in place of its `origin`, which is constant for every arrival, except where the source is the transport that minted this very message, which says nothing the address does not ({§message-short-identity}). The operator's message is then the one arrival with no sender to show, so it renders `"origin": "user"`; a bare arrival would read as the model's own SEND and hide the request it answers. The Open Messages pointer carries the same attribution ({§message-arrival}).
5315
+ §message-causal-source **Message authorship and delivery are distinct facts.** The harness publishes every arrival row; the row's `source` carries the canonical address of the causal actor. Native WORK, FORK, and directed worker SEND derive `worker://<sender>` from the authenticated sender worker ID. A trusted exterior adapter supplies its own canonical actor address through {§methods-loop-run}: the AG-UI module names the client's message under `agui://` ({§agui-run-source}), the inbound A2A adapter under `a2a://`. An absent source is the operator. Attribution persists with the message through the inbox, parking, orphan recovery, restart, and later log projection; model syntax cannot author it. The wire renders the row's `source` in place of its `origin`, which is constant for every arrival, except where the source is the transport that minted this very message, which says nothing the address does not ({§message-short-identity}). The operator's message is then the one arrival with no sender to show, so it renders `"origin": "user"`; a bare arrival would read as the model's own SEND and hide the request it answers. The Open Messages pointer carries the same attribution ({§message-arrival}).
5294
5316
 
5295
5317
  §message-projection **Message storage is unbounded by model context; its arrival in the packet is not.** Core persists every accepted message completely before packet assembly. An arrival renders complete when it fits the remaining budget ({§context-fit}) and otherwise as its size and address, where a READ of the message's log body, scoped or whole, retrieves it. A peer worker's message is received text and arrives as its first page ({§markerless-first-page}). No share of the capacity is set aside for arrivals, and no knob sizes one.
5296
5318
 
@@ -5316,7 +5338,7 @@ their row shape, and their ordering are ordinary FIND projections owned by
5316
5338
  The model's runtime alert surface has two distinct kinds of information:
5317
5339
 
5318
5340
  - **Turn failures are log items.** A failed action and an engine-rail failure are durable `log_entries` rows whose `rx` is an RFC 9457 operation result. They can be scoped-KILLed, retired, and budgeted like every other row. The `errors` section is a derived pointer index over recent `status_rx ≥ 400` rows; it owns no bodies or failure state. Rejected emissions never become accepted turn content; their private response and admission evidence remains in `model_calls` and `turn_attempts`, apart from the bounded recovery `emissionAttempt` under {§invalid-emission-attempts}.
5319
- - **Notices are transient observations.** Progress and non-fatal diagnostics such as `turn_awaiting_model`, `search_progress`, and `grammar_unenforced` may appear once in the packet and broadcast live. They neither substitute for a failure result nor influence scheduling or recovery.
5341
+ - **Notices are transient observations.** Progress and non-fatal diagnostics such as `turn_awaiting_model`, `search_progress`, and `output_unaccounted` may appear once in the packet and broadcast live. They neither substitute for a failure result nor influence scheduling or recovery.
5320
5342
 
5321
5343
  The `log` is durable product truth. The `errors` section points at its failures
5322
5344
  while the separate `notices` section displays transient observations. The two
@@ -5342,12 +5364,12 @@ retain distinct contracts and lifetimes.
5342
5364
  separate item is minted for operation failures. Actionless engine rails mint
5343
5365
  `op='error'` items because no authored operation row exists. Invalid provider
5344
5366
  emissions are outside this channel because they are not turns. A bare
5345
- failure status, a top-level string `error`, or mismatched result/problem
5367
+ failure status or mismatched result/problem
5346
5368
  statuses violate the producer contract and fail hard. Genuine
5347
5369
  engine-internal faults crash and never mint model-facing rows.
5348
5370
  - **Asynchronous work does not weaken the contract.** A stream-producing operation returns its initial `102` after acquisition. At conclusion the subscription stores the exact universal terminal result; `stream/concluded` carries it unchanged; the next ambient terminal READ merges it with the stream payload and preserves its Problem instance, assigning the committed `log:///.../READ` URI only when absent. Timeouts and service cancellations replace the complete terminal result with a new valid 504/499 Problem—they never mutate a status while retaining a contradictory Problem.
5349
5371
  - **Self-explaining rows.** A problem `title` names the stable class and `detail` states the occurrence-specific cause. Producer-known operands belong in factual extensions. `stage` appears only when neighboring stages imply different recovery; `recovery` states one generally valid next action; `retryable` is true only when the producer recommends automatically retrying the identical request. Unknown recovery or retryability is omitted rather than guessed. General workflow teaching stays in the packet rather than being duplicated into every failure. The runtime-neutral writing contract is owned by `@plurnk/plurnk-contracts`.
5350
- - **Exact Problems cross durable and external boundaries.** Scheme capabilities, proposal application, subscription conclusion, loop settlement, AG-UI, clients, digests, and benchmark records preserve the originating Problem object. The model packet alone derives `{§problem-projection}` without mutating that object. An adapter may add a missing durable `instance`, never replace an existing one; it must not rebuild failure truth from `status`, `detail`, `RUN_ERROR`, a scheduler projection, or a legacy string. A failed boundary without a valid Problem is a contract violation and fails hard.
5372
+ - **Exact Problems cross durable and external boundaries.** Scheme capabilities, proposal application, subscription conclusion, loop settlement, AG-UI, clients, digests, and benchmark records preserve the originating Problem object. The model packet alone derives `{§problem-projection}` without mutating that object. An adapter may add a missing durable `instance`, never replace an existing one; it must not rebuild failure truth from `status`, `detail`, `RUN_ERROR`, or a scheduler projection. A failed boundary without a valid Problem is a contract violation and fails hard.
5351
5373
  - **Caught diagnostics are bounded.** Core-owned Problems may include a bounded preview of a caught runtime diagnostic when it states the occurrence-specific cause. `PLURNK_SERVICE_ERROR_DETAIL_LIMIT` owns that model-facing character bound; complete errors remain in daemon diagnostics. Input validation and stable contract failures do not spend this allowance on implementation text.
5352
5374
  - §notice-drain-on-read **Notices** - the few observations that are not log rows render one terse line under their distinct `## Notices` section, never a JSON dump. Packet rendering normalizes whitespace, bounds the producer message with the shared preview limits, and appends any typed position. The notice buffer drains on read; event Notices appear on at most one packet. Parking preserves pending notices for that loop's next packet; another loop cannot consume them. Terminal cleanup, including cancellation while parked, discards undelivered notices. This buffer is process-local, not restart-persistent. Stateful derivation progress and provider availability coalesce in the buffer, so clients observe every checkpoint live while a later model packet receives only the current state under ordinary level filtering.
5353
5375
  - §rail-accounting-private **Rail accounting is private.** Visibility is owned by {§engine-rails}: the model sees concrete failures from admitted turns, never rejected emissions, attempt counts, the strike streak, or cycle detection. Surfacing internal state creates a gamification surface where the model optimizes for engine metrics instead of the task.
@@ -5362,12 +5384,12 @@ retain distinct contracts and lifetimes.
5362
5384
 
5363
5385
  | notice `kind` | Source | Position |
5364
5386
  |---|---|---|
5365
- | `grammar_unenforced` | engine rail verdict, or a forwarded provider transport anomaly such as a discarded-channel escape | content-offset when the observed position maps into content; none for a reasoning-prefix divergence |
5387
+ | `output_unaccounted` | a provider whose billed output tokens exceed those its content and reasoning hold | content-offset at the end of the content |
5366
5388
  | `parse_advisory` | grammar parser — recoverable near-miss which did not invalidate the parsed statements | content-offset into the model's emission |
5367
5389
  | `search_progress` | repository materialization/indexing lifecycle ({§persistent-search-index}); structured phase, count, and percent; `level: info`, `warn` when a completed pass carries failed members ({§derivation-member-failure}), `error` on terminal failure | none |
5368
5390
  | `git_inspection_refused` | engine membership — automatic Git inspection refused a supplied repository whose config declares a `filter.*` program ({§membership-git-hermetic}); names the key; `level: warn`, once per workspace until the key changes or clears | none |
5369
5391
 
5370
- §notice-level **Severity on the wire (`level`, required).** Every `Notice` carries `level: "error" | "warn" | "info"`, set by the **producer** at the emit site. The level is client presentation, not operation status: even an `error` notice cannot terminalize work or substitute for a durable Problem. A forwarded `grammar_unenforced` is `warn`; ordinary lifecycle and progress notices are `info`. Clients color straight off `level` without interpreting the open `kind` vocabulary.
5392
+ §notice-level **Severity on the wire (`level`, required).** Every `Notice` carries `level: "error" | "warn" | "info"`, set by the **producer** at the emit site. The level is client presentation, not operation status: even an `error` notice cannot terminalize work or substitute for a durable Problem. A forwarded `output_unaccounted` is `warn`; ordinary lifecycle and progress notices are `info`. Clients color straight off `level` without interpreting the open `kind` vocabulary.
5371
5393
 
5372
5394
  §operation-result-no-error-scheme Private strike and cycle accounting stays engine-internal ({§rail-accounting-private}). Every failure within an accepted turn - a bounded parse error, failed action, or engine rail - is a LOG ITEM (`log:///<coord>`, `status_rx ≥ 400`) with Problem Details, independently curatable and exactly READable while active. The `errors` section surfaces a derived pointer to each. Rejected emissions stay in the forensic model-call and admission relations. There is **no bespoke `error://` scheme** and no ephemeral per-category failure buffer.
5373
5395
 
@@ -5406,7 +5428,7 @@ USD and token totals with separately named known subtotals across every physical
5406
5428
  calls included. It is the shared exact derivation from the ledger, never a second
5407
5429
  stored fact, so a live watcher accrues running loop cost per turn (#465).
5408
5430
 
5409
- §notice-content-offset-pointer **Content-offset position.** A non-fatal diagnosis on an accepted emission (for example `grammar_unenforced` or `parse_advisory`) carries `position: { type: "content-offset", line, column }` into the model's exact `ops://<worker>/<loop>/<turn>` source; the transcript shows that turn's canonical emission instead ({§emission-row}), so the position names a line of the source, which READ of its address shows. A bounded hard parse error becomes a durable failed operation whose Problem Details preserve its line, column, source, and parser-owned diagnostic. Hard errors that make the frame untrustworthy remain only with their rejected forensic attempt.
5431
+ §notice-content-offset-pointer **Content-offset position.** A non-fatal diagnosis on an accepted emission (for example `output_unaccounted` or `parse_advisory`) carries `position: { type: "content-offset", line, column }` into the model's exact `ops://<worker>/<loop>/<turn>` source; the optional previous-emission section shows a canonical program ({§previous-emission}), so the position names a line of the source, which READ of its address shows. A bounded hard parse error becomes a durable failed operation whose Problem Details preserve its line, column, source, and parser-owned diagnostic. Hard errors that make the frame untrustworthy remain only with their rejected forensic attempt.
5410
5432
  Reasoning normalization notices use source `grammar:reasoning` and omit the
5411
5433
  content-offset position: reasoning coordinates do not address the content source.
5412
5434
 
@@ -5426,7 +5448,7 @@ Schema-backed targets link from that aside to
5426
5448
  preserve the full tool description and raw input schema under
5427
5449
  {§executor-input-schema-preview}; their nested paths do not contribute extra
5428
5450
  Turn0 rows. A schema-backed general runtime uses `<runtime>/input.md`.
5429
- Non-schema targets retain supplemental details in family sections.
5451
+ Non-schema targets retain supplemental details after the framework-owned sections.
5430
5452
  Tool-result/output schemas remain ordinary evidence, not teaching. Unknown-target
5431
5453
  recovery names the published runtime document through the same path owner as
5432
5454
  materialization, including a runtime's declared `resourcesPath`.
@@ -5461,10 +5483,10 @@ with a multiline regex over matching fences), so turn 0 names every tool with it
5461
5483
  signature — one row per tool, paged like every survey. Capability attenuation
5462
5484
  restricts that matcher to the admitted exact tools. No document is delivered
5463
5485
  unasked.
5464
- When the registry supplies {§executor-tool-catalog} definitions, the family
5486
+ When the registry supplies {§executor-tool-catalog} definitions, the runtime
5465
5487
  document links to its sibling `<runtime>.json`. The catalog contains exactly
5466
5488
  the effective tools and is an ordinary `application/json` resource for READ,
5467
- FIND, and JSONPath selection. It follows the same reconciliation as the family
5489
+ FIND, and JSONPath selection. It follows the same reconciliation as the runtime
5468
5490
  document and per-tool schemas; neither the catalog nor its definitions are
5469
5491
  automatically included in turn 0 or expanded-tool surveys.
5470
5492
  Attached tools are capabilities like every other runtime; the model never
@@ -5496,10 +5518,11 @@ The alias is a short name, suggested from the glob. `list` shows each
5496
5518
  definition with what it resolved to — `include` or `exclude`, the pattern, the members it
5497
5519
  admits or removes (count and a bounded sample), and for a model's inclusion the matches the
5498
5520
  repository's ignore rules refused — so the model sees what its glob did and adapts.
5499
- `discover` is introspection, never a catalog: a path answers why it is or is not visible
5500
- (tracked, included by which pattern, a creation record, excluded by which `!glob`, ignored,
5501
- untracked, absent); a glob previews what `add` would include or exclude. Names only, never
5502
- content; nothing is added.
5521
+ `discover` is introspection, never a catalog: its one input, `query`, is a path or a glob. A path
5522
+ answers why it is or is not visible (tracked, included by which pattern, a creation record,
5523
+ excluded by which `!glob`, ignored, untracked, absent); a glob previews what `add` would include
5524
+ or exclude. A query that names neither, blank or a bare `!`, is 400 `query-invalid`. Names only,
5525
+ never content; nothing is added.
5503
5526
 
5504
5527
  §members-configuration *Available definitions.* `PLURNK_MEMBERS_<alias>=<glob>`
5505
5528
  declares one service-origin rule (`!glob` excludes). The shared naming and
@@ -5554,7 +5577,7 @@ same {§operator-config-env-defaults} renderer as the operator command, never th
5554
5577
  effective environment. Native chapter files retain their owners and locations;
5555
5578
  runtime-generated bytes have no invented disk location. Disable/enable,
5556
5579
  shared workspace visibility, and project/global shadowing use the ordinary Skills lifecycle.
5557
- Service-provided skills are not installer targets; removal follows {§skills-remove}.
5580
+ Service-provided skills are host-provided trees, never fetched; removal follows {§skills-remove}.
5558
5581
 
5559
5582
  The catalog and Turn0 describe Functionality under the current workspace
5560
5583
  capability policy and service ceiling. A direct denied attempt receives the
@@ -5590,18 +5613,6 @@ surfaces with its cause and leaves no apparently initialized home.
5590
5613
  After that bootstrap the file is user-owned: edits and deletion persist, and a
5591
5614
  later boot never refreshes or recreates it.
5592
5615
 
5593
- §legacy-home-transition A legacy `~/.plurnk` is never an ambient fallback. If
5594
- legacy state exists while canonical destinations do not, ordinary startup
5595
- fails with the exact `plurnk-service paths migrate` recovery. That explicit,
5596
- idempotent command refuses destination conflicts and a live database owner,
5597
- moves known user configuration and durable SQLite files to their semantic
5598
- homes, byte-verifies the complete copied set before removing any source,
5599
- discards only recognized generated references, and removes the empty legacy
5600
- directory. A pre-commit failure rolls back canonical files and directories
5601
- created by that attempt. Unknown legacy members or simultaneous
5602
- legacy/canonical state fail without guessing. No dual read or dual write survives
5603
- the transition.
5604
-
5605
5616
  §schemes-self-doc-materialization **One generated reference tree per workspace.** `@plurnk/plurnk-schemes` owns `SchemeManifest.documentation` ({§manifest-self-doc}). Each reference carries an H2 `Summary` for ordinary catalog projection. `SchemeRegistry.docs(workspaceId)` resolves corpus-or-manifest documentation, and `referenceEntries` applies workspace capabilities to that source. One workspace materializer deletes vanished contributions and upserts current documents. Concurrent readers share the same reconciliation and outcome; independent workspaces do not serialize together. Ordinary runtime turns maintain {§worker-generated-subtree}, including under read-only workspace policy.
5606
5617
 
5607
5618
  ### §packet-git-status The Git status section — compact repository state
@@ -5975,7 +5986,7 @@ Every Problem code core mints is named here under its family ({§problem-error-c
5975
5986
  | code | status | contract |
5976
5987
  |---|---:|---|
5977
5988
  | `target-required` | 400 | *OP* requires a target path. Recovery: Write the target in parentheses on the opening fence line: `OP (path)`. |
5978
- | `scheme-not-found` | 501 | Scheme '*name*' is not registered. Recovery: ({§unregistered-scheme-recovery}) `` `*host*` is an executor: run ```*host* (*tool*)``` with its input in the body. `` when the address's authority is one, else `Registered schemes: *names*.` |
5989
+ | `scheme-not-found` | 501 | Scheme '*name*' is not registered. Recovery: ({§unregistered-scheme-recovery}) `Registered schemes: *names*.` |
5979
5990
  | `scheme-metadata-unsupported` | 400 | *OP* on '*scheme*' does not accept the [metadata] modifier. |
5980
5991
  | `operation-not-implemented` | 501 | Scheme '*name*' does not implement *OP* (or exec). |
5981
5992
  | `entry-read-not-implemented` | 501 | The '*scheme*' scheme does not provide entry reads. |
@@ -5998,15 +6009,13 @@ Every Problem code core mints is named here under its family ({§problem-error-c
5998
6009
  | `channel-required` | 400 | The '*scheme*' scheme has no default channel. Recovery: Address a named channel with a URI fragment. |
5999
6010
  | `binary-source-unsupported` | 415 | Channel #*name* is binary and its scheme keeps no bytes to transfer. |
6000
6011
  | `metadata-unsupported` | 400 | *OP* takes only the env option; '*key*' is not one. |
6001
- | `worker-name-conflict` | 409 | Worker '*name*' already exists in this workspace. Recovery: To give '*name*' more work, write `SEND (worker://_name_)` with the task as the body; to start another worker, choose a name no worker holds. |
6012
+ | `worker-name-conflict` | 409 | Worker '*name*' already exists in this workspace. |
6002
6013
  | `no-operation` | 422 | The turn performed no operation ({§empty-turn}). |
6003
6014
  | `send-target-not-a-recipient` | 400 | The addressed scheme is not a SEND recipient. Recovery: A targetless SEND answers the open messages; a directed SEND requires a recipient that implements SEND. |
6004
6015
 
6005
- §unregistered-scheme-recovery **An unregistered scheme's refusal names what does exist.** When the
6006
- address's authority is an executor available to the workspace — an MCP server's tool written as
6007
- `tool://gh/get_me` — the recovery is that executor's fence with the address's path as its target;
6008
- otherwise it lists the schemes the workspace registers. READ, FIND, KILL and resource selection share
6009
- the sentence.
6016
+ §unregistered-scheme-recovery **An unregistered scheme's refusal names what does exist.** Its recovery
6017
+ lists the schemes the workspace registers, and rebuilds the address into nothing else
6018
+ ({§diagnostic-observation}). READ, FIND, KILL and resource selection share the sentence.
6010
6019
 
6011
6020
  §problems-content **Content and transfer Problems.** Every code minted here, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
6012
6021
 
@@ -6038,11 +6047,11 @@ the sentence.
6038
6047
  | `path-names-no-file` | 403 | The spelling '*path*' does not name a file: it is empty, or it names a directory. |
6039
6048
  | `path-occupied-by-nonmember` | 403 | A non-member file already occupies '*path*'. Recovery: Choose an unoccupied member path. |
6040
6049
  | `path-outside-workspace` | 403 | A symlink on '*path*' resolves outside the namespace. |
6041
- | `binary-write-unsupported` | 415 | A text EDIT cannot author binary '*mimetype*'; COPY or MOVE the bytes instead. |
6050
+ | `binary-write-unsupported` | 415 | A text EDIT cannot author binary '*mimetype*'. |
6042
6051
  | `file-create-excluded` | 403 | A members exclusion (`!_glob_`) covers '*path*'. Recovery: Remove or disable the excluding members definition, or choose another path. |
6043
6052
  | `file-create-gitignored` | 403 | Active Git policy ignores '*path*', and no members definition includes it. Recovery: Choose a Git-admitted path or add a members definition that includes it. |
6044
6053
  | `file-materialization-limit` | 413 | The file exceeds the materialization byte limit and is not read into the workspace. |
6045
- | `entry-not-found` | 404 | No member of this workspace is at '*path*'. Recovery: Check the path with FIND. EDIT creates files; `members (add)` admits existing files with a `{"glob": "<path>"}` body. A path beyond the root recovers instead to: '*path*' is outside the project root: only a members definition under the operator's namespace scope admits it, so keep working files inside the root. |
6054
+ | `entry-not-found` | 404 | No member of this workspace is at '*path*'. Recovery: FIND lists paths. EDIT creates files; `members (add)` admits existing files with a `{"glob": "<path>"}` body. A path beyond the root recovers instead to: '*path*' is outside the project root: only a members definition under the operator's namespace scope admits it. |
6046
6055
 
6047
6056
  §problems-exec **Execution Problems.** Every code minted here, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
6048
6057
 
@@ -6071,12 +6080,12 @@ the sentence.
6071
6080
  | code | status | contract |
6072
6081
  |---|---:|---|
6073
6082
  | `service-starting` | 503 | The PLURNK service owns this listener but has not admitted its client interface yet. |
6074
- | `configuration-unsupported` | 400 | Environment discovery reads this installation's declared configuration; client configuration contributes nothing. |
6083
+ | `route-not-found` | 404 | The requested HTTP route does not exist. |
6075
6084
  | `configuration-invalid` | 503 | The owning configuration reader's diagnostic, naming the invalid key. Recovery: Correct the named configuration input. Other capabilities remain available. |
6076
6085
  | `name-reserved` | 400 | '*alias*' is plurnk's own: PLURNK_* configuration and provider credential names never reach a subprocess. |
6077
6086
  | `value-invalid` | 400 | '*alias*' needs a string value. |
6078
6087
  | `env-invalid` | 400 | `env` must be an object of string values; '*name*' is not a name a shell can export. |
6079
- | `query-required` | 400 | discover takes a path or a glob. Recovery: Supply `{ "query": "<path or glob>" }`. |
6088
+ | `query-invalid` | 400 | '*query*' names no path or pattern. Recovery: Supply a path such as `docs/guide.md` or a pattern such as `docs/**`. |
6080
6089
  | `headless` | 409 | The workspace has no project root, so there are no file members. Recovery: Open the workspace on a project root. |
6081
6090
  | `definition-invalid` | 400 | A members definition is { glob }: a gitignore-style pattern, `!glob` to exclude. |
6082
6091
  | `model-scope` | 403 | The model may not change membership here: the members scope is none. Recovery: `git add` the file so git tracks it, or ask the operator to add it (/members add) or raise PLURNK_SERVICE_MEMBERS_MODEL_SCOPE. |
@@ -6089,9 +6098,8 @@ the sentence.
6089
6098
  | `offset-channel-required` | 400 | Recovery: Select the channel to read from the offset. |
6090
6099
  | `target-invalid` | 400 | Recovery: Use a scheme://path target. |
6091
6100
  | `proposal-not-pending` | 409 | Recovery: Refresh pending proposals before resolving one. |
6092
- | `loop-policy-invalid` | 400 | An unattended loop cannot hold a proposal for review: nobody is present to answer. Recovery: State proposals accept or reject, or attend the loop. |
6093
6101
  | `scope-cancelled` | 499 | The worker scope was cancelled: *reason*. |
6094
- | `range-not-satisfiable` | 416 | `Range <0,-1>` starts at 0, which is not a line; lines are numbered from 1. Recovery: Write `<1,-1>` to trim every line of the body; `KILL (log:///…/READ)` with no scope retires the item. |
6102
+ | `range-not-satisfiable` | 416 | `Range <0,-1>` starts at 0, which is not a line; lines are numbered from 1. Recovery: Trim one line with `<L>` or lines L through M with `<L,M>`; `KILL (log:///…/READ)` with no scope retires the whole row. |
6095
6103
 
6096
6104
  §pinned-wording-core **Pinned wording.** Verbatim sentences tests pin: each is contract, and a change here is a change of contract.
6097
6105
 
@@ -6101,12 +6109,13 @@ the sentence.
6101
6109
  | Completion deferred. Conclude with KILL alone. | a concluding KILL that carries other operations ({§kill-conclusion}) |
6102
6110
  | Context exceeds budget. YOU MUST ONLY KILL, MOVE or NOTE this turn. | the over-budget row ({§context-over-budget-row}) |
6103
6111
  | Context window overflow: the packet cannot fit the model's window even as receipts. | the wall ({§context-wall}) |
6104
- | This run is unattended: nobody is present to answer. | a capability that needs a present operator in an unattended loop ({§loop-attendance}) |
6112
+ | No recipient implements the requested interaction. | The worker's owner is not interactive or does not declare the required client tool ({§client-interaction-routing}). |
6113
+ | Nobody is present to answer. Decide from what you already have, or conclude stating what you could not resolve. | dispatch of an interaction tool the worker's owner would not receive ({§owner-interaction-ring}) |
6105
6114
  | Worker name '*name*' must match `[A-Za-z0-9][A-Za-z0-9_-]{0,62}`. Recovery: Use 1–63 ASCII letters, digits, '_' or '-', starting with a letter or digit. | an invalid worker name |
6106
6115
  | Provide the client identifier. / Provide an absolute project path. / Use a positive integer limit. / prompt is not a non-empty string. | client input validation on the daemon's methods |
6107
6116
  | The stream was cancelled by KILL. | a stream terminal after KILL |
6108
6117
  | '*program*' exited with code *n*. | an execution's non-zero exit |
6109
- | '*path*' is a directory, not a file; READ reads one file. Recovery: List its files with `FIND (_path_/)`, then READ one by its path. | READ of a directory |
6118
+ | '*path*' is a directory, not a file; READ reads one file. Recovery: `FIND (_path_/)` lists its files. | READ of a directory |
6110
6119
  | The execution at ops://*worker*/*loop* has not concluded. | a bare READ of a running worker's result (425) |
6111
6120
  | The child provider failed. | a child's provider failure read back by its parent |
6112
6121
  | '*path*' exists on disk but is not a member of this workspace. Recovery: Admit it with `members (add)` and a `{"glob": "<path>"}` body. | a non-member on disk at the addressed path |