@plurnk/plurnk-service 1.6.0 → 1.7.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 (164) hide show
  1. package/.env.defaults +7 -21
  2. package/INSTALL.md +18 -17
  3. package/README.md +1 -1
  4. package/SPEC.md +209 -136
  5. package/dist/build-info.json +1 -1
  6. package/dist/core/ChannelWrite.sql +3 -3
  7. package/dist/core/ClientInteractions.d.ts +19 -0
  8. package/dist/core/ClientInteractions.d.ts.map +1 -0
  9. package/dist/core/ClientInteractions.js +124 -0
  10. package/dist/core/ClientInteractions.js.map +1 -0
  11. package/dist/core/ClientInteractions.sql +34 -0
  12. package/dist/core/CoreSchemeServices.d.ts +9 -3
  13. package/dist/core/CoreSchemeServices.d.ts.map +1 -1
  14. package/dist/core/CoreSchemeServices.js +8 -2
  15. package/dist/core/CoreSchemeServices.js.map +1 -1
  16. package/dist/core/Dispatcher.d.ts +4 -2
  17. package/dist/core/Dispatcher.d.ts.map +1 -1
  18. package/dist/core/Dispatcher.js +116 -1149
  19. package/dist/core/Dispatcher.js.map +1 -1
  20. package/dist/core/Engine.d.ts +24 -34
  21. package/dist/core/Engine.d.ts.map +1 -1
  22. package/dist/core/Engine.js +137 -1672
  23. package/dist/core/Engine.js.map +1 -1
  24. package/dist/core/Engine.sql +46 -32
  25. package/dist/core/ExecutableTools.d.ts +1 -0
  26. package/dist/core/ExecutableTools.d.ts.map +1 -1
  27. package/dist/core/ExecutableTools.js +17 -9
  28. package/dist/core/ExecutableTools.js.map +1 -1
  29. package/dist/core/ExecutorRegistry.d.ts +11 -3
  30. package/dist/core/ExecutorRegistry.d.ts.map +1 -1
  31. package/dist/core/ExecutorRegistry.js +139 -16
  32. package/dist/core/ExecutorRegistry.js.map +1 -1
  33. package/dist/core/LogBody.d.ts +1 -0
  34. package/dist/core/LogBody.d.ts.map +1 -1
  35. package/dist/core/LogBody.js +4 -0
  36. package/dist/core/LogBody.js.map +1 -1
  37. package/dist/core/ModelCall.d.ts +2 -2
  38. package/dist/core/ModelCall.d.ts.map +1 -1
  39. package/dist/core/ModelCall.js +4 -2
  40. package/dist/core/ModelCall.js.map +1 -1
  41. package/dist/core/PacketBuilder.d.ts +11 -22
  42. package/dist/core/PacketBuilder.d.ts.map +1 -1
  43. package/dist/core/PacketBuilder.js +90 -180
  44. package/dist/core/PacketBuilder.js.map +1 -1
  45. package/dist/core/ProblemLog.d.ts +1 -1
  46. package/dist/core/ProblemLog.d.ts.map +1 -1
  47. package/dist/core/ProblemLog.js +14 -3
  48. package/dist/core/ProblemLog.js.map +1 -1
  49. package/dist/core/ProposalLifecycle.d.ts +4 -2
  50. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  51. package/dist/core/ProposalLifecycle.js +19 -7
  52. package/dist/core/ProposalLifecycle.js.map +1 -1
  53. package/dist/core/ProviderInstantiate.js +2 -2
  54. package/dist/core/ProviderInstantiate.js.map +1 -1
  55. package/dist/core/ResourceMutations.d.ts +73 -0
  56. package/dist/core/ResourceMutations.d.ts.map +1 -0
  57. package/dist/core/ResourceMutations.js +1142 -0
  58. package/dist/core/ResourceMutations.js.map +1 -0
  59. package/dist/core/SchemeRegistry.d.ts +18 -10
  60. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  61. package/dist/core/SchemeRegistry.js +195 -43
  62. package/dist/core/SchemeRegistry.js.map +1 -1
  63. package/dist/core/StoredPacket.d.ts +2 -2
  64. package/dist/core/StoredPacket.js +7 -7
  65. package/dist/core/TurnRunner.d.ts +74 -0
  66. package/dist/core/TurnRunner.d.ts.map +1 -0
  67. package/dist/core/TurnRunner.js +1733 -0
  68. package/dist/core/TurnRunner.js.map +1 -0
  69. package/dist/core/WorkspaceGate.d.ts +1 -0
  70. package/dist/core/WorkspaceGate.d.ts.map +1 -1
  71. package/dist/core/WorkspaceGate.js +39 -0
  72. package/dist/core/WorkspaceGate.js.map +1 -1
  73. package/dist/core/caps/CoreInteractionCaps.d.ts +9 -0
  74. package/dist/core/caps/CoreInteractionCaps.d.ts.map +1 -0
  75. package/dist/core/caps/CoreInteractionCaps.js +14 -0
  76. package/dist/core/caps/CoreInteractionCaps.js.map +1 -0
  77. package/dist/core/caps/DbChannelCaps.js +4 -4
  78. package/dist/core/caps/DbChannelCaps.js.map +1 -1
  79. package/dist/core/caps/SchemeCtxImpl.d.ts +2 -1
  80. package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
  81. package/dist/core/caps/SchemeCtxImpl.js +3 -0
  82. package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
  83. package/dist/core/content-hash.js +2 -2
  84. package/dist/core/content-hash.js.map +1 -1
  85. package/dist/core/content-weight.d.ts +2 -0
  86. package/dist/core/content-weight.d.ts.map +1 -0
  87. package/dist/core/content-weight.js +4 -0
  88. package/dist/core/content-weight.js.map +1 -0
  89. package/dist/core/content_weight.d.ts +4 -0
  90. package/dist/core/content_weight.d.ts.map +1 -0
  91. package/dist/core/content_weight.js +6 -0
  92. package/dist/core/content_weight.js.map +1 -0
  93. package/dist/core/fork.sql +5 -5
  94. package/dist/core/packet-wire.d.ts +4 -4
  95. package/dist/core/packet-wire.d.ts.map +1 -1
  96. package/dist/core/packet-wire.js +21 -21
  97. package/dist/core/packet-wire.js.map +1 -1
  98. package/dist/core/scheme-types.d.ts +3 -2
  99. package/dist/core/scheme-types.d.ts.map +1 -1
  100. package/dist/core/scheme-types.js.map +1 -1
  101. package/dist/digest/Digest.js +1 -1
  102. package/dist/digest/Digest.js.map +1 -1
  103. package/dist/schemes/Exec.d.ts.map +1 -1
  104. package/dist/schemes/Exec.js +53 -15
  105. package/dist/schemes/Exec.js.map +1 -1
  106. package/dist/schemes/ExecOutputScheme.js +1 -1
  107. package/dist/schemes/ExecOutputScheme.js.map +1 -1
  108. package/dist/schemes/File.d.ts.map +1 -1
  109. package/dist/schemes/File.js +1 -1
  110. package/dist/schemes/File.js.map +1 -1
  111. package/dist/schemes/Log.d.ts.map +1 -1
  112. package/dist/schemes/Log.js +2 -2
  113. package/dist/schemes/Log.sql +2 -2
  114. package/dist/schemes/Worker.js +2 -2
  115. package/dist/schemes/Worker.js.map +1 -1
  116. package/dist/schemes/_entry-crud.js +4 -4
  117. package/dist/schemes/_entry-crud.js.map +1 -1
  118. package/dist/schemes/_entry-crud.sql +2 -2
  119. package/dist/schemes/_entry-find.d.ts +5 -5
  120. package/dist/schemes/_entry-find.d.ts.map +1 -1
  121. package/dist/schemes/_entry-find.js +29 -28
  122. package/dist/schemes/_entry-find.js.map +1 -1
  123. package/dist/schemes/_entry-manifest.js +4 -4
  124. package/dist/schemes/_entry-manifest.js.map +1 -1
  125. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  126. package/dist/schemes/_entry-ops.js +3 -3
  127. package/dist/schemes/_entry-ops.js.map +1 -1
  128. package/dist/schemes/_entry-ops.sql +3 -3
  129. package/dist/server/Daemon.d.ts +20 -49
  130. package/dist/server/Daemon.d.ts.map +1 -1
  131. package/dist/server/Daemon.js +311 -908
  132. package/dist/server/Daemon.js.map +1 -1
  133. package/dist/server/DaemonModule.d.ts +31 -3
  134. package/dist/server/DaemonModule.d.ts.map +1 -1
  135. package/dist/server/DrainSupervisor.d.ts +117 -0
  136. package/dist/server/DrainSupervisor.d.ts.map +1 -0
  137. package/dist/server/DrainSupervisor.js +802 -0
  138. package/dist/server/DrainSupervisor.js.map +1 -0
  139. package/dist/server/client-input.d.ts +2 -0
  140. package/dist/server/client-input.d.ts.map +1 -1
  141. package/dist/server/client-input.js +12 -0
  142. package/dist/server/client-input.js.map +1 -1
  143. package/dist/server/lifecycle-recovery.sql +9 -0
  144. package/dist/server/logEntry.d.ts +1 -1
  145. package/dist/server/logEntry.js +1 -1
  146. package/dist/server/loopDocs.d.ts +1 -0
  147. package/dist/server/loopDocs.d.ts.map +1 -1
  148. package/dist/server/loopDocs.js +37 -8
  149. package/dist/server/loopDocs.js.map +1 -1
  150. package/dist/server/loopDocs.sql +10 -0
  151. package/dist/server/seam-entry-read.sql +2 -2
  152. package/dist/server/workspace-capabilities.sql +19 -0
  153. package/dist/service.js +1 -1
  154. package/dist/service.js.map +1 -1
  155. package/migrations/001_schema.sql +51 -10
  156. package/package.json +34 -33
  157. package/dist/core/ruler_count.d.ts +0 -4
  158. package/dist/core/ruler_count.d.ts.map +0 -1
  159. package/dist/core/ruler_count.js +0 -7
  160. package/dist/core/ruler_count.js.map +0 -1
  161. package/dist/core/token-ruler.d.ts +0 -2
  162. package/dist/core/token-ruler.d.ts.map +0 -1
  163. package/dist/core/token-ruler.js +0 -5
  164. package/dist/core/token-ruler.js.map +0 -1
package/SPEC.md CHANGED
@@ -26,7 +26,7 @@ flowchart LR
26
26
  | **agent** | PLURNK | The plurnk runtime. Acts in-workspace as the reserved `plurnk` worker ({§actor-boundary} self-hosting), never a privileged singleton owning its own entries ({§entry-owner}, {§machine-processes}). |
27
27
  | **workspace** | Core | Durable user-named shared world. Persists across workers and process restarts. Identity: `workspaces.id` + unique `workspaces.name`. |
28
28
  | **worker** | Core | Durable actor and private history over one workspace. Owns its loops and log rows, may carry a `parent_worker_id`, and has one process-local cancellation scope while active. |
29
- | **loop** | Core | Queued-to-terminal unit of model or client work within a worker. Status ∈ {100 pending · 102 running · 200 done · 202 waiting (blocked on a live obligation, {§send}) · 413 context-envelope rejection · 429 turn-ceiling · 499 cancelled · 500 failed · 504 wall-clock timeout ({§operator-config-loop-timeout}) · 508 runaway}. Many loops may belong to one worker. |
29
+ | **loop** | Core | Queued-to-terminal unit of model or client work within a worker. Status ∈ {100 pending · 102 running · 200 done · 202 waiting (blocked on a live obligation, {§send}) · 413 provider input-capacity failure · 429 turn-ceiling · 499 cancelled · 500 failed · 504 wall-clock timeout ({§operator-config-loop-timeout}) · 508 runaway}. Many loops may belong to one worker. |
30
30
  | **turn** | Core | One engine scheduling unit (or one client-op scheduling unit). A model turn sends one assembled prompt through one or more emission attempts and admits at most one response. Many turns may belong to one loop. Identity: `(loop_id, sequence)`. |
31
31
  | **model call** | Core/provider | One logical `provider.generate` invocation. Emission attempts and BARE inferences share this durable accounting owner; provider retries remain cardinal physical requests beneath it. Identity: `(turn_id, sequence)`. |
32
32
  | **op** | Model/core | One DSL operation the model emits, parsed into a `PlurnkStatement`. One admitted turn produces zero or more ops. |
@@ -36,14 +36,14 @@ flowchart LR
36
36
  | **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. |
37
37
  | **AG-UI thread** | AG-UI protocol | Conversation identity. Within an explicitly selected workspace, `threadId` resolves to one conversation worker. |
38
38
  | **`--run`** | Client compatibility | A compatibility-sensitive client spelling, not an internal entity. |
39
- | **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 -->
39
+ | **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 --> |
40
40
 
41
41
  ### §storage-terms Storage terms
42
42
 
43
43
  | Term | Meaning |
44
44
  |---|---|
45
45
  | **entry** | The unit of canonical state. Identity: `(workspace, owner, scheme, pathname)` ({§entry-identity-no-null}). Holds one or more `channels` of content plus private `attributes`. |
46
- | **channel** | A named content buffer on an entry. Examples: `body`, `stdout`, `stderr`, `headers`, `symbols`. Each channel has `content`, `mimetype`, `tokens`, `state`. |
46
+ | **channel** | A named content buffer on an entry. Examples: `body`, `stdout`, `stderr`, `headers`, `symbols`. Each channel has `content`, `mimetype`, curation `weight`, and lifecycle `state`. |
47
47
  | **scope** | A scheme-manifest declaration ignored by core; registrations are discovered at boot and are not persisted. Entry sharing and privacy are owner-based; #80 owns retiring this residual axis. |
48
48
  | **scheme** | An addressed capability family + handler. Built-ins include `worker`, `prompt`, `log`, and bare/file paths; discovered schemes and executor-runtime tags extend that set. Internal `exec` routes the EXEC op but is not an addressable model namespace. Consumption surface {§scheme-surface}; author contract: [plurnk-schemes](../plurnk-schemes/SPEC.md). |
49
49
  | **mimetype** | A channel's content type. Drives the handler that produces the structural projections (`symbols`, `deepJson`, `deepXml`). Consumption surface {§mimetype-surface}; author contract: [plurnk-mimetypes](../plurnk-mimetypes/SPEC.md). |
@@ -103,7 +103,7 @@ shown. The current streak may ride first-party provider metadata
103
103
  | §mode-ask-read-only **mode** | `"ask" \| "act"`. Per-loop. Ask = read-only: the dispatch gate refuses every side-effecting op (a filesystem write — EDIT/COPY-dest/MOVE/KILL on the `file` scheme — or any EXEC invocation); reads of the workspace stay open. `act` = full surface. Ask never changes the world. |
104
104
  | **flag** | Per-loop value: `mode`, `noWeb`, and `noInteraction` shape scheme authority ({§manifest-flag-affinity}); `auto` and `noProposals` select proposal settlement. |
105
105
  | **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
- | **resolution** | Accept, reject, or cancel of a durable proposal. Client-owned authority enters core through `resolveProposal`; AG-UI carries it in standard resume entries ({§methods-proposal-resolve}, {§agui-proposal-resolve}). |
106
+ | **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}). |
107
107
 
108
108
  ### §packet-terms Packet terms
109
109
 
@@ -192,7 +192,18 @@ Composed daemon internals + admin CLI. Four plug points:
192
192
  - **Mimetypes** ({§mimetype}) — content interpretation. Render-time handlers consume channel content; framework owns the dispatch.
193
193
  - **Executors** ({§exec} / {§bundled-set}) — EXEC runtime dispatch for subprocess, search, data, and pure-computation runtimes.
194
194
 
195
- The engine dispatches ops, persists state to SQLite, orchestrates cross-scheme COPY/MOVE ({§copy}/{§move}), and writes the log. Capability-specific behavior remains with the owning plug point.
195
+ Core's internal owners compose without becoming new package or public seams:
196
+
197
+ | Owner | Machine |
198
+ |-------|---------|
199
+ | `Daemon` | Process/module lifecycle, dependency composition, provider policy, notifications, and the external client façade. |
200
+ | `DrainSupervisor` | One worker's queue consumer, drain identity, wake obligations, cancellation scope, poll/park timers, and terminal cleanup. |
201
+ | `Engine` | Loop lifecycle and the public turn, dispatch, derivation, and proposal façades. |
202
+ | `TurnRunner` | One durable model turn from materialization through provider admission, operation scheduling, and settlement. |
203
+ | `Dispatcher` | Operation admission/routing, scheme execution, proposal waiting, curation, and durable log writes. |
204
+ | `ResourceMutations` | EDIT/COPY/MOVE selection, anchor preconditions, cross-scheme effects, and mutation settlement. |
205
+
206
+ Capability-specific behavior remains with the owning plug point.
196
207
 
197
208
  The contracts package (`@plurnk/plurnk-contracts`) owns the parser and AST contract. Schemes receive parsed statement fragments via dispatch.
198
209
 
@@ -538,7 +549,7 @@ stateDiagram-v2
538
549
  Terminal --> [*]
539
550
  ```
540
551
 
541
- §worker-lifecycle-state-machine The lifecycle store admits only the guarded transitions shown above: `100 → 102`, `102 → 202`, `202 → 100`, and any unresolved state (`100`, `102`, `202`) to a terminal status. Terminal state is immutable. The queue drain owns claim, the dispatcher owns model-requested park/conclusion, the daemon owns wake/cancellation/restart reconciliation, and the engine owns policy terminals. A racing transition that loses observes the durable winner; it does not overwrite it or report the requested state as fact.
552
+ §worker-lifecycle-state-machine The lifecycle store admits only the guarded transitions shown above: `100 → 102`, `102 → 202`, `202 → 100`, and any unresolved state (`100`, `102`, `202`) to a terminal status. Terminal state is immutable. `DrainSupervisor` owns claim, wake, and cancellation; the dispatcher owns model-requested park/conclusion; the daemon owns boot-recovery orchestration; and the engine owns policy terminals. A racing transition that loses observes the durable winner; it does not overwrite it or report the requested state as fact.
542
553
 
543
554
  §stream-catalog-lifecycle Streams are independently durable subscriptions owned by a worker. Payload and
544
555
  lifecycle are orthogonal: zero bytes is a valid payload for both success and
@@ -549,7 +560,6 @@ their exact `status` and derive `closed` (status below 400), `killed` (499), or
549
560
  `failed` (other failure status). An entry with no subscription has no `stream`
550
561
  member. This is historical state, not merely a live-process hint.
551
562
 
552
-
553
563
  ```mermaid
554
564
  stateDiagram-v2
555
565
  [*] --> Open: executor registers subscription
@@ -631,7 +641,6 @@ it is the parked lifecycle state, not a terminal. No product surface may infer o
631
641
  reconstruct a result from that projection. Active rows have no terminal result;
632
642
  terminal rows must have one, and database triggers enforce both directions.
633
643
 
634
-
635
644
  ```mermaid
636
645
  flowchart TD
637
646
  W[Worker cancellation] --> L[Terminalize unresolved loops]
@@ -654,13 +663,13 @@ boundary.
654
663
  - §worker-lifecycle-total-reap **Cancellation is recursive and reaps every held stream.** `loop.cancel`, worker `KILL`, shutdown, and a worker's SEND signal `499` terminalize every unresolved loop in the cancelled worker subtree and iterate each worker's durable open-subscription rows, invoking each exact callable owner from the process-local live registry. The durable rows answer *what is held*; the live registry answers *how this process tears it down*; the abort signal is a fast-path optimization. There is no implicit detachment. Before shutdown awaits drains, it cancels every process-local proposal waiter through {§proposal-cancel-aborts} with outcome `daemon_stopping`, so a stopped-world dispatch cannot hold teardown open. A stream that is running, mid-spawn (its row written before it is killable), or spawned after the cancel is reaped alike. The teardown abort is bounded: the executor sends a polite signal then SIGKILL after a consumer-set grace (`PLURNK_SERVICE_EXEC_KILL_GRACE_MS`). A model `## KILL0 [code]` on one live stream instead delivers exactly that signal once (bare KILL uses the executor's SIGHUP default; `## KILL0 [9]` uses SIGKILL).
655
664
  - §worker-lifecycle-exec-epoch-bound **A stream's kill binds to the scope it captured at spawn.** A stream captures the worker's cancellation scope as it registers and wires its kill to it, re-checking `aborted` AFTER wiring — no check-then-listen gap can drop an abort that lands mid-registration. Because the scope is replaced only once aborted, a captured-then-replaced scope is necessarily already aborted, so replacement never strands a live stream.
656
665
  - §worker-lifecycle-no-resurrection **A cancelled worker is not resurrected by its own torn-down work.** A stream conclusion delivered to a cancelled, idle worker starts no fresh drain: an aborted (499) conclusion is skipped, and a straggler that concluded cleanly surfaces its deliverable as an environment delta ({§env-delta}), never a revived loop. The cancel was deliberate; only an explicit `runLoop` request resumes the worker.
657
- - §worker-lifecycle-wake-liveness **A stream conclusion always reaches its worker.** When a backgrounded stream concludes, the daemon routes it through the same inject seam as any loop source ({§actor-boundary-passive-wake}): an active worker folds the conclusion into its next turn; a worker **blocked on a 202 wait** for that stream ({§wait-obligation-matrix}) **awakens that loop in place** — the blocked loop *is* the continuation, so there is no fresh loop and no summary-as-prompt fiction. The result is never lost: a blocked loop sleeps rather than ending, and the stream's status-transition is the arrival ({§actor-boundary-passive-wake}) that wakes it; on resume it reads the concluded stream's own state, not a synthetic prompt.
658
- - §worker-lifecycle-child-wake **A child worker concluding wakes a parent blocked on it — the topology join.** `worker://` spawn/fork records `parent_worker_id` ({§lifecycle-terms}). When a worker's drain exits having **concluded** — no `202`-blocked loop, no open stream — the daemon resumes its parent **in place** if the parent is blocked on the join (`#onDrainExit` → the shared `#wakeParkedWorker`, the same 202→100 resume a stream conclusion uses). So a parent that spawns work and blocks with SEND signal `202` is woken the moment its child finishes; on resume it reads the child's deliverable from the {§worker-scheme-collect} delta in its own log — a control edge, **never an injected prompt**. The wake recurses upward via the parent's own drain-exit. A child still running — or itself blocked at 202 — is not *concluded*, so it does not wake the parent (it's still a live thing the subtree holds). This is the structured-concurrency join: streams and child workers are the same kind of "live thing a worker holds," driving premature-terminate ({§send-premature-terminate}), the wake edge, and the collect delta identically. A worker conclusion is a **bounded, un-loseable** wake: if the conclusion fires while the parent is mid-turn (before its block commits), `#wakeParkedWorker` finds it not-yet-slept and records an **owed wake**, which the drain honors when the parent blocks — so a wait awaiting workers **always returns**, never dead-blocks on a conclude-before-block race. (Only a live exec stream, unbounded absent a timeout, may legitimately hold a wait open.)
666
+ - §worker-lifecycle-wake-liveness **A stream conclusion always reaches its worker.** When a backgrounded stream concludes, `DrainSupervisor` routes it through the same inject seam as any loop source ({§actor-boundary-passive-wake}): an active worker folds the conclusion into its next turn; a worker **blocked on a 202 wait** for that stream ({§wait-obligation-matrix}) **awakens that loop in place** — the blocked loop *is* the continuation, so there is no fresh loop and no summary-as-prompt fiction. The result is never lost: a blocked loop sleeps rather than ending, and the stream's status-transition is the arrival ({§actor-boundary-passive-wake}) that wakes it; on resume it reads the concluded stream's own state, not a synthetic prompt.
667
+ - §worker-lifecycle-child-wake **A child worker concluding wakes a parent blocked on it — the topology join.** `worker://` spawn/fork records `parent_worker_id` ({§lifecycle-terms}). When a worker's drain exits having **concluded** — no `202`-blocked loop, no open stream — `DrainSupervisor.#onDrainExit` resumes its parent **in place** through the shared `#wakeParkedWorker`, the same 202→100 resume a stream conclusion uses. So a parent that spawns work and blocks with SEND signal `202` is woken the moment its child finishes; on resume it reads the child's deliverable from the {§worker-scheme-collect} delta in its own log — a control edge, **never an injected prompt**. The wake recurses upward via the parent's own drain-exit. A child still running — or itself blocked at 202 — is not *concluded*, so it does not wake the parent (it's still a live thing the subtree holds). This is the structured-concurrency join: streams and child workers are the same kind of "live thing a worker holds," driving premature-terminate ({§send-premature-terminate}), the wake edge, and the collect delta identically. A worker conclusion is a **bounded, un-loseable** wake: if the conclusion fires while the parent is mid-turn (before its block commits), `#wakeParkedWorker` finds it not-yet-slept and records an **owed wake**, which the drain honors when the parent blocks — so a wait awaiting workers **always returns**, never dead-blocks on a conclude-before-block race. (Only a live exec stream, unbounded absent a timeout, may legitimately hold a wait open.)
659
668
  - §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 its terminal SEND; separately, a stream or direct-child conclusion persists and publishes immediately but holds the parked worker's single `202→100` requeue 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 every conclusion that lands within it into one requeue. With no sibling obligation the wake is immediate; at the deadline, surviving work follows the ordinary monitored lifecycle. A conclusion that lands after provider dispatch begins retains its next wake, while poll, park-deadline, prompt, and operator wakes never open this hold. Only packet/provider dispatch waits: terminal state, client events, cancellation, and child execution do not. One redaction-safe span records elapsed time, quiescence versus deadline, and conclusion count without entering the packet.
660
669
  - §worker-lifecycle-idle-is-concluded **An idle worker concludes; it does not park.** A loop is idle only when it has neither live obligations nor completed results awaiting their first packet. A live child or stream blocks a SEND signal `202` join; a completed stream, child result, or same-turn retrieval continues directly to the next packet where it is observed. Only after those sets are drained does signal `202` resolve like signal `200`. There is no held-open idle loop and no `loop/quiesced` soft signal. A concluded worker is durable working history and an addressed arrival reawakens it as a new loop.
661
670
  - §worker-lifecycle-no-lost-loop **A loop is never stranded by a drain's exit.** A drain relinquishes its registry slot only after a lock-held re-claim confirms the queue is empty; a loop enqueued during that teardown is either re-claimed by the exiting drain or claimed by a fresh drain that a later inject starts. The relinquish and the start are serialized, so neither the lost-loop hang nor a transient double-drain can occur.
662
671
  - §worker-lifecycle-durable-disposition **Durable disposition wins cancellation races.** At a turn boundary, the engine reads the loop's durable status before interpreting a process-local abort. A committed `202` park survives a later daemon-shutdown signal; only a loop still durably running at `102` can be terminalized by that cancellation.
663
- - §worker-lifecycle-restart-recovery **Restart is owner-loss reconciliation, not replay.** Before opening client transports, the service holds an exclusive database-adjacent daemon lock; a second live owner fails before touching SQLite, while a dead-PID crash claim is replaced atomically without a timeout lease. Boot preserves accepted `100` loops and restores their drains. A `102` loop belonged to a vanished drain/provider call, so it settles `500` with the interruption on its durable row—never replayed across an unknown effect boundary. Every pending physical provider request beneath that loop first settles as an error with absent usage and explicitly unknown cost, then its logical model call closes; recovery never fabricates zero evidence. Every durable proposed operation likewise lost its process-local resolution waiter and settles as a visible `500 owner_vanished` occurrence rather than an unresolvable interrupt ({§proposal-list}). Every durable-open subscription belonged to a vanished callable: active channels become errored and its row closes `500`. A `202` continuation survives only while a live child obligation remains; after reconciliation, an unblocked park requeues `202→100` and resumes in place. Child terminalization wakes its parked parent on every outcome, including provider exceptions, cancellation, and restart interruption, recursively through the durable parent edges. These operations are idempotent, so an interrupted recovery safely repeats.
672
+ - §worker-lifecycle-restart-recovery **Restart is owner-loss reconciliation, not replay.** Before opening client transports, the service holds an exclusive database-adjacent daemon lock; a second live owner fails before touching SQLite, while a dead-PID crash claim is replaced atomically without a timeout lease. Boot preserves accepted `100` loops and restores their drains. A `102` loop belonged to a vanished drain/provider call, so it settles `500` with the interruption on its durable row—never replayed across an unknown effect boundary. Every pending physical provider request beneath that loop first settles as an error with absent usage and explicitly unknown cost, then its logical model call closes; recovery never fabricates zero evidence. Every durable proposed operation likewise lost its process-local resolution waiter and settles as a visible `500 owner_vanished` occurrence rather than an unresolvable interrupt ({§proposal-list}). A pending client interaction also lost its exact awaiting operation, so boot removes the orphan instead of replaying work or inventing a response ({§client-interactions}). Every durable-open subscription belonged to a vanished callable: active channels become errored and its row closes `500`. A `202` continuation survives only while a live child obligation remains; after reconciliation, an unblocked park requeues `202→100` and resumes in place. Child terminalization wakes its parked parent on every outcome, including provider exceptions, cancellation, and restart interruption, recursively through the durable parent edges. These operations are idempotent, so an interrupted recovery safely repeats.
664
673
 
665
674
  ---
666
675
 
@@ -670,12 +679,13 @@ Author-facing contract: [`@plurnk/plurnk-providers`](../plurnk-providers/SPEC.md
670
679
 
671
680
  ### §provider-surface Consumption surface
672
681
 
673
- Two current entry points:
682
+ Three current entry points:
674
683
 
675
684
  - §provider-surface-generate `provider.generate(args)` — once per logical model call. An emission attempt supplies the complete packet messages, worker/turn coordinates, generation envelope, optional local grammar, first-party metadata, and `callKind: "emission"`. A BARE inference supplies only one user message containing its body plus non-prompt call identity and accounting metadata, including `callKind: "bare"` ({§bare-inference} {§provider-call-kind}). Both receive a durable physical-request observer; provider-owned retry and failover may issue several ordered requests beneath either call. A successful `ProviderResponse` reaches its call-specific consumer; a `ProviderError.attempt` remains failed response evidence under {§provider-interrupted-attempt}. Core persists normalized response evidence separately from physical accounting and relays encrypted reasoning only from an admitted emission ({§encrypted-reasoning-carrier}).
676
- - §provider-surface-prompt-measurement `provider.countPromptTokens(messages, signal)` — cancellable measurement of the complete provider request with `exact`, `upper_bound`, or `estimate` provenance. It is used only for hard context-envelope admission ({§tokenomics-context-envelope-admission}); model-facing stored and rendered weights use the provider-agnostic ruler.
685
+ - §provider-surface-capacity `provider.assessRequestCapacity(messages, maxOutputTokens?, signal?)` — provider-owned intersection of request-shaped token evidence and every known physical input limit. It admits, rejects only a proven exact overflow, or defers ambiguity to upstream ({§tokenomics-context-envelope-admission}). `generate` performs this assessment for its exact request and preserves the evidence on success and capacity failure.
686
+ - §provider-surface-prompt-measurement `provider.countPromptTokens(messages, signal)` — the cancellable complete-request measurement primitive used by provider capacity assessment, with `exact`, `upper_bound`, `estimate`, or `unavailable` provenance. Core never substitutes this physical fact for its curation ruler.
677
687
 
678
- §provider-surface-identity Provider capacity and identity are immutable for one instance. `contextWindow` (the effective total context envelope, or `null` when unknown) and the optional reasoning/completion reserves define the natural prompt partition and hard admission check ({§tokenomics}); `model` identifies persisted turn/provider evidence. Local GBNF boot verification also consumes `constrainsOutput` ({§grammar-enforcement-verified-at-boot}).
688
+ §provider-surface-identity Provider capacity and identity are immutable for one instance. `contextWindow`, `maxInputTokens`, and `maxOutputTokens` carry known model limits; `outputBudget` is the total generation envelope, optional `reasoningBudget` is its strict subset, and `inputCapacity` is the stable intersection of known input constraints ({§tokenomics}). Unknown facts remain `null`. `model` identifies persisted turn/provider evidence. Local GBNF boot verification also consumes `constrainsOutput` ({§grammar-enforcement-verified-at-boot}).
679
689
 
680
690
  §meta-passthrough **Metadata passthrough (provider → client).** `generate` may return an open `meta: Record<string, unknown>` bag. The service stores it unenforced per turn (`turns.meta`, `json_valid` only — no schema) and forwards the latest turn's blob in `loop/terminated.usage` ({§notifications}). The service never reads a field within it. Providers own their metadata shapes; monetary values carry an explicit amount and currency rather than an implied unit. Absent → `{}`. The mirror direction (client → provider, the self-identified `client` id) rides `generate({client})` ({§attribution}).
681
691
 
@@ -687,13 +697,13 @@ Two current entry points:
687
697
  - BARE calls admitted by one turn launch as one parallel batch; each call retains independent observer and failure state, and the engine awaits the complete batch before committing results in authored order ({§bare-inference}).
688
698
  - §provider-guarantees-request-observer Immediately before each physical provider I/O, the provider opens its provider/model identity through `observeRequest` and settles the returned handle exactly once as response or error. Core durably records that occurrence before I/O and rejects a returned response or `ProviderError` whose ordered accounting differs from the observed records. Persistence failure is an internal contract failure, never optional telemetry.
689
699
  - §provider-guarantees-assistantraw-opaque `assistantRaw` is opaque to the engine (forensics-only).
690
- - `countPromptTokens` receives the exact `PacketWire` messages later supplied to `generate`. The engine calls it only when negative ruler pressure requires hard context-envelope admission; it may perform provider I/O and receives the loop cancellation signal.
700
+ - Capacity assessment receives the exact `PacketWire` messages supplied to `generate`, its effective total-output tightening, and the loop cancellation signal. It may perform provider I/O. A curation-weight comparison never authorizes or rejects physical I/O.
691
701
 
692
702
  ### §emission-admission Provider emission admission
693
703
 
694
704
  A completed provider exchange is an **emission attempt**, not necessarily an engine turn. The provider transports and observes the model's bytes; ANTLR is the admission authority only after provider completion. Admission asks whether the exchange has a trustworthy frame: its first parsed operation is PLAN, its last parsed operation is a terminal SEND, every hard parse error is bounded between those anchors, and no `unparsedTail` exists. Missing anchors, an error outside the frame, or a boundary-destroying tail rejects the entire exchange regardless of `finishReason`; no recovered prefix dispatches. Parser warnings remain admissible. `finish=length` is forensic evidence of likely truncation, not an independent rejection rule. A provider-declared resource interruption never reaches admission, even when its partial bytes form a complete-looking frame ({§provider-interrupted-attempt}).
695
705
 
696
- Core retries a rejected emission against the exact same packet beneath the same engine turn, up to `PLURNK_SERVICE_EMISSION_ATTEMPTS`. Rejected bytes never dispatch or reach the engine strike rail. Before each `generate`, Core opens one durable logical `model_calls` row and its emission-specific `turn_attempts` admission row. Beneath the model call, every provider observer invocation opens one cardinal `provider_requests` occurrence immediately before physical I/O and settles it as response or error. Adapter retries and capacity failover append requests in issue order; a response-less failure therefore remains an accounted occurrence rather than disappearing. Normalized response evidence is durable before parser classification and does not duplicate the separately owned accounting. The accepted exchange alone completes `turns.packet`; every physical request remains in turn and loop accounting, while the context gauge reads the latest settled emission request on the latest turn. Digest exposes rejected response evidence as `packetNNN.attemptNNN.rejected.*` and every physical request in its machine-readable ledger.
706
+ Core retries a rejected emission against the exact same packet beneath the same engine turn, up to `PLURNK_SERVICE_EMISSION_ATTEMPTS`. Rejected bytes never dispatch or reach the engine strike rail. Before each `generate`, Core opens one durable logical `model_calls` row and its emission-specific `turn_attempts` admission row. A call that ends without response evidence leaves that admission row unclassified (`accepted IS NULL`) and does not consume the emission-attempt ceiling. Beneath the model call, every provider observer invocation opens one cardinal `provider_requests` occurrence immediately before physical I/O and settles it as response or error. Adapter retries and capacity failover append requests in issue order; a response-less failure therefore remains an accounted occurrence rather than disappearing. Normalized response evidence is durable before parser classification and does not duplicate the separately owned accounting. The accepted exchange alone completes `turns.packet`; every physical request remains in turn and loop accounting, while the context gauge reads the latest settled emission request on the latest turn. Digest exposes rejected response evidence as `packetNNN.attemptNNN.rejected.*` and every physical request in its machine-readable ledger.
697
707
 
698
708
  The first exhaustion in a consecutive sequence closes that unadmitted turn as a continue and opens exactly one ordinary recovery turn. Its packet projects the latest rejected response OPEN from a durably FOLDED model item under {§model-entry-log-curation} and carries one transient `invalid_emission` Notice whose complete message is: `Your previous response contained an unrecoverable syntax error. No operations were performed. Try again.` No parser diagnostic, attempt count, or rail state becomes model-facing. The recovery turn has its own honestly stored packet and its configured private same-packet attempts. The packet-local projection never changes the row's curation state, so no later packet repeats the malformed body unless the model explicitly OPENs it. Admission clears the recovery state; exhausting the informed turn terminates instead of opening another.
699
709
 
@@ -721,7 +731,7 @@ an assistant or a zero-valued observation.
721
731
  A plugin may declare opaque attribution tags statically or at runtime under the
722
732
  shared contract {§plugin-attribution}:
723
733
 
724
- ```
734
+ ```jsonc
725
735
  { "plurnk": { "attribution": "@acme/widgets" } } // always-on string or string[]
726
736
  ```
727
737
 
@@ -787,7 +797,7 @@ inside the projected reasoning prefix has no false content pointer. With no
787
797
  local GBNF, core adds no rail state and makes no claim about endpoint-owned
788
798
  settings.
789
799
 
790
- ```
800
+ ```dotenv
791
801
  PLURNK_MODEL_gemma=openai/macher.gguf
792
802
  PLURNK_MODEL_opus=openrouter/anthropic/claude-opus-latest
793
803
  PLURNK_MODEL=gemma
@@ -1013,9 +1023,9 @@ Engine → scheme guarantees:
1013
1023
  - `ctx.signal` is wired to the worker's AbortController ({§provider-guarantees-signal-wired}).
1014
1024
  - §scheme-surface-exception-500 Scheme exceptions are contract violations. Core records their complete cause in daemon diagnostics, closes the action with a generic core-owned 500 Problem, and surfaces that durable row in the next turn's `errors` section ({§operation-results}). Implementation exception text is not repurposed as a model recovery instruction.
1015
1025
 
1016
- **Tokenization participation.** Core's shared `_entry-crud.ts` write helper
1017
- populates `entry_channels.tokens` at write time through `ctx.tokenize`
1018
- ({§tokenomics-tokens-stored-at-write}). Scheme handlers reach that path through
1026
+ **Curation-weight participation.** Core's shared `_entry-crud.ts` write helper
1027
+ populates `entry_channels.weight` at write time through `ctx.weigh`
1028
+ ({§tokenomics-weight-stored-at-write}). Scheme handlers reach that path through
1019
1029
  the public `ctx.entries` capability. Raw database writes are outside the scheme
1020
1030
  API and receive no implicit token accounting.
1021
1031
 
@@ -1106,9 +1116,9 @@ discovery ({§mimetype-discovery}).
1106
1116
 
1107
1117
  **Token accounting.** The daemon injects no tokenizer into `Mimetypes`; content
1108
1118
  projection is independent of packet budgeting. Core uses the stable
1109
- model-independent ruler for stored/catalog weights and the model-facing budget
1119
+ model-independent ruler for stored/catalog weights and the model-facing curation budget
1110
1120
  ({§tokenomics-agnostic-ruler}). The provider's request-shaped measurement is
1111
- confined to hard context-envelope admission
1121
+ confined to provider-owned physical capacity assessment
1112
1122
  ({§tokenomics-context-envelope-admission}).
1113
1123
 
1114
1124
  §persistent-search-index **Persistent search index.** `SearchIndex.maintain` is the pre-model engine pass. Each searchable resource supplies an address and the exact readable body its READ exposes. Entries supply their default body; `LogBody` resolves each log row's canonical full body from its durable tx/rx envelope. Acquisition schemes project remote source material before storing that body; search never introduces a second hidden text projection. The readable body, mimetype, resolved text/binary classification, mimetype projection identity, embedder configuration, and applicable search exclusion form a content hash. Complete artifacts own FTS, vectors, symbol definitions, and references; resource rows hold only the attachment hash. Binary, empty, and excluded derivations do not invoke handler projections and therefore use one fixed no-projection identity.
@@ -1341,7 +1351,7 @@ OPEN/FOLD operate on the **log** (`log:///`) - the model's context-curation surf
1341
1351
 
1342
1352
  ### §jsonplurnk The Log's wire format
1343
1353
 
1344
- The `## Log` section renders as a fixed three-backtick `jsonplurnk` fence - a JSON array of entry objects, otherwise-valid JSON with **exactly one** deviation: an open, nonempty `body` is a raw multiline string. Its opening JSON quote is followed by a physical newline, every content line begins with a numeric `N:` or anchored `@hash N:` coordinate prefix whose separator is one or more ASCII spaces, and its closing quote appears at column zero before either the object close or a following member. Source quotes, braces, fences, and headings cannot collide with either boundary because source text never occupies column zero after projection; source backticks therefore cannot form a CommonMark closing fence. The fixed opener keeps the packet prefix stable across content changes. The carve-out is localized to `body`, so the strip-parser recognizes `"body":"` followed by a newline, consumes one or more coordinate-prefixed lines, and replaces the raw multiline value with an escaped JSON string while preserving following members to recover strict JSON. The body shape is a strict three-state invariant: `"display":"none","body":""` for no body, `"display":"folded"` with the ordinary projection withheld, and `"display":"open","body":"\n<coordinate>...\n"` with it shown. `path` is the complete model-facing log identity: when a projected operation exists it ends in `/OP`, and no separate `op` field duplicates it. Nonempty `tags` is the row's complete deduplicated, sorted folksonomy; an untagged row omits it. When an OPEN bounded projection differs from the canonical body, it appends `"chunk":"showing <selected> of <complete>"` after `body`; otherwise it omits `chunk`. Complete-line extents use inclusive two-coordinate line regions. A cut inside a line uses four-coordinate, start-inclusive and end-exclusive regions with 1-based Unicode code-point columns. The row's `path` remains the canonical READ target. The block is data only - no prose leads the fence. `tokens` is the ruler-weight of the row's ordinary packet body: the room OPEN adds and FOLD saves. A FIND's nonzero `itemsTokenTotal` weighs the complete matched set; a nonzero `returnedItemsTokenTotal` appears only when the returned page has a different weight. These are curation weights, not dollars. The invariants bind regardless of shape ({§packet}): addressability (`path`/`target`/`#channel`/coordinate-prefixed bodies), weighability (per-item `tokens`), honesty (every 4xx/5xx row and the exact body/display state). {§jsonplurnk} {§packet-jsonplurnk-exception}
1354
+ The `## Log` section renders as a fixed three-backtick `jsonplurnk` fence - a JSON array of entry objects, otherwise-valid JSON with **exactly one** deviation: an open, nonempty `body` is a raw multiline string. Its opening JSON quote is followed by a physical newline, every content line begins with a numeric `N:` or anchored `@hash N:` coordinate prefix whose separator is one or more ASCII spaces, and its closing quote appears at column zero before either the object close or a following member. Source quotes, braces, fences, and headings cannot collide with either boundary because source text never occupies column zero after projection; source backticks therefore cannot form a CommonMark closing fence. The fixed opener keeps the packet prefix stable across content changes. The carve-out is localized to `body`, so the strip-parser recognizes `"body":"` followed by a newline, consumes one or more coordinate-prefixed lines, and replaces the raw multiline value with an escaped JSON string while preserving following members to recover strict JSON. The body shape is a strict three-state invariant: `"display":"none","body":""` for no body, `"display":"folded"` with the ordinary projection withheld, and `"display":"open","body":"\n<coordinate>...\n"` with it shown. `path` is the complete model-facing log identity: when a projected operation exists it ends in `/OP`, and no separate `op` field duplicates it. Nonempty `tags` is the row's complete deduplicated, sorted folksonomy; an untagged row omits it. When an OPEN bounded projection differs from the canonical body, it appends `"chunk":"showing <selected> of <complete>"` after `body`; otherwise it omits `chunk`. Complete-line extents use inclusive two-coordinate line regions. A cut inside a line uses four-coordinate, start-inclusive and end-exclusive regions with 1-based Unicode code-point columns. The row's `path` remains the canonical READ target. The block is data only - no prose leads the fence. `tokens` is the model-facing projection of the row's curation weight: the room OPEN adds and FOLD saves. A FIND's nonzero `itemsTokenTotal` weighs the complete matched set; a nonzero `returnedItemsTokenTotal` appears only when the returned page has a different weight. These are curation weights, not dollars. The invariants bind regardless of shape ({§packet}): addressability (`path`/`target`/`#channel`/coordinate-prefixed bodies), weighability (per-item `tokens`), honesty (every 4xx/5xx row and the exact body/display state). {§jsonplurnk} {§packet-jsonplurnk-exception}
1345
1355
 
1346
1356
  ### §retrieval-packet-metadata READ/FIND packet metadata
1347
1357
 
@@ -1464,7 +1474,7 @@ AST: `{ op: "FIND", target (scope), body: MatcherBody | null (predicate), signal
1464
1474
  - §find-glob-filter-on-content `body` matcher operates on entry content (glob/regex/jsonpath/xpath), per `plurnk.md` "Pattern Filtering"; the path-glob lives in the (target), not the body.
1465
1475
  - §find-semantic-selection Every matcher operates only over the candidate set selected by `(target)`; relation matchers do not bypass that selection. Semantic ranking is exhaustive within that candidate set, then applies the ordinary FIND result scope. Markerless semantic FIND therefore uses the same `<1,16>` default as every other matcher. Integers retain FIND's positional contract: `<N>` selects result N and `<N,M>` selects the inclusive range. A leading decimal first applies a minimum cosine-similarity threshold; following integers select positions within that ranked threshold set. Thus `<0.7,10,20>` means threshold 0.7 followed by results 10 through 20, while `<0.7>` applies the threshold and the ordinary first-16 page.
1466
1476
  - §find-scoped-isolation Workspace + scheme scoped — no cross-workspace/cross-scheme leakage.
1467
- - §find-result-projection **The authored target shape determines the result unit; result cardinality never changes it** ({§find-result-unit}). Returns `FindResult { status, content, mimetype, results, range, matchingPathCount, matchLocationCount, itemsTokenTotal, returnedItemsTokenTotal }`:
1477
+ - §find-result-projection **The authored target shape determines the result unit; result cardinality never changes it** ({§find-result-unit}). Returns `FindResult { status, content, mimetype, results, range, matchingPathCount, matchLocationCount, itemsWeightTotal, returnedItemsWeightTotal }`:
1468
1478
 
1469
1479
  | Target | Matcher body | `range.unit` | Result rows |
1470
1480
  |---|---|---|---|
@@ -1488,25 +1498,26 @@ AST: `{ op: "FIND", target (scope), body: MatcherBody | null (predicate), signal
1488
1498
  contains one nonempty, flat channel array per resource. Element `[0]` is always
1489
1499
  the default channel and carries the bare resource path; later elements carry
1490
1500
  their complete `path#channel` addresses. Each channel is
1491
- `{ path, mimetype, tokens, lines, parseIssues? }`; `parseIssues` is the
1501
+ `{ path, mimetype, weight, lines, parseIssues? }`; `parseIssues` is the
1492
1502
  positive-only advisory projection of `{§mimetype-parse-issues}` for the exact
1493
1503
  body derivation under `{§scheme-catalog-parse-issues}`. Resource-level `stream` and broad-match
1494
1504
  `matchLocationCount` live only on `[0]`. A single-channel resource is therefore
1495
1505
  a one-element array, with no path-owning wrapper or duplicated channel map.
1496
1506
  A terminal single-star path scope is a one-level map: direct entries retain
1497
1507
  that shape, while deeper first-segment directories collapse to the one-element
1498
- group `[{ path: "dir/**", items, tokens }]`, where the selector and both aggregates
1508
+ group `[{ path: "dir/**", items, weight }]`, where the selector and both aggregates
1499
1509
  describe the exact recursive subtree. Scope summaries are navigation
1500
1510
  metadata, not resources. Markerless FIND returns positions 1–16 in the
1501
1511
  selected unit; `<N,M>` selects an inclusive page and `<1,-1>` explicitly
1502
1512
  selects all. `range` reports the unit, complete result total, normalized
1503
- request, and returned positions ({§range-extent}). `itemsTokenTotal` weighs the complete matched set while
1504
- `returnedItemsTokenTotal` weighs the returned resource page; in exact
1513
+ request, and returned positions ({§range-extent}). `itemsWeightTotal` weighs the complete matched set while
1514
+ `returnedItemsWeightTotal` weighs the returned resource page; in exact
1505
1515
  location mode both weigh the one selected resource once. Resource order is
1506
1516
  rank for `~`semantic and candidate order otherwise; location order is dialect
1507
1517
  order and exact duplicates deduplicate. The intended drill-down is broad FIND
1508
1518
  to choose paths, exact-target FIND to choose locations, then exact READ.
1509
- `content` uses the shared generated-JSON projection
1519
+ `content` uses the shared generated-JSON projection and translates only this
1520
+ final model-facing representation from `weight` to `tokens`
1510
1521
  ({§json-result-rendering}), so universal packet numbering makes result
1511
1522
  ordinal N addressable as line N, matching `<N>` pagination without a second
1512
1523
  coordinate system. Pagination is the only FIND materialization bound; no
@@ -1573,8 +1584,9 @@ the loop continue; repeated offenses terminate through the engine's 500.
1573
1584
  AST: `{ op: "EXEC", target (optional runtime-specific target), body: string | null (runtime-specific input), signal: string | null (runtime tag), lineMarker (timeout/poll) }`.
1574
1585
 
1575
1586
  §exec-target-routing Engine routes unconditionally to the `exec` scheme,
1576
- resolves the runtime first, and enforces that runtime's required
1577
- {§executor-invocation} declaration before effect admission. Core owns target
1587
+ resolves the runtime first, selects its static {§executor-invocation} or exact
1588
+ {§executor-tool-registry} entry, and enforces that declaration before effect
1589
+ admission. Core owns target
1578
1590
  realization; neither filesystem type nor body presence may invent a target role
1579
1591
  the selected runtime did not declare. With no declared directory override,
1580
1592
  `cwd` is the workspace's `project_root`, where the File scheme writes — never
@@ -1605,6 +1617,13 @@ its one declared role whether the body is empty or non-empty. Runtime selection,
1605
1617
  target validation, and body/target relation failures therefore occur before
1606
1618
  effect classification or proposal creation.
1607
1619
 
1620
+ A runtime with {§executor-tool-registry} admits only the snapshot's exact
1621
+ literal targets. An absent target is 400; a target outside that closed enabled
1622
+ set is 404; neither reaches effect classification or proposal creation. The
1623
+ selected entry's invocation—not the family's structural fallback—owns body
1624
+ requiredness and roles. The executor independently rejects an unregistered
1625
+ target at its run boundary.
1626
+
1608
1627
  §exec-source-temporary A non-file `resource` target is materialized into one
1609
1628
  core-owned temporary file after acceptance. Core reparses the complete authored
1610
1629
  address and resolves one exact `<1,-1>` READ through
@@ -1633,15 +1652,24 @@ unchanged for proposal policy, application, stream registration, and
1633
1652
  effect-qualified hold policy. The post-acceptance materialization path never
1634
1653
  triggers reclassification.
1635
1654
 
1636
- §exec-registry-resolves The runtime slot (`signal`) selects an executor,
1637
- resolved against the boot-time `ExecutorRegistry`: siblings are discovered and
1638
- probed at startup, and availability is cached. An absent or empty tag selects
1655
+ §exec-registry-resolves The runtime slot (`signal`) selects an executor from
1656
+ the current workspace snapshot. Installed siblings form the immutable base:
1657
+ they are discovered and probed at startup, and availability is cached.
1658
+ Workspace capability providers may atomically overlay additional names under
1659
+ {§module-workspace-capabilities}; a name has one owner within a workspace, while
1660
+ independent workspaces may use the same name. An absent or empty tag selects
1639
1661
  `sh`; a non-empty tag selects exactly that registered executable tool. Unknown
1640
1662
  tags are refused 501 with direction to use only the advertised catalogue or
1641
1663
  put a complete command in bare `EXEC`; they are never reinterpreted as shell
1642
1664
  command words. An unavailable runtime is also 501 and carries the probe
1643
1665
  `detail`.
1644
1666
 
1667
+ For a family runtime, `ExecutorRegistry.toolRegistry(tag, workspaceId)`
1668
+ validates the one executor-owned snapshot used by packet presentation,
1669
+ dispatch admission, and pull-document materialization. Core performs no
1670
+ protocol discovery while building a packet and has no alternate tool
1671
+ catalogue.
1672
+
1645
1673
  Per-tool programs such as `go`, `cargo`, `make`, and `npm` do not earn executor tags merely because they are executables; they are complete shell commands under `## EXEC0` or `## EXEC0 [sh]`. Registered tags exist only for tools that own a distinct body, target, or output contract. {§exec-registry-resolves}
1646
1674
 
1647
1675
  **Timeout and poll — `<T,P>` on the `<L>` slot (grammar 0.74.20).** EXEC
@@ -1717,8 +1745,9 @@ The runtime scheme participates in the durable lookup; a completed `sh:///`
1717
1745
  stream cannot fall through an internal `exec`-only query. {§stream-control}
1718
1746
 
1719
1747
  §exec-env-scoped **Scoped environment.** An EXEC subprocess inherits the *project's* environment — its `.env`, the standard shell vars — so the model's commands run as the project expects; but never plurnk's own secrets: the provider API keys and `PLURNK_*` config are stripped before the spawn, so a model-executed command can't `printenv` the engine's keys. The service owns the scoping policy (the denylist); the executor spawns with the env it is handed.
1748
+
1720
1749
  - §exec-hold-until-concluded **The turn-hold exception** — for runtimes in `PLURNK_SERVICE_EXEC_HOLD` (a decision-table env, shipped listing the search family), an in-flight stream **pauses the cycle**: the next packet does not assemble until the stream concludes, so the model never burns a turn asking "are we there yet" about a result the engine controls end-to-end. This exception is limited to seconds-bounded runtimes whose final result the engine controls end-to-end. Bounded by `PLURNK_SERVICE_EXEC_HOLD_MS` and **fail-open**: at the cap the standard cycle resumes untouched (waits, wakes, polls). Zero grammar or teaching surface — the model emits EXEC followed by SEND signal `102` as ever; the wake-shaped world simply arrives one packet sooner. It extends selected runtimes beyond the ordinary {§worker-optimistic-settlement} cap before the next packet assembles. A bare entry holds ALL of a runtime's spawns; a `<runtime>:<effect>` suffix (`github:read`) holds only that effect-class — an MCP server is one runtime whose tools split (a `read` `get_issue` is instant; a `host` `run_migration` is a slow mutation), so an operator opts the known-fast read-class in without parking on the mutation. Conservative stays default: an arbitrary third-party server's latency never parks the engine unless a suffix opts a class in.
1721
- - §exec-entry-sink **The entry() sink** — an executor may *request* entry materialization (execs SPEC §2.6: every sink is a consumer-implemented callback; the executor owns zero substrate). The service implements it in exec dispatch: `entry(path, content: string | null, {tags, mimetype?})` upserts the entry, then journals ONE typed `EDIT` row in the reserved `plurnk` worker's log — the fs-fiction pattern, `source` = the calling worker, `tokens` = the content's count, and `attrs.kind="entry_materialized"`. The requested tags classify that journal row under {§log-item-tags}; they never become resource metadata or duplicate into attrs. Durable replay and clients retain that exact creation event. The model packet projects the typed event as a folded system `READ` of the resulting ordinary resource: its relevant truth is readable state now available in the environment, not an agent-authored mutation. **The executor owns no fetcher:** a `content: null` is a *declaration* — the service acquires the page through schemes-http's checked WebFetcher and accepts its model-facing body and available source/evidence channels {§html-materialization}. Generic public HTML follows the same origin-Markdown, configured Tavily, and local-projection routes as exact HTTP acquisition. A failed acquisition, body-production failure, materialization exception, or absent final projection rejects the sink and produces no HTTP entry, but does not invalidate a search runtime's upstream discovery row; materialization exceptions retain their cause in daemon diagnostics. A non-null `content` is the materialize-given-body path (the caller already holds the bytes and states their mimetype) and grants no provider authority. **No page body ever rides a packet**; the announcement is the folded row's path, weight, and log classifications, and the model READs/~queries what it chooses. Parallel `entry()` calls serialize on a per-spawn chain; a rejected call leaves the chain healthy. The spawn tail settles that complete chain before unregistering, so executor idleness and shutdown are barriers over its materialization writes. The narration context (one plurnk-worker turn) is lazy per spawn, not per entry.
1750
+ - §exec-entry-sink **The entry() sink** — an executor may *request* entry materialization (execs SPEC §2.6: every sink is a consumer-implemented callback; the executor owns zero substrate). The service implements it in exec dispatch: `entry(path, content: string | null, {tags, mimetype?})` upserts the entry, then journals ONE typed `EDIT` row in the reserved `plurnk` worker's log — the fs-fiction pattern, `source` = the calling worker, `weight` = the canonical resulting span's curation weight, and `attrs.kind="entry_materialized"`. The requested tags classify that journal row under {§log-item-tags}; they never become resource metadata or duplicate into attrs. Durable replay and clients retain that exact creation event. The model packet projects the typed event as a folded system `READ` of the resulting ordinary resource: its relevant truth is readable state now available in the environment, not an agent-authored mutation. **The executor owns no fetcher:** a `content: null` is a *declaration* — the service acquires the page through schemes-http's checked WebFetcher and accepts its model-facing body and available source/evidence channels {§html-materialization}. Generic public HTML follows the same origin-Markdown, configured Tavily, and local-projection routes as exact HTTP acquisition. A failed acquisition, body-production failure, materialization exception, or absent final projection rejects the sink and produces no HTTP entry, but does not invalidate a search runtime's upstream discovery row; materialization exceptions retain their cause in daemon diagnostics. A non-null `content` is the materialize-given-body path (the caller already holds the bytes and states their mimetype) and grants no provider authority. **No page body ever rides a packet**; the announcement is the folded row's path, weight, and log classifications, and the model READs/~queries what it chooses. Parallel `entry()` calls serialize on a per-spawn chain; a rejected call leaves the chain healthy. The spawn tail settles that complete chain before unregistering, so executor idleness and shutdown are barriers over its materialization writes. The narration context (one plurnk-worker turn) is lazy per spawn, not per entry.
1722
1751
 
1723
1752
  ### §proposal The proposal lifecycle
1724
1753
 
@@ -1758,6 +1787,23 @@ Core derives the contracts-owned `ProposalProjection` from the durable proposed
1758
1787
 
1759
1788
  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.
1760
1789
 
1790
+ ### §client-interactions Client-owned interaction lifecycle
1791
+
1792
+ A scheme or executor may pause its current operation on one
1793
+ `ClientInteractionRequest` ({§client-interaction-wire}). Core first inserts a
1794
+ pending-only `client_interactions` row bound to one exact
1795
+ workspace/worker/loop/turn ownership chain, then publishes the same validated
1796
+ `ClientInteractionProjection` through `loop/interaction` and reconnect
1797
+ discovery. The projection contains no workspace id or private upstream
1798
+ continuation state; those remain respectively in the event envelope and the
1799
+ awaiting operation owner.
1800
+
1801
+ Only the live process-local waiter can make a durable row resolvable. Resolution
1802
+ validates one `ClientInteractionResolution`, deletes the pending row, then
1803
+ releases the owner exactly once. Owner abort deletes the row and rejects the
1804
+ waiter. Reconnect discovery intersects durable rows with live waiters; restart
1805
+ removes ownerless rows without fabricating cancellation, payload, or replay.
1806
+
1761
1807
  ### §proposal-disposition Settlement authority and precedence
1762
1808
 
1763
1809
  `ProposalDisposition` is either `{ owner: "client" }` or `{ owner: "loop", decision: "accept" | "reject", outcome? }`. The decision table is complete and ordered:
@@ -1795,7 +1841,7 @@ pretends to reconstruct an opaque plugin connection.
1795
1841
 
1796
1842
  ### §chunk-accumulation Chunk accumulation
1797
1843
 
1798
- §chunk-accumulation-chunks-accumulate SSE event types, WS message types, exec stdout/stderr each map to a named channel. Each stored channel carries `content`, `mimetype`, `tokens`, and lifecycle `state` ({§channel-state}). The subscription registry owns durable subscription identity and process-local cancellation routing, not a second channel-state representation. Chunks accumulate into the channel as they arrive — not buffered until close.
1844
+ §chunk-accumulation-chunks-accumulate SSE event types, WS message types, exec stdout/stderr each map to a named channel. Each stored channel carries `content`, `mimetype`, curation `weight`, and lifecycle `state` ({§channel-state}). The subscription registry owns durable subscription identity and process-local cancellation routing, not a second channel-state representation. Chunks accumulate into the channel as they arrive — not buffered until close.
1799
1845
 
1800
1846
  ### §no-chunk-rows No per-chunk log rows
1801
1847
 
@@ -1850,6 +1896,7 @@ No generator. SQLite-optimal: STRICT (3.37+), `INTEGER PRIMARY KEY` aliasing, ex
1850
1896
  ### §sql-ts-boundary SQL/TS responsibility boundary
1851
1897
 
1852
1898
  **Lives in SQL:**
1899
+
1853
1900
  - Render queries — log assembly + the manifest catalog.
1854
1901
  - Cross-scope path collision (CHECK/trigger → 409).
1855
1902
  - Logical model-call identity plus cardinal physical provider-request lifecycle and immutable settlement constraints.
@@ -1857,6 +1904,7 @@ No generator. SQLite-optimal: STRICT (3.37+), `INTEGER PRIMARY KEY` aliasing, ex
1857
1904
  - Entry-vs-log integrity.
1858
1905
 
1859
1906
  **Lives in TS:**
1907
+
1860
1908
  - Status-bubble rules (`turn.status` → `loop.status` → `worker.status` → `workspace.status`). Engine UPDATEs explicitly; CHECK constraints enforce; triggers fight branching state machines.
1861
1909
  - Tokenization (provider-bound; hot-swap re-tokenizes per {§tokenomics}).
1862
1910
  - Provider dispatch, request-accounting validation, and exact-decimal aggregate projection through the shared contracts-owned path.
@@ -2005,7 +2053,7 @@ Model selection: separate alias cascade in `ProviderRegistry` ({§provider-insta
2005
2053
  | `PLURNK_SERVICE_EMISSION_ATTEMPTS` | `3` | Completed provider responses allowed beneath one engine turn before an untrustworthy model-turn frame exhausts admission. Bounded interior operation errors are admitted and do not spend this budget. Consecutive exhaustion after the one informed recovery turn terminates independently of strikes. |
2006
2054
  | `PLURNK_SERVICE_PREVIEW_LINES` | `16` | Maximum lines in an ordinary bounded log-body projection ({§body-projection}). |
2007
2055
  | `PLURNK_SERVICE_PREVIEW_CHARS` | `2560` | Maximum Unicode code points in an ordinary bounded log-body projection, with CRLF treated as one indivisible separator; independently contains single-line bodies ({§body-projection}). |
2008
- | `PLURNK_SERVICE_PROMPT_PROJECTION` | `25%` | Aggregate ruler-weight share of the enforced packet budget available to automatic prompt-body projection ({§prompt-projection}); alias-scoped overrides are supported. |
2056
+ | `PLURNK_SERVICE_PROMPT_PROJECTION` | `25%` | Aggregate curation-weight share of the provider-derived input capacity available to automatic prompt-body projection ({§prompt-projection}); alias-scoped overrides are supported. |
2009
2057
  | `PLURNK_SERVICE_LINE_ANCHOR_CONTEXT_LINES` | `2` | Complete neighboring lines hashed on each side of a model-facing line anchor ({§line-anchors}). |
2010
2058
  | `PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES` | `2` | Surrounding and landed lines shown at each EDIT result boundary ({§edit-result-receipt-projection}). |
2011
2059
  | `PLURNK_SERVICE_MIN_CYCLES` | `3` | Min repetitions before cycle detection fires ({§engine-rails}). |
@@ -2019,6 +2067,7 @@ Model selection: separate alias cascade in `ProviderRegistry` ({§provider-insta
2019
2067
  Every core knob listed is enforced at its owning read site; `.env.defaults` is the authoritative default ({§operator-config-env-defaults}). Provider, scheme, executor, mimetype, and client-interface knobs are documented by their owning packages and appear in the assembled catalog.
2020
2068
 
2021
2069
  **Two override semantics — ceiling vs default.** Which kind a var is determines what "override" means across the cascade:
2070
+
2022
2071
  - **Ceiling** (most-restrictive-wins) — an operator-set hard bound nothing downstream may exceed: not a lower-precedence file, not a per-workspace constraint, not a per-call seam argument. `PLURNK_SERVICE_GIT_ALLOWED` ({§operator-config-git-ceiling}), `PLURNK_SERVICE_MAX_COMMANDS`, `PLURNK_SERVICE_MAX_STRIKES`, and `PLURNK_SERVICE_MAX_TURNS` (`-1` ships it off; a positive value caps the per-call request). The sandbox/cost guarantee: the operator caps it; no client widens it.
2023
2072
  - **Default** (explicit-wins) — a fallback the most-specific setter replaces freely: `PLURNK_MODEL` (a `runLoop({alias})` request overrides it) and the config-time vars (`HOST` / `PORT` / `DB_PATH`).
2024
2073
 
@@ -2055,14 +2104,13 @@ before assembled package defaults fill unset values:
2055
2104
  | Configuration family | Zero-pin treatment |
2056
2105
  |-----------------------------------------------------------|--------------------|
2057
2106
  | Any `PLURNK_PROVIDERS_CONTEXT_WINDOW` | Remove |
2058
- | Alias-specific reasoning/completion reserves and safety | Remove |
2059
- | Any `PLURNK_SERVICE_PROMPT_BUDGET` | Remove |
2107
+ | Alias-specific output and reasoning budgets | Remove |
2060
2108
  | Model selection, routes, and credentials | Retain |
2061
- | Bare shipped reserve and safety defaults | Retain |
2109
+ | Bare shipped generation-envelope defaults | Retain |
2062
2110
  | Unrelated environment | Retain |
2063
2111
 
2064
2112
  The floor reports every removed key. A gate that succeeds only with those pins
2065
- is red because provider capacity and the natural prompt budget did not derive for
2113
+ is red because provider capacity did not derive for
2066
2114
  a fresh-user configuration.
2067
2115
 
2068
2116
  §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` ships **off** (`-1` = no cap; the loop ends via SEND, budget, strikes, or cycle detection) and, when an operator sets a positive value, the per-call request is `min()`-capped against it.
@@ -2087,10 +2135,12 @@ The composition families remain distinct so one setting's semantics never
2087
2135
  leak into another.
2088
2136
 
2089
2137
  *Defaults — explicit-wins (the client replaces/merges freely):*
2138
+
2090
2139
  - §operator-config-workspace-files-items `settings.filesItems` (number) **replaces** `PLURNK_SERVICE_FILES_ITEMS` for the workspace: a one-shot opens clean (`0`, no preview), with ordinary markerless pages (`-1`), or with the file list explicitly capped (`N`, other surveys remain markerless). A single scalar — the client value wins outright.
2091
2140
  - §operator-config-workspace-md-docs `settings.mdDocs` (`[{alias, content}]`) **unions** with the server's `PLURNK_SERVICE_MD_*` docs, keyed by alias — a client adds its own repo docs atop the operator's systemwide policy doc. On alias collision the client wins before I/O (a deliberate shadow), so the unselected operator path is not read; every selected non-empty operator path is required, and absence or another read failure rejects materialization with its cause. The client sends content (it owns the file), not a path.
2092
2141
 
2093
2142
  *Ceilings — most-restrictive-wins (the client may only narrow, never widen):*
2143
+
2094
2144
  - §operator-config-workspace-max-commands `settings.maxCommands` (number)
2095
2145
  **min()s** the `PLURNK_SERVICE_MAX_COMMANDS` per-emission cap for the
2096
2146
  workspace: a client tightens the runaway-op guard and never raises it past
@@ -2105,10 +2155,11 @@ leak into another.
2105
2155
  {§executor-policy}. Keys are matched case-insensitively and must be
2106
2156
  `PLURNK_EXECS_ONLY` or `PLURNK_EXECS_<canonical-runtime-tag>`; MCP connection
2107
2157
  configuration and non-string values are rejected. The boot-discovered
2108
- registry is authoritative and the workspace layer only intersects it: it
2109
- cannot register or re-enable a runtime. A canonical key for a currently
2110
- absent tag is accepted as inert policy and applies if that tag later belongs
2111
- to the boot set. Dispatch, the model-facing capability sheet, and executor
2158
+ effective workspace registry is authoritative and the settings layer only
2159
+ intersects it: settings cannot register or re-enable a runtime. A canonical
2160
+ key for a currently absent tag is accepted as inert policy and applies if a
2161
+ workspace capability provider later publishes that tag. Dispatch, the
2162
+ model-facing capability sheet, and executor
2112
2163
  document materialization use the same registered-set intersection and policy
2113
2164
  predicate, so a workspace-disabled tag is neither executable nor taught.
2114
2165
 
@@ -2132,7 +2183,7 @@ names, request validation, discovery result, and event projection.
2132
2183
  ```mermaid
2133
2184
  flowchart LR
2134
2185
  register["Daemon.registerModule"] --> setup["module.setup(ModuleSetupSeam)"]
2135
- setup --> capabilities["Register runtimes, schemes,<br/>and extension actions"]
2186
+ setup --> capabilities["Register static capabilities,<br/>workspace providers, and actions"]
2136
2187
  capabilities --> ready["Schemes ready; docs published;<br/>durable lifecycle recovered"]
2137
2188
  ready --> start["module.start(CoreSeam)"]
2138
2189
  start --> interface["Module-owned listener<br/>and client protocol"]
@@ -2148,11 +2199,44 @@ recovers durable lifecycle, and starts modules in registration order. Shutdown
2148
2199
  closes started and self-closing modules in reverse order and surfaces aggregated
2149
2200
  close failures.
2150
2201
 
2151
- | Setup function | Contract |
2152
- |------------------------------------------------------------------------|----------|
2153
- | `registerRuntime({ decl, executor, availability, scheme? })` | Admits one canonical tag under {§executor-runtime-declaration}, then adds its executor and optional claimed scheme facet atomically. |
2154
- | `registerScheme(name, handler)` | Adds one addressable scheme handler; scheme readiness and model-facing capability publication remain core-owned. |
2155
- | §module-action-registration `registerModuleAction(name, handler)` | Adds a non-empty, extension-unique name and a handler receiving only `Readonly<Record<string, unknown>>`. Core supplies no implicit workspace or transport context. A client-interface module decides whether and how that name becomes public, and owns collisions with its built-ins. |
2202
+ §module-shutdown-order `Daemon.stop()` returns only after active worker drains,
2203
+ streaming producers, derivations, mimetypes, schemes, and every accepted
2204
+ conclusion-wake task have settled in dependency order. The supervisor owns each
2205
+ asynchronous wake task from acceptance through settlement; a task failure
2206
+ participates in the shutdown aggregate. The database may be released only after
2207
+ the final wake barrier resolves.
2208
+
2209
+ ```mermaid
2210
+ flowchart LR
2211
+ stop[Stop accepting work] --> drains[Settle worker drains]
2212
+ drains --> producers[Settle streaming producers]
2213
+ producers --> resources[Dispose derivations,<br/>mimetypes, and schemes]
2214
+ resources --> wakes[Settle conclusion wakes]
2215
+ wakes --> database[Release database]
2216
+ ```
2217
+
2218
+ | Setup function | Contract |
2219
+ |---|---|
2220
+ | `registerRuntimes([{ decl, executor, availability, scheme? }, ...])` | Validates the complete canonical tag set under {§executor-runtime-declaration}, then publishes every process-wide executor and optional claimed scheme facet atomically. |
2221
+ | `registerScheme(name, handler)` | Adds one process-wide addressable scheme handler; scheme readiness and model-facing capability publication remain core-owned. |
2222
+ | §module-action-registration `registerModuleAction({ name, scope, handler })` | Adds one non-empty, extension-unique action. `scope` is exactly `worldless` or `workspace`; the handler receives validated params and a separate matching context. A workspace context contains the trusted bound `workspaceId`, never a client parameter. A client-interface module decides whether and how the name becomes public and owns collisions with its built-ins. |
2223
+ | §module-workspace-provider `registerWorkspaceCapabilityProvider(namespaceOwner, provider)` | Registers one extension-unique provider whose `hydrate(workspaceId)` reconstructs its effective snapshot. Core invokes every provider for existing workspaces before capability publication and for a new workspace before that workspace is returned or advertised. |
2224
+ | §module-workspace-state `readWorkspaceModuleState(workspaceId, namespaceOwner)` | Reads the provider's one nullable JSON state value. Core owns workspace isolation and storage; the provider owns and validates its schema. Secret values are forbidden when a durable symbolic reference can identify their authoritative source. |
2225
+ | §module-workspace-capabilities `replaceWorkspaceCapabilities({ workspaceId, namespaceOwner, state, runtimes })` | Replaces one provider's complete durable state and runtime/scheme snapshot at a quiescent workspace boundary. Core validates base/peer namespace claims before mutation, blocks new turns, commits the snapshot, and reconciles pull docs as one operation. Failure restores the prior state and presentation. The empty runtime set removes that provider's workspace namespace. |
2226
+
2227
+ §module-workspace-quiescence **A capability snapshot changes only between
2228
+ workspace operations.** A replacement attempt while a turn or another
2229
+ capability mutation owns the workspace fails 409 instead of waiting behind an
2230
+ unbounded proposal. Candidate discovery may occur before the gate, but the
2231
+ provider must re-check its old connection for active user work after acquiring
2232
+ the gate. Infrastructure-owned watches may be cancelled during replacement;
2233
+ an active request, input exchange, or Task keeps the old snapshot authoritative
2234
+ and makes replacement fail 409.
2235
+
2236
+ The version-1 baseline table `workspace_module_state` stores one JSON value per
2237
+ `(workspace_id, namespace_owner)`. It is not an alternate registry: executable
2238
+ and resource presentation always comes from the in-memory snapshot reconstructed
2239
+ by the registered provider. Deleting a workspace cascades its module state.
2156
2240
 
2157
2241
  ### §methods CoreSeam function set
2158
2242
 
@@ -2165,13 +2249,15 @@ Its function names are transport-neutral library calls, not public wire names.
2165
2249
  | §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. |
2166
2250
  | §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. |
2167
2251
  | §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. |
2252
+ | §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. |
2253
+ | §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. |
2168
2254
  | §methods-loop-run Loops | `runLoop({ workspaceId, workerId, prompt, maxTurns?, flags?, openPaths?, alias?, model?, childAlias?, childModel? })` | Validates a model worker and provider policy, persists it with the effective turn ceiling, then returns an immediate status-100 acknowledgement with `loopId` and `action`. The exact terminal result arrives only through `loop/terminated`; parking and resuming do not replace the loop. |
2169
2255
  | §methods-loop-cancel Loops | `cancelDrain(workerId, reason?)` | Begins durable structured cancellation of the worker tree and reaps its process-local scopes. The boolean reports whether process-local work existed when called; queued or parked durable work is still terminalized when it is `false`. |
2170
2256
  | §methods-op-mirror Client dispatch | `dispatchClientAction({ workspaceId, workerId, statements })` | Dispatches already-parsed grammar statements as one client action and one journal segment. Every statement is an ordered turn, and every committed `log/entry` is emitted before the action promise resolves; a proposal may keep that promise and segment open until resolution. Core exposes no per-op method family. |
2171
2257
  | Client observation | `look({ workspaceId, workerId, statement })` | Runs an already-parsed READ through the full resolver without a log row. A non-READ statement is rejected ({§op-look}). |
2172
2258
  | §methods-log-read Reads | `readLog({ workspaceId, workerId, ...coordinate })` | Ownership-checks the worker, then reads by ids, recency, or the complete `loopSeq`/`turnSeq`/`sequence` display coordinate. `limit` defaults to 100 and is capped at 1000. |
2173
2259
  | §methods-entry-read Reads | `readEntry({ workspaceId, workerId, target, channel?, offset? })` | Resolves the selector from that worker's perspective and returns {§entry-read-result}, either complete or as one channel suffix, without creating action evidence. |
2174
- | Providers | `listProviders()` | Lists configured aliases with provider/model identity, active state, and the effective `promptBudget` when core can establish it. |
2260
+ | Providers | `listProviders()` | Lists configured aliases with provider/model identity, active state, and the effective provider-derived `inputCapacity` when known. |
2175
2261
  | Client capabilities | `listClientDisplayCapabilities()` | Composes sorted scheme declarations ({§manifest-client-display}) followed by sorted MIME declarations ({§mimetype-client-display}) into the validated shared wire ({§client-display-capabilities}). The internal `exec` operation handler is excluded; its addressable runtime-tag scheme faces remain included. |
2176
2262
  | §methods-workspace-create Workspace lifecycle | `createWorkspace({ name?, projectRoot?, settings?, constraints? })` | Validates `settings` through {§operator-config-workspace-settings}, creates the world and its client envelope, materializes current docs and constraints, starts derivation warming, and emits global `workspace/created`. `projectRoot` is established here or the workspace remains headless. |
2177
2263
  | §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. |
@@ -2182,7 +2268,7 @@ Its function names are transport-neutral library calls, not public wire names.
2182
2268
  | Workspace metadata | `constrain(...)`, `unconstrain(...)`, `listConstraints(...)`, `listMembers(...)` | Owns the membership overlay and returns its resolved effects; clients do not reimplement constraint semantics. |
2183
2269
  | §methods-workspace-prompts Workspace metadata | `listPrompts(workspaceId, limit?)` | Returns nonempty loop-seed prompts from the workspace's model-origin root conversations, newest-first. The positive limit defaults to 100; spawned and forked child prompts are excluded. |
2184
2270
  | Workspace metadata | `listWorkspaces()`, `listWorkers(...)`, `workspaceDerivationStatus(...)` | Reads current workspace topology and derivation progress. |
2185
- | Extension actions | `listModuleActions()`, `invokeModuleAction(name, params)` | Lists setup-registered names in sorted order and invokes the exact registered handler. Missing names fail; handler values remain opaque to core. |
2271
+ | Extension actions | `listModuleActions()`, `invokeModuleAction(name, params, context)` | Lists setup-registered `{ name, scope }` descriptors in sorted order. Invocation requires a context matching the registered scope; missing names, forged scope, and missing workspace identity fail before the owner runs. Handler values remain opaque to core. |
2186
2272
 
2187
2273
  §methods-loop-run-fold-consistency **A folded prompt cannot silently reconfigure
2188
2274
  its loop.** When `runLoop` targets an active or 202-parked loop, core appends the
@@ -2271,8 +2357,9 @@ active lifecycle behind. LOOK text anchors resolve through the same
2271
2357
  | Event | Payload | When fired |
2272
2358
  |--------------------------------------------------------------|---------|------------|
2273
2359
  | §notifications-log-entry-notify `log/entry` | `{ entry: LogEntry }` | A `log_entries` row is committed. |
2274
- | §notifications-loop-terminated `loop/terminated` | `{ workerId, loopId, result, hitMaxTurns, turnIds, usage: { accounting, contextTokens, promptBudget, meta }, attributions }` | One loop reaches a terminal state. `result` is the exact universal operation result, including its RFC 9457 Problem Details on failure. `accounting` is the loop's contracts-owned {§provider-accounting}; `contextTokens` and `promptBudget` are the latest-turn gauge pair, and `meta` is that turn's opaque provider bag. `attributions` is the sorted union of exact provider-request evidence ({§attribution}), separate from accounting. Worker and loop are an inseparable owning coordinate. |
2360
+ | §notifications-loop-terminated `loop/terminated` | `{ workerId, loopId, result, hitMaxTurns, turnIds, usage: { accounting, curationWeight, curationBudget, contextTokens, contextCapacity, meta }, attributions }` | One loop reaches a terminal state. `result` is the exact universal operation result, including its RFC 9457 Problem Details on failure. `accounting` is the loop's contracts-owned {§provider-accounting}; the two curation facts and two physical-context facts follow {§tokenomics-client-gauge}; `meta` is that turn's opaque provider bag. `attributions` is the sorted union of exact provider-request evidence ({§attribution}), separate from accounting. Worker and loop are an inseparable owning coordinate. |
2275
2361
  | §notifications-loop-proposal `loop/proposal` | contracts-owned `ProposalProjection` | Dispatch pauses on a durable 202 proposal. `disposition` is the sole authority for whether a client presents review UI; live and reconnect share {§proposal-projection}. |
2362
+ | §notifications-loop-interaction `loop/interaction` | contracts-owned `ClientInteractionProjection` | An operation is paused on client input. Live delivery and reconnect discovery share {§client-interactions}; workspace scope remains the event envelope. |
2276
2363
  | §notifications-workspace-created `workspace/created` | `{ id, name, projectRoot }` | A workspace is created. This is the only current global event. |
2277
2364
  | §notifications-workspace-branch-batch `workspace/branch-batch` | Branch-batch lifecycle payload | A branch batch enters queued, running, completed, failed, or recovery-required state. |
2278
2365
  | §notifications-stream-event-on-channel-change `stream/event` | `{ entryId, workerId, target, channel, state, contentLength, mimetype?, loop_seq?, turn_seq?, sequence? }` | Channel content grows or channel state transitions. `workerId` is the entry owner and read perspective; `target` is its canonical URI. The optional coordinate is copied from schemes whose addresses carry one. Core-managed channel writes include the current stored `mimetype`, which may change per call ({§channel-mimetype}); the generic plugin notification capability does not require it. It carries metadata, not content; consumers read bytes from the stated worker perspective. |
@@ -2384,63 +2471,40 @@ trust gate; an external client action cannot invoke it. Whole-list transformatio
2384
2471
  the fork-avoidance valve for alternate packet shapes ({§ecosystem}), while
2385
2472
  grinding and folding remain closed engine concerns.
2386
2473
 
2387
- ### §tokenomics Tokenomics: four quantities, one model-facing ruler
2474
+ ### §tokenomics Tokenomics: four facts, one curation ruler
2388
2475
 
2389
2476
  Token accounting distinguishes the artifact being measured, the unit, and the
2390
2477
  time of measurement.
2391
2478
 
2392
- | Quantity | Source and unit | When | Contract |
2393
- |:--------------------------------------------------------------------|:------------------------------------------------------------------|:------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
2394
- | Provider usage | Provider-reported input/output tokens and cache/reasoning details | After each physical request | Durable monetary and transport forensics under {§provider-usage}; never the pre-call packet budget. |
2395
- | Packet render-weight | `rulerCount = ceil(chars/2)` over rendered slots | Every packet build | Drives the Budget readout and grinder. |
2396
- | Stored content-depth | The same ruler over entry/log content | When content is written | Weights catalog and log rows without per-model workspace state. |
2397
- | §tokenomics-context-envelope-admission Hard context-envelope admission | Provider measurement of the complete `PacketWire` message request | After the grinder leaves negative ruler pressure | Exact counts and proven upper bounds may admit the request within the effective total envelope. Estimates and unknown capacity fail closed. Never model-facing. |
2398
-
2399
- - §tokenomics-tokens-stored-at-write **Ruler tokens, stored at write.** `entry_channels.tokens` and `log_entries.tokens` are populated with the model-independent `rulerCount` when their content is written. The stored number is a stable content-depth measurement, not a provider-specific prediction.
2400
- - §tokenomics-render-weight-budget **Render-weight budget.** The budget headline `ceiling`, `tokenUsage`, `tokensFree` — is the ruler measurement of the *assembled packet* after section transforms and budget substitution. Core measures minimum-width probes, monotonically expands fields that do not fit, then right-aligns the final values into those same widths; final substitution is length-invariant and displayed usage equals the stored request weight. A `SUM` of stored content-depth would measure different artifacts and cannot substitute for packet render-weight.
2401
- - §tokenomics-context-percent **Prompt-budget percent.** The headline carries usage as a percent of the enforced model-facing budget — `usage Y (P%)` — beside the absolutes. It reads the ceiling already in hand; no extra provider call.
2402
- - §tokenomics-window-partition **Hard capacity and virtual pressure are
2403
- separate.** The provider owns the effective total context envelope and
2404
- generation settings. The envelope is already the minimum of model capacity
2405
- and any stricter operator context cap ({§model-fact-resolution}); that cap is
2406
- final. Core derives the natural prompt gauge from the envelope and its packing-safety
2407
- margin. `PLURNK_SERVICE_PROMPT_BUDGET` is an optional alias-scoped virtual
2408
- ceiling:
2409
- `promptBudget = min(configuredPromptBudget, naturalPromptCapacity)`. Unset
2410
- uses the natural capacity. The virtual ceiling controls only the packet gauge
2411
- and grinder; it never changes the hard context envelope, reasoning, completion, or
2412
- `maxTokens`.
2479
+ | Fact | Owner and unit | Time | Contract |
2480
+ |:-----|:---------------|:-----|:---------|
2481
+ | Core curation weight | `contentWeight = ceil(chars/2)` over channel content, canonical log bodies, and rendered packet slots | Write/build | Stable, model-independent pressure and OPEN/FOLD savings; never a tokenizer claim. |
2482
+ | §tokenomics-context-envelope-admission Provider input capacity | Provider model limits and configured total output envelope, in provider tokens | Before every logical request | `min(maxInputTokens, contextWindow - outputBudget)` over the known terms. The provider alone measures the complete request and admits, defers, or rejects it. |
2483
+ | Provider generation envelope | Provider total output budget and optional reasoning subset, in provider tokens | Before every logical request | One total output budget includes hidden reasoning. A reasoning budget is a strict subset, never an additive reserve. |
2484
+ | 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. |
2485
+
2486
+ - §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.
2487
+ - §tokenomics-render-weight-budget **Packet curation budget.** The Curation headline measures the *assembled packet* after section transforms and readout substitution. 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 displayed weight equals the stored request weight. A `SUM` of stored content weights measures a different artifact and cannot substitute for packet render weight.
2488
+ - §tokenomics-context-percent **Curation percent.** The headline carries packet weight as a percentage of its curation budget beside the absolutes. It reads the capacity already resolved by the provider; no extra provider call.
2489
+ - §tokenomics-window-partition **One capacity derivation; no service-side token budget.** The provider owns model limits and the configured total output envelope. Its resolved `inputCapacity` is the numeric curation-budget calibration as well as the physical denominator exposed to clients. That reuse is policy, not a unit conversion: Core compares stable curation weight with it only to shape context, while provider request-shaped evidence alone admits or rejects 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.
2413
2490
  - §tokenomics-prompt-projection-share **Prompt projection is stable packet policy.**
2414
2491
  `PLURNK_SERVICE_PROMPT_PROJECTION` is a required alias-scoped percentage in
2415
- `(0, 100)`. It allocates that share of the enforced `promptBudget` to the
2492
+ `(0, 100)`. It allocates that share of the provider-derived curation budget to the
2416
2493
  aggregate automatic prompt-body projection. It does not bound stored prompt
2417
2494
  size, provider capacity, an explicit READ/FIND result, or the complete packet.
2418
2495
  Basing the share on the stable ceiling rather than current free weight keeps
2419
2496
  one prompt's projection byte-stable as the worker log evolves.
2420
- - §tokenomics-window-unpollable-deliberate **Unknown provider capacity stays
2421
- unknown.** Without a configured virtual ceiling, an unknown provider window
2422
- leaves the prompt uncapped and omits denominator-dependent gauge telemetry. A
2423
- configured virtual ceiling still supplies PLURNK's curation gauge without
2424
- pretending to describe hard capacity. A negative-gauge request cannot be
2425
- admitted without a known effective context and generation envelope.
2426
-
2427
- §tokenomics-client-gauge **The client gauge pairs current occupancy with its
2428
- effective ceiling.** A model switch changes both latest-turn values together;
2429
- the loop's aggregate provider accounting remains monetary evidence, not a gauge
2430
- input.
2431
-
2432
- | Surface | `contextTokens` | `promptBudget` |
2433
- | -------------------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
2434
- | `loop/terminated.usage` | `inputTokens` from the latest settled physical request on the latest turn; `null` when absent or unknown | Latest turn's enforced packet allowance; `null` when uncapped or unknown |
2435
- | `providers.list` alias row | not present | Active alias's current enforced packet allowance; `null` when not established |
2497
+ - §tokenomics-window-unpollable-deliberate **Unknown provider capacity stays unknown.** When the provider cannot derive `inputCapacity`, Core omits denominator-dependent curation telemetry and uses the ordinary bounded prompt projection. The provider still sends requests whose measurement or limits are estimates or unavailable: ambiguity defers to the upstream capacity oracle rather than becoming a local rejection.
2498
+
2499
+ §tokenomics-client-gauge **Clients receive curation and physical occupancy as separate pairs.** `loop/terminated.usage` carries latest-turn `curationWeight`/`curationBudget` and latest-emission-call `contextTokens`/`contextCapacity`; each unknown fact is `null`. 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.
2436
2500
 
2437
2501
  - **Derivation is eager and exhaustive.** Workspace creation and searchable-resource changes start one coalesced warm. The first model turn joins that warm; later turns derive intervening changes before dispatch. No model operation observes partial graph or vector coverage. A semantic query ranks every eligible candidate in scope, so lexical overlap never gates vector recall. With no embedder, readable-content FTS is the explicit keyword fallback. Progress notices make the wait visible; latency is never hidden by partial semantics. {§derivation-exhaustive}
2438
2502
  - §membership-binary-sniff **Binary truth beats the label; no entry dominates the corpus.** A tracked member whose HEAD bytes contain NUL enters {§membership-source-projection} as `application/octet-stream` **regardless of what extension-based detection claims**; byte-level evidence outranks a default label. Every eligible text is tiled losslessly to the embedder window and every tile is embedded before its derivation attaches; semantic ranking max-pools the best chunk per candidate.
2439
- - §tokenomics-agnostic-ruler **One model-agnostic ruler.** The daemon runs workers on different models in one workspace concurrently, while catalog and log accounting are workspace-wide. The complete model-facing ledger therefore uses `rulerCount = ceil(chars/2)`: one content has one number regardless of which model reads it, with no per-model workspace state or recount pass. Nonnegative-gauge packets rely on that ruler. Only a negative-gauge packet invokes the shipping provider's request-shaped hard-envelope measurement.
2440
- - §tokenomics-neutral-telemetry **Budget telemetry is state, with one pressure alarm.** The model-facing Budget ordinarily has exactly one line: token ceiling, current usage and percentage, and free tokens. When free tokens are negative, exactly one second line reads `Context Token Budget Panic: YOU MUST FOLD or KILL enough less-relevant log items to restore free tokens.` Per-entry weights remain on log rows where they describe the entries themselves. Packet-level composition, rankings, and visualizations are absent. OPEN/FOLD/KILL remain the ordinary curation capabilities; no deterministic recovery process is prescribed.
2441
- - §tokenomics-content-hash-identity **Content identity, not per-tokenizer counts.** Static channel writes stamp `content_hash` (SHA-256) as a stable per-content identity. `tokens` is stored in ruler units beside that content and is never keyed or recomputed by model.
2503
+ - §tokenomics-agnostic-ruler **One model-agnostic curation ruler.** The daemon runs workers on different models in one workspace concurrently, while catalog and log accounting are workspace-wide. `contentWeight = ceil(chars/2)` therefore gives one content one stable number without per-model workspace state or recount passes. It controls curation only; every provider call independently measures the complete request as well as it can.
2504
+ - §tokenomics-neutral-telemetry **Curation telemetry is state, with one pressure alarm.** The model-facing Curation section ordinarily has one line: budget, current packet weight and percentage, and free weight. When free weight is negative, exactly one second line directs the model to FOLD or KILL less-relevant log items. Per-entry weights remain on log rows where they describe OPEN cost and FOLD savings. Packet-level composition, rankings, and physical token speculation are absent.
2505
+ - §tokenomics-content-hash-identity **Content identity, not per-tokenizer counts.** Static channel writes stamp `content_hash` (SHA-256) as stable content identity. `weight` is stored beside that content and is never keyed or recomputed by model.
2442
2506
  - §tokenomics-provider-usage **Provider accounting is physical-request evidence, not curation state.** Every issued physical request has one durable pre-I/O `provider_requests` identity and settles once as response or error. Each record preserves conventional {§provider-usage} quantities and required {§provider-cost} evidence; an unreported quantity remains absent, including on response-less failures, and is never replaced by zero. `model_calls` own logical response/failure evidence, `turn_attempts` specialize emission admission, and `provider_requests` are the sole durable accounting representation. Emissions, BARE calls, rejected responses, retries, failovers, and errors therefore remain cardinal and ordered. Turn, loop, worker, workspace, digest, and protocol accounting are derived from those records through the shared {§provider-accounting} projection; only emission calls contribute the latest-packet context gauge. The baseline stores no floating-point money, denormalized totals, or rollup triggers. A documented direct charge becomes `charged`; otherwise the provider may compute an exact-decimal USD `estimated` amount from complete usage and the exact model's Models.dev rates; insufficient evidence becomes `unknown`. Derived `costUsd` is an exact decimal string only when every request is expressible in USD and is `null` otherwise. This is operational request accounting, not invoice reconciliation. Output and reasoning are quantities the model cannot FOLD, so they never alter the model-facing Budget ledger.
2443
- - §tokenomics-negative-pressure **Negative ruler pressure is honest and nonterminal.** The readout describes the packet the model actually receives: usage and percent may exceed the virtual ceiling, and free tokens equal `ceiling - usage` without flooring. Crossing the ceiling records {§grinder-overflow-problem}, then the grinder ({§grinder}) folds only the newest boundary. If the rebuilt packet remains negative but exact or proven-bounded request evidence fits the effective hard envelope, generation proceeds under the ordinary operation contract. Overflow creates no strike or one-turn quota; only failed hard-envelope admission terminally rejects generation.
2507
+ - §tokenomics-negative-pressure **Negative curation pressure is honest and nonterminal.** The readout describes the packet the model actually receives: weight and percent may exceed the curation budget, and free weight equals `budget - weight` without flooring. Crossing the budget records {§grinder-overflow-problem}, then the grinder ({§grinder}) folds only the newest boundary. Remaining curation debt never decides physical admission, creates a strike, or establishes a one-turn quota.
2444
2508
 
2445
2509
  ### §membership Workspace identity, membership, disk co-location
2446
2510
 
@@ -2517,6 +2581,7 @@ materialization verdicts and folded ambient rows for every acquired page.
2517
2581
  - §membership-create-parents **Parent-complete creation.** An accepted File creation—whether authored as EDIT or as a COPY/MOVE destination—recursively creates missing parent directories before writing and registering the new member.
2518
2582
 
2519
2583
  **The overlay — `pick | view | hide`, removed by `drop`.** A `workspace_constraints` table is the client's supersede over Git. Resolved membership is `(project repository files ∪ pick) − hide`, with `view` enforced at the edit gate.
2584
+
2520
2585
  - §membership-auto-add **Auto-add** — the project repository's membership is its tracked `ls-files` plus untracked-but-not-ignored files (`git ls-files --others --exclude-standard`), with `git` origin. A model-created file is a member the moment it exists—no `git add`—while `.gitignore` still filters it.
2521
2586
  - §membership-overlay-pick **`pick`** — admit an untracked file git misses: a targeted client-dictated `node:fs` glob scan over untracked matches (files only), 'constraint' origin, reconciled like git members. Enumerated, so the manifest stays exhaustive. git-absent, `pick` is the *sole* membership source.
2522
2587
  - §membership-overlay-hide **`hide`** — exclude a tracked file: resolution drops matches (`node:path.matchesGlob`) and reconciles so the entry set *equals* the member set. The lever to exclude a committed-but-sensitive tracked file; `entries.membership_origin` keeps reconciliation off model-created members.
@@ -2584,46 +2649,53 @@ explicit.
2584
2649
 
2585
2650
  ### §grinder Budget enforcement: the grinder
2586
2651
 
2587
- The grinder is the one pre-provider curation path for the model-facing prompt
2588
- gauge. Hard admission remains a separate predicate over the effective total
2589
- context envelope:
2652
+ The grinder is the one pre-provider curation path for the model-facing gauge.
2653
+ It never decides physical admission. Every request then crosses the provider's
2654
+ request-shaped capacity boundary, where proven exact overflow may reject
2655
+ pre-I/O and ambiguous evidence defers to upstream:
2590
2656
 
2591
2657
  ```mermaid
2592
2658
  flowchart TD
2593
- assemble["Assemble and measure<br/>request packet"] --> policy{"Nonnegative<br/>ruler gauge?"}
2594
- policy -->|yes| unchanged["Provider generate<br/>packet unchanged"]
2659
+ assemble["Assemble and measure<br/>request packet"] --> policy{"Packet weight ≤<br/>curation budget?"}
2660
+ policy -->|yes| generate["Provider generate"]
2595
2661
  policy -->|no| problem["Record nonterminal 413<br/>Token Budget Overflow"]
2596
2662
  problem --> fold["FOLD newest boundary<br/>and its OPEN effects<br/>tag overflow"]
2597
2663
  fold --> rebuild["Rebuild and remeasure"]
2598
- rebuild --> folded{"Nonnegative<br/>ruler gauge?"}
2599
- folded -->|yes| recovered["Provider generate<br/>with folded packet"]
2600
- folded -->|no| admission{"Exact or bounded request<br/>fits hard envelope?"}
2601
- admission -->|yes| pressured["Provider generate<br/>with negative Budget alarm"]
2602
- admission -->|no / unknown / estimate| stop["413 hard stop<br/>no provider call"]
2664
+ rebuild --> generate
2665
+ generate --> capacity{"Provider capacity<br/>failure?"}
2666
+ capacity -->|no| response["Classify completed response"]
2667
+ capacity -->|yes| prompt{"Withholding automatic<br/>prompt bodies changes request?"}
2668
+ prompt -->|yes| retryPrompt["Persist failure;<br/>rebuild and retry"]
2669
+ retryPrompt --> generate
2670
+ prompt -->|no| boundary{"Newest boundary not yet<br/>rolled back, and rollback<br/>changes request?"}
2671
+ boundary -->|yes| retryBoundary["Persist failure;<br/>roll back, rebuild, retry"]
2672
+ retryBoundary --> generate
2673
+ boundary -->|no| stop["Persist terminal 413"]
2603
2674
  ```
2604
2675
 
2605
2676
  §grinder-overflow-only **The grinder fires only on actual overflow.** In
2606
2677
  `Engine.runTurn`, after `PacketBuilder.buildRequestPacket` assembles the request
2607
2678
  and before `provider.generate`, it compares the packet's render-weight
2608
- ({§tokenomics}) with the virtual ceiling. At or under the ceiling, the packet
2679
+ ({§tokenomics}) with the provider-derived curation budget. At or under the budget, the packet
2609
2680
  ships untouched. The grinder never trims
2610
2681
  speculatively or "helpfully."
2611
2682
 
2612
2683
  - §grinder-overflow-problem **Token Budget Overflow is a nonterminal 413 Problem.** Every over-ceiling assembly records exactly one `engine/context/token-budget-overflow` Problem before automatic recovery, with pre-recovery `usage`, `ceiling`, and `deficit` evidence. Its exact `detail` is `Token Budget Overflow: Token Usage exceeded Token Ceiling. Newest log items were automatically FOLDed to fit within token budget. Curate the log and/or perform more conservatively scoped or chunked retrieval operations to recover.` The Problem remains durable and model-visible even when folding restores room and the turn later concludes successfully; it does not strike or replace the turn's terminal disposition.
2613
2684
  - §grinder-layer1-rollback **One rule, every turn: roll back context introduced by the newest boundary.** On overflow the grinder folds, in one set operation, still-open rows born in the immediately prior turn or current pre-model turn plus exact older rows that a successful OPEN in the immediately prior turn transitioned from folded to open. It reads those landed effects from {§fold-open-meta-operations}; it never re-runs the selector, treats an already-open target as newly introduced, or chooses other older history by relevance. Turn 1 has no prior turn, so only its pre-model rows qualify. The same set operation additively applies the `overflow` tag to every row it folds; rows and bodies remain re-OPENable.
2614
2685
  - §grinder-errors-exempt **Errors, the prompt, AND the plan are exempt.** The grinder never folds an `op='error'` row: errors are the model's durable, curatable record of what went wrong. Nor does it fold the actionless **user prompt** row (`prompt:///<loop>/<N>`): the task frame is not ordinary model-authored memory. Nor a **PLAN row**: the checklist is the model's orientation surface when the grinder fires. All three stay OPEN until the model itself FOLDs or KILLs them.
2615
- - §grinder-hard-413 **The terminal hard stop belongs only to effective-envelope admission.** If the rebuilt packet remains ruler-negative, Core measures the complete `PacketWire` request and reserves the configured generation envelope. Exact or proven-bounded evidence within `provider.contextWindow` admits an ordinary turn regardless of ruler debt. Unknown capacity, an estimate, or evidence exceeding the effective prompt capacity abandons the loop immediately at **413 Content Too Large** without calling `provider.generate`. The terminal request stores one `engine/context/context-envelope-admission-failed` Problem with ruler pressure and admission evidence, distinct from the preceding nonterminal overflow 413; it is failure forensics, not a model recovery turn.
2686
+ - §grinder-hard-413 **Only a provider capacity failure can terminalize at 413.** Every `provider.generate` assesses its exact request under {§provider-surface-capacity}. A proven exact preflight overflow issues no physical request; estimate, unavailable evidence, and a non-fitting upper bound defer to upstream, whose context rejection normalizes to the same `capacity_exceeded` failure. Core retries only after changing the request: first by withholding automatic prompt-body projection when that changes the packet, then by rolling back the newest boundary once when the grinder has not already done so and that changes the packet. Each failed logical call, any issued physical request, capacity evidence, and model-visible provider Problem remain durable; these recovery calls do not consume the completed-emission attempt budget. Core never resends identical bytes as capacity recovery and never chooses arbitrary older history. If neither recovery changes the request, or the changed request is still rejected, the request-only turn and exact provider Problem terminalize the loop at **413 Content Too Large**.
2616
2687
 
2617
- - §tokenomics-fetch-fits-free **A retrieval larger than the available packet room arrives folded with a 413 explanation.** The result lands in the next build; if that build exceeds the ceiling, Core records {§grinder-overflow-problem} and folds the newest boundary, which contains the result. Its row and exact body remain durable and re-OPENable, and its `overflow` tag identifies every row selected by the automatic fold. Remaining ruler debt follows {§tokenomics-negative-pressure}.
2688
+ - §tokenomics-fetch-fits-free **A retrieval larger than the available packet room arrives folded with a 413 explanation.** The result lands in the next build; if that build exceeds the curation budget, Core records {§grinder-overflow-problem} and folds the newest boundary, which contains the result. Its row and exact body remain durable and re-OPENable, and its `overflow` tag identifies every row selected by the automatic fold. Remaining curation debt follows {§tokenomics-negative-pressure}.
2618
2689
 
2619
- - §loop-terminals **Engine-imposed terminals are HTTP-precise** — the loop-status vocabulary, one meaning each: `200` concluded (the model's SEND signal `200`) · `499` model-abandoned (signal `499`, or a cancel) · `429` maxTurns exhausted · `413` effective context-envelope admission failed · `500` strike threshold or invalid-emission exhaustion (distinct Problem types; `508` when the crossing strike was a detected cycle) · `504` loop timeout / exec-timeout restamp · `202` the bounded wait — a loop blocked on a live obligation (the model's `## SEND0 [202] <T,P>`, {§wait-obligation-matrix}); a wait on nothing resolves to `200` unless a successful same-turn FOLD requires the curated next packet · `100`/`102` queued/running. Never a catch-all, never a new value without changing the owning schema.
2690
+ - §loop-terminals **Engine-imposed terminals are HTTP-precise** — the loop-status vocabulary, one meaning each: `200` concluded (the model's SEND signal `200`) · `499` model-abandoned (signal `499`, or a cancel) · `429` maxTurns exhausted · `413` provider input-capacity failure after changed-request recovery · `500` strike threshold or invalid-emission exhaustion (distinct Problem types; `508` when the crossing strike was a detected cycle) · `504` loop timeout / exec-timeout restamp · `202` the bounded wait — a loop blocked on a live obligation (the model's `## SEND0 [202] <T,P>`, {§wait-obligation-matrix}); a wait on nothing resolves to `200` unless a successful same-turn FOLD requires the curated next packet · `100`/`102` queued/running. Never a catch-all, never a new value without changing the owning schema.
2620
2691
 
2621
- §grinder-pressure-surface **What the model sees.** A fold-to-fit packet carries the open {§grinder-overflow-problem} row, its terse `## Errors` pointer, and ordinary folded rows whose complete sorted `tags` include `overflow`. If ruler pressure remains negative but hard admission succeeds, the Budget section also reports the negative free-token value and its one panic line ({§tokenomics-neutral-telemetry}). The 413 diagnoses the overflow without striking, terminalizing the turn, or changing the ordinary operation contract.
2692
+ §grinder-pressure-surface **What the model sees.** A fold-to-fit packet carries the open {§grinder-overflow-problem} row, its terse `## Errors` pointer, and ordinary folded rows whose complete sorted `tags` include `overflow`. If curation pressure remains negative but the provider accepts the request, the Budget section also reports the negative free-weight value and its one panic line ({§tokenomics-neutral-telemetry}). The curation 413 diagnoses the overflow without striking, terminalizing the turn, or changing the ordinary operation contract.
2622
2693
 
2623
- The model controls its context; the engine enforces the hard envelope without
2624
- choosing what older history matters. The same boundary applies on turn 1 and
2625
- turn 101. The grinder folds reversibly, never deletes, and never performs
2626
- speculative or non-overflow trimming.
2694
+ The model controls its context; the provider owns physical admission, and Core
2695
+ performs only bounded changed-request recovery without choosing what older
2696
+ history matters. The same boundary applies on turn 1 and turn 101. The grinder
2697
+ folds reversibly, never deletes, and never performs speculative or non-overflow
2698
+ trimming.
2627
2699
 
2628
2700
  ### §env-delta The environment delta: what changed since the model last looked
2629
2701
 
@@ -2833,14 +2905,14 @@ leaves the request-only record, while rejected exchanges remain in
2833
2905
  | Turn state | `turns.packet` |
2834
2906
  | ----------------------------- | ----------------------------------------------- |
2835
2907
  | No model request assembled | SQL `NULL` |
2836
- | Request assembled | `{ tokens, sections }` |
2837
- | Response admitted | `{ tokens, sections, assistant, assistantRaw }` |
2908
+ | Request assembled | `{ weight, sections }` |
2909
+ | Response admitted | `{ weight, sections, assistant, assistantRaw }` |
2838
2910
 
2839
2911
  | Field | Presence | Contract |
2840
2912
  | ----------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2841
- | `tokens` | Every assembled model request | Ruler weight of both rendered request slots. Admission does not change its meaning; it is never response weight or provider usage. |
2913
+ | `weight` | Every assembled model request | Curation weight of both rendered request slots. Admission does not change its meaning; it is never response weight or provider usage. |
2842
2914
  | `sections` | Every assembled model request | Ordered post-transform request sections. `PacketWire.renderSlot` groups them into system and user messages, and the digest re-renders those stored sections byte-for-byte. |
2843
- | `sections[].tokens` | Every stored section | Independently measured render-weight of that section. Their sum is not the rendered request weight because slot separators and independent rounding remain outside each row. |
2915
+ | `sections[].weight` | Every stored section | Independently measured curation weight of that section. Their sum is not the rendered request weight because slot separators and independent rounding remain outside each row. |
2844
2916
  | `assistant.content` | Admitted response only | Accepted model content from which operations were parsed. |
2845
2917
  | `assistant.ops` | Admitted response only | Parsed operations admitted from that content. |
2846
2918
  | `assistant.reasoning` | Admitted response only | Normalized readable reasoning text, or `null`. |
@@ -2855,7 +2927,7 @@ or request-only note instead of fabricating response files.
2855
2927
 
2856
2928
  The external tokenless draft and transformation boundary is owned by
2857
2929
  {§scheme-packet-transform}. Core alone extends each validated draft with its
2858
- measured `tokens` field for storage. #74 tracks coverage that mistakes the sum
2930
+ measured `weight` field for storage. #74 tracks coverage that mistakes the sum
2859
2931
  of section weights for the rendered request weight.
2860
2932
 
2861
2933
  §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 plugin transforms, measurement, storage, and wire rendering; alignment colons survive, while fenced blocks and all non-table whitespace remain exact.
@@ -2868,17 +2940,13 @@ of section weights for the rendered request weight.
2868
2940
  | Core lifecycle | Use the exact Workspace → Worker → Loop → Turn → Op hierarchy in {§lifecycle-terms}. An AG-UI Run or thread is always protocol-qualified. |
2869
2941
  | Model-facing packet | Use the model's training distribution: operations mirror HTTP and shell, `display` mirrors CSS, and jsonplurnk remains JSON. Renaming this vocabulary to internal API terminology would discard useful resonance for a standard the model never sees. |
2870
2942
 
2871
- `promptBudget` is deliberately distinct from `contextWindow`: it is PLURNK's
2872
- enforced packet allowance, bounded by provider capacity and optionally tightened
2873
- by service policy.
2874
-
2875
2943
  | PLURNK-native term | Why it remains |
2876
2944
  |--------------------------------|----------------|
2877
2945
  | `worker` / `loop` / `turn` | The process hierarchy in {§lifecycle-terms}; unqualified `run` names no internal entity. |
2878
2946
  | `packet` | The assembled address space, a kernel concept rather than merely a provider request. |
2879
2947
  | `costUsd` | No standard cost field exists; the explicit currency avoids implied units, and the value remains an exact decimal string. |
2880
- | `PLURNK_SERVICE_SAFETY` | The ruler's packing margin is a service fact no provider owns. |
2881
- | the `chars/2` ruler | Model-agnostic by design ({§tokenomics-agnostic-ruler}). |
2948
+ | `curationWeight` / `curationBudget` | Explicitly distinguish Core's model-independent context-shaping facts from physical provider tokens. |
2949
+ | the `chars/2` curation ruler | Model-agnostic by design ({§tokenomics-agnostic-ruler}); it is never presented as a tokenizer. |
2882
2950
 
2883
2951
  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 -->
2884
2952
 
@@ -2908,7 +2976,7 @@ READ and FIND own their range or pagination before packet rendering; the packet
2908
2976
 
2909
2977
  §prompt-entry **Prompt as a first-class entry and log row.** Each prompt is stored once at `prompt:///<loop>/<N>` as an owner-keyed text/markdown entry, then published to its turn as one actionless lowercase `prompt` log row. No synthetic EDIT or READ operation is invented. The row is born OPEN and obeys {§body-projection}. The **Active User Prompts** section closes the user-slot status clump as a paths-only list (`* prompt:///<loop>/<N>`), so every frame remains directly READable even after its log row is folded or killed.
2910
2978
 
2911
- §prompt-projection **Prompt storage is unbounded by model context; automatic materialization is not.** Core persists every accepted prompt completely before packet assembly. The selected provider's enforced `promptBudget` and the alias-resolved percentage from `PLURNK_SERVICE_PROMPT_PROJECTION` derive one aggregate ruler-weight allowance for OPEN prompt bodies. Complete prompt bodies render when their aggregate weight fits. Otherwise all OPEN prompt rows share the allowance: full bodies consume only their required share, unused shares are redistributed, and partial bodies render the largest leading complete-line region that fits their share or an exact character-bound prefix when the first physical line alone is larger. The sum of their rendered body weights never exceeds the allowance. Every partial body carries its exact `chunk` after `body`; the canonical `prompt:///` entry and `log:///` body remain complete and READ/FIND-addressable. Without an enforced packet ceiling the percentage is underivable, so prompt rows retain the ordinary bounded projection rather than inventing capacity. This policy never rejects, summarizes, or discards a prompt because it exceeds a context window.
2979
+ §prompt-projection **Prompt storage is unbounded by model context; automatic materialization is not.** Core persists every accepted prompt completely before packet assembly. The selected provider's derived `inputCapacity` and the alias-resolved percentage from `PLURNK_SERVICE_PROMPT_PROJECTION` derive one aggregate curation-weight allowance for OPEN prompt bodies. Complete prompt bodies render when their aggregate weight fits. Otherwise all OPEN prompt rows share the allowance: full bodies consume only their required share, unused shares are redistributed, and partial bodies render the largest leading complete-line region that fits their share or an exact character-bound prefix when the first physical line alone is larger. The sum of their rendered body weights never exceeds the allowance. Every partial body carries its exact `chunk` after `body`; the canonical `prompt:///` entry and `log:///` body remain complete and READ/FIND-addressable. When provider input capacity is unknown the percentage is underivable, so prompt rows retain the ordinary bounded projection rather than inventing capacity. This policy never rejects, summarizes, or discards a prompt because it exceeds a context window.
2912
2980
 
2913
2981
  §prompt-self-only The frame is self-only and owner-keyed:
2914
2982
  `entries.owner_id` carries worker identity while the address carries only the
@@ -2971,7 +3039,7 @@ retain distinct contracts and lifetimes.
2971
3039
  | failure | row | status |
2972
3040
  |---|---|---|
2973
3041
  | action failure | the failed op's own row; the owning scheme supplies Problem Details | 4xx/5xx |
2974
- | context-envelope admission | `op='error'`, origin `plurnk`, source `engine`; `engine/context/context-envelope-admission-failed` Problem Details | 413 |
3042
+ | provider input capacity | `op='error'`, origin `plurnk`, source `provider`; exact provider-owned `capacity-exceeded` Problem Details | 413 |
2975
3043
  | max commands exceeded | `op='error'`, origin `plurnk`, source `rail`; `engine/rail/max-commands-exceeded` Problem Details | 429 |
2976
3044
  | idle turn | `op='error'`, origin `plurnk`, source `rail`; `engine/rail/idle-turn` Problem Details | 409 |
2977
3045
 
@@ -3018,7 +3086,7 @@ turn.** It cannot execute operations or alter the audited history.
3018
3086
 
3019
3087
  ### §tools user.tools — the capability sheet
3020
3088
 
3021
- §tools-capability-sheet The tools capability sheet renders under `## Registered Tools`, after the policy sections. One generated Markdown table is the closed set of valid executor selectors and states each runtime declaration's model-facing `(target)` role, body role, and exact canonical example. The compact legend leaves required inputs unmarked, marks optional inputs with `?`, pairs mutually exclusive alternatives with `↔`, marks refused buckets with `—`, and locates optional `<timeout,poll>` on the heading. The preface prefers purpose-built Plurnk operations over EXEC scripts. For a declaration with `target.directory: "cwd"`, the target cell distinguishes a local-directory working context from the plugin-authored non-directory target role. Optional non-EXEC operations render separately under `## Enabled Optional Operations` in a `plurnk` fence, so the catalogue remains truthful. `PacketBuilder.#collectTools` assembles both; a prose notice (e.g. the EXEC-disabled line) stays beside the table, and empty sections are omitted.
3089
+ §tools-capability-sheet The tools capability sheet renders under `## Registered Tools`, after the policy sections. One generated Markdown table is the closed set of valid executor selectors. Its columns state `[executor]`, the `(target)` role and any concrete target witness, the body role, and `Invocation`: a body example, a schema-derived signature, or `bodyless`. The Invocation cell never repeats the `## EXEC0 [executor] (target)` syntax already defined by the headers and columns. A runtime exposing {§executor-tool-registry} contributes exactly one row per enabled exact target instead of its general row; an empty exact registry contributes no row and has no fallback. The compact legend leaves required inputs unmarked, marks optional inputs with `?`, pairs mutually exclusive alternatives with `↔`, marks refused buckets with `—`, and locates optional `<timeout,poll>` on the heading. The preface prefers purpose-built Plurnk operations over EXEC scripts. For a declaration with `target.directory: "cwd"`, the target cell distinguishes a local-directory working context from the plugin-authored non-directory target role. Optional non-EXEC operations render separately under `## Enabled Optional Operations` in a `plurnk` fence, so the catalogue remains truthful. `PacketBuilder.#collectTools` assembles both; a prose notice (e.g. the EXEC-disabled line) stays beside the table, and empty sections are omitted.
3022
3090
 
3023
3091
  §tools-loop-affinity **The capability sheet describes the current loop.** The
3024
3092
  sheet filters registered capabilities through the same
@@ -3027,7 +3095,15 @@ registered executors exist but EXEC is inactive, their table is replaced by
3027
3095
  an explicit disabled notice rather than silent absence. The dispatch 403 remains
3028
3096
  the backstop and names the non-retryable loop restriction.
3029
3097
 
3030
- **Contributors: the wired executor tags.** Every available executor tag contributes exactly one row derived from its required {§executor-invocation} declaration. Its doc is materialized at `worker://plurnk/docs/<tag>.md` and discovered via the turn-0 `## FIND0 [+init,+docs] (worker://plurnk/docs/**)` foist, not linked inline. `PLURNK_SERVICE_DOCS_EXCLUDE` drops a named tag's row and doc. The boot `ExecutorRegistry` probes availability per tag, so the table advertises runnable selectors instead of presuming a particular runtime exists.
3098
+ **Contributors: the workspace executor snapshot.** Every available executor tag
3099
+ contributes its general {§executor-invocation} row or the closed rows and
3100
+ documentation from its {§executor-tool-registry} snapshot. Its doc is
3101
+ materialized at `worker://plurnk/docs/<tag>.md` and discovered via the turn-0
3102
+ `## FIND0 [+init,+docs] (worker://plurnk/docs/**)` foist, not linked inline.
3103
+ `PLURNK_SERVICE_DOCS_EXCLUDE` drops a named tag's rows and doc. Installed
3104
+ executors are probed once; workspace providers publish only prepared runnable
3105
+ selectors, so the table never presumes that another workspace's capability is
3106
+ available here.
3031
3107
 
3032
3108
  ### §schemes user.schemes — the resource directory
3033
3109
 
@@ -3048,7 +3124,7 @@ surfaces with its cause and leaves no apparently initialized home.
3048
3124
  After that bootstrap the file is user-owned: edits and deletion persist, and a
3049
3125
  later boot never refreshes or recreates it.
3050
3126
 
3051
- §schemes-self-doc-materialization **The scheme self-doc contract.** `@plurnk/plurnk-schemes` owns `example` and `documentation` in `SchemeManifest` ({§manifest-self-doc}); the former is the hot-path operation example set and the latter is the deep pull doc. `SchemeRegistry.teach()` renders the directory, `SchemeRegistry.docs()` resolves corpus-or-manifest documentation, and `docEntries()` materializes the result when core publishes capabilities for a workspace.
3127
+ §schemes-self-doc-materialization **The scheme self-doc contract.** `@plurnk/plurnk-schemes` owns `example` and `documentation` in `SchemeManifest` ({§manifest-self-doc}); the former is the hot-path operation example set and the latter is the deep pull doc. `SchemeRegistry.teach(workspaceId)` renders the effective directory, `SchemeRegistry.docs(workspaceId)` resolves corpus-or-manifest documentation, and `docEntries(workspaceId)` supplies the current pull-document set when core publishes capabilities for a workspace. Materialization reconciles `worker://plurnk/docs/` exactly under the workspace capability gate: vanished contributions are deleted before current documents are upserted, so an excluded, disabled, detached, or replaced capability cannot leave a stale model-facing contract.
3052
3128
 
3053
3129
  ### §packet-git-status The Git status section — compact repository state
3054
3130
 
@@ -3117,7 +3193,6 @@ container identity.
3117
3193
  §matcher-dispatch-203-soft-fallback On parse failure, 203 returns raw content as the text primitive with `reason`
3118
3194
  so the model can use ordinary text retrieval or repair the source.
3119
3195
 
3120
-
3121
3196
  `Matcher.matchCandidates` searches heterogeneous resource sets. A candidate
3122
3197
  whose handler returns 415 is omitted when another candidate supports the
3123
3198
  dialect; if every candidate is unsupported, the first exact 415 Problem is the
@@ -3192,7 +3267,6 @@ scope shape. A follow-up `## READ0 (resource) <SL,SC,EL,EC>` retrieves that exac
3192
3267
  region. JSONPath/XPath remain locators and matchers; they do not introduce a
3193
3268
  second structural scope or structural EDIT language.
3194
3269
 
3195
-
3196
3270
  ### §ext-mimetype Path-extension declares mimetype
3197
3271
 
3198
3272
  `resolveEntryMimetype` (exported from `@plurnk/plurnk-schemes`): pathname extension → `Mimetypes.detect({ ext })` (with `text/plain` normalized to `text/markdown` per the text-primitive rule {§markdown-primitive}); falls back to scheme manifest channel default when no extension.
@@ -3206,7 +3280,6 @@ second structural scope or structural EDIT language.
3206
3280
  mimetype is stored in `entry_channels.mimetype` on write and drives matcher,
3207
3281
  projection, and binary handling. Text scope meaning does not vary by mimetype.
3208
3282
 
3209
-
3210
3283
  ### §render-rule Render rule
3211
3284
 
3212
3285
  §render-rule-line-navigable-prefix Every textual content body with a source