@plurnk/plurnk-service 1.23.0 → 1.25.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 (215) hide show
  1. package/.env.defaults +17 -15
  2. package/INSTALL.md +96 -20
  3. package/README.md +2 -2
  4. package/SPEC.md +519 -206
  5. package/dist/Paths.d.ts +1 -0
  6. package/dist/Paths.d.ts.map +1 -1
  7. package/dist/Paths.js +1 -0
  8. package/dist/Paths.js.map +1 -1
  9. package/dist/build-info.json +1 -1
  10. package/dist/core/AdministrativeLoop.d.ts +1 -1
  11. package/dist/core/AdministrativeLoop.d.ts.map +1 -1
  12. package/dist/core/AdministrativeLoop.js +5 -5
  13. package/dist/core/AdministrativeLoop.js.map +1 -1
  14. package/dist/core/AdmittedTurnExecutor.d.ts +3 -2
  15. package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
  16. package/dist/core/AdmittedTurnExecutor.js +14 -1
  17. package/dist/core/AdmittedTurnExecutor.js.map +1 -1
  18. package/dist/core/CapabilityResolver.js +1 -1
  19. package/dist/core/CapabilityResolver.js.map +1 -1
  20. package/dist/core/DataStatementRunner.d.ts.map +1 -1
  21. package/dist/core/DataStatementRunner.js +1 -3
  22. package/dist/core/DataStatementRunner.js.map +1 -1
  23. package/dist/core/Dispatcher.d.ts +10 -0
  24. package/dist/core/Dispatcher.d.ts.map +1 -1
  25. package/dist/core/Dispatcher.js +71 -18
  26. package/dist/core/Dispatcher.js.map +1 -1
  27. package/dist/core/Dispatcher.sql +0 -7
  28. package/dist/core/Engine.d.ts +2 -2
  29. package/dist/core/Engine.d.ts.map +1 -1
  30. package/dist/core/ExecutorRegistry.d.ts +16 -4
  31. package/dist/core/ExecutorRegistry.d.ts.map +1 -1
  32. package/dist/core/ExecutorRegistry.js +31 -31
  33. package/dist/core/ExecutorRegistry.js.map +1 -1
  34. package/dist/core/FabricatedLog.d.ts +2 -0
  35. package/dist/core/FabricatedLog.d.ts.map +1 -1
  36. package/dist/core/FabricatedLog.js +18 -7
  37. package/dist/core/FabricatedLog.js.map +1 -1
  38. package/dist/core/HostPaths.d.ts +7 -1
  39. package/dist/core/HostPaths.d.ts.map +1 -1
  40. package/dist/core/HostPaths.js +25 -12
  41. package/dist/core/HostPaths.js.map +1 -1
  42. package/dist/core/LogEntryProjection.d.ts +1 -0
  43. package/dist/core/LogEntryProjection.d.ts.map +1 -1
  44. package/dist/core/LogEntryProjection.js +10 -0
  45. package/dist/core/LogEntryProjection.js.map +1 -1
  46. package/dist/core/LoopDriver.d.ts.map +1 -1
  47. package/dist/core/LoopDriver.js +8 -1
  48. package/dist/core/LoopDriver.js.map +1 -1
  49. package/dist/core/LoopLifecycle.d.ts +1 -1
  50. package/dist/core/LoopLifecycle.js +1 -1
  51. package/dist/core/LoopLifecycle.sql +2 -2
  52. package/dist/core/LoopPolicies.d.ts.map +1 -1
  53. package/dist/core/LoopPolicies.js +12 -7
  54. package/dist/core/LoopPolicies.js.map +1 -1
  55. package/dist/core/OperatorConfig.d.ts.map +1 -1
  56. package/dist/core/OperatorConfig.js +97 -51
  57. package/dist/core/OperatorConfig.js.map +1 -1
  58. package/dist/core/PacketBuilder.d.ts +2 -0
  59. package/dist/core/PacketBuilder.d.ts.map +1 -1
  60. package/dist/core/PacketBuilder.js +44 -32
  61. package/dist/core/PacketBuilder.js.map +1 -1
  62. package/dist/core/PacketBuilder.sql +3 -1
  63. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  64. package/dist/core/SchemeRegistry.js +3 -2
  65. package/dist/core/SchemeRegistry.js.map +1 -1
  66. package/dist/core/TurnDispositionHandler.d.ts +1 -1
  67. package/dist/core/TurnDispositionHandler.d.ts.map +1 -1
  68. package/dist/core/TurnDispositionHandler.js +6 -15
  69. package/dist/core/TurnDispositionHandler.js.map +1 -1
  70. package/dist/core/TurnMaterialization.js +1 -1
  71. package/dist/core/TurnMaterialization.js.map +1 -1
  72. package/dist/core/TurnOps.d.ts +1 -0
  73. package/dist/core/TurnOps.d.ts.map +1 -1
  74. package/dist/core/TurnOps.js +18 -0
  75. package/dist/core/TurnOps.js.map +1 -1
  76. package/dist/core/TurnRunner.d.ts +6 -0
  77. package/dist/core/TurnRunner.d.ts.map +1 -1
  78. package/dist/core/TurnRunner.js +18 -8
  79. package/dist/core/TurnRunner.js.map +1 -1
  80. package/dist/core/TurnSources.sql +0 -15
  81. package/dist/core/env-defaults.d.ts +12 -1
  82. package/dist/core/env-defaults.d.ts.map +1 -1
  83. package/dist/core/env-defaults.js +46 -7
  84. package/dist/core/env-defaults.js.map +1 -1
  85. package/dist/core/file-creation-policy.d.ts.map +1 -1
  86. package/dist/core/file-creation-policy.js +2 -1
  87. package/dist/core/file-creation-policy.js.map +1 -1
  88. package/dist/core/packet-wire.d.ts +10 -2
  89. package/dist/core/packet-wire.d.ts.map +1 -1
  90. package/dist/core/packet-wire.js +87 -69
  91. package/dist/core/packet-wire.js.map +1 -1
  92. package/dist/core/results.d.ts +2 -0
  93. package/dist/core/results.d.ts.map +1 -1
  94. package/dist/core/results.js +7 -0
  95. package/dist/core/results.js.map +1 -1
  96. package/dist/digest/Digest.d.ts.map +1 -1
  97. package/dist/digest/Digest.js +10 -4
  98. package/dist/digest/Digest.js.map +1 -1
  99. package/dist/digest/DigestRender.d.ts.map +1 -1
  100. package/dist/digest/DigestRender.js +51 -22
  101. package/dist/digest/DigestRender.js.map +1 -1
  102. package/dist/digest/DigestRequiem.d.ts.map +1 -1
  103. package/dist/digest/DigestRequiem.js +10 -4
  104. package/dist/digest/DigestRequiem.js.map +1 -1
  105. package/dist/digest/digest-rows.d.ts +9 -1
  106. package/dist/digest/digest-rows.d.ts.map +1 -1
  107. package/dist/digest/digest-rows.js.map +1 -1
  108. package/dist/digest/digest.sql +16 -0
  109. package/dist/observe/init.d.ts +1 -0
  110. package/dist/observe/init.d.ts.map +1 -1
  111. package/dist/observe/init.js +5 -1
  112. package/dist/observe/init.js.map +1 -1
  113. package/dist/schemes/EffectPolicy.d.ts.map +1 -1
  114. package/dist/schemes/EffectPolicy.js +4 -4
  115. package/dist/schemes/EffectPolicy.js.map +1 -1
  116. package/dist/schemes/Exec.d.ts +1 -0
  117. package/dist/schemes/Exec.d.ts.map +1 -1
  118. package/dist/schemes/Exec.js +30 -14
  119. package/dist/schemes/Exec.js.map +1 -1
  120. package/dist/schemes/ExecScheduler.d.ts.map +1 -1
  121. package/dist/schemes/ExecScheduler.js +4 -3
  122. package/dist/schemes/ExecScheduler.js.map +1 -1
  123. package/dist/schemes/ExecScratch.d.ts.map +1 -1
  124. package/dist/schemes/ExecScratch.js +2 -1
  125. package/dist/schemes/ExecScratch.js.map +1 -1
  126. package/dist/schemes/File.d.ts.map +1 -1
  127. package/dist/schemes/File.js +15 -10
  128. package/dist/schemes/File.js.map +1 -1
  129. package/dist/schemes/Log.d.ts.map +1 -1
  130. package/dist/schemes/Log.js +25 -3
  131. package/dist/schemes/Log.js.map +1 -1
  132. package/dist/server/AgentRoots.d.ts +6 -0
  133. package/dist/server/AgentRoots.d.ts.map +1 -0
  134. package/dist/server/AgentRoots.js +25 -0
  135. package/dist/server/AgentRoots.js.map +1 -0
  136. package/dist/server/ConfigurationDiagnostics.d.ts +12 -0
  137. package/dist/server/ConfigurationDiagnostics.d.ts.map +1 -0
  138. package/dist/server/ConfigurationDiagnostics.js +40 -0
  139. package/dist/server/ConfigurationDiagnostics.js.map +1 -0
  140. package/dist/server/Daemon.d.ts +11 -5
  141. package/dist/server/Daemon.d.ts.map +1 -1
  142. package/dist/server/Daemon.js +124 -48
  143. package/dist/server/Daemon.js.map +1 -1
  144. package/dist/server/DaemonModule.d.ts +11 -4
  145. package/dist/server/DaemonModule.d.ts.map +1 -1
  146. package/dist/server/EnvFunctionality.d.ts.map +1 -1
  147. package/dist/server/EnvFunctionality.js +5 -2
  148. package/dist/server/EnvFunctionality.js.map +1 -1
  149. package/dist/server/Functionality.d.ts +6 -0
  150. package/dist/server/Functionality.d.ts.map +1 -1
  151. package/dist/server/Functionality.js +173 -37
  152. package/dist/server/Functionality.js.map +1 -1
  153. package/dist/server/FunctionalityManager.js +6 -6
  154. package/dist/server/FunctionalityManager.js.map +1 -1
  155. package/dist/server/MembersFunctionality.d.ts.map +1 -1
  156. package/dist/server/MembersFunctionality.js +13 -41
  157. package/dist/server/MembersFunctionality.js.map +1 -1
  158. package/dist/server/PluginSources.d.ts +20 -0
  159. package/dist/server/PluginSources.d.ts.map +1 -0
  160. package/dist/server/PluginSources.js +52 -0
  161. package/dist/server/PluginSources.js.map +1 -0
  162. package/dist/server/PlurnkSkill.js +1 -1
  163. package/dist/server/PlurnkSkill.js.map +1 -1
  164. package/dist/server/Retention.d.ts.map +1 -1
  165. package/dist/server/Retention.js +11 -30
  166. package/dist/server/Retention.js.map +1 -1
  167. package/dist/server/ServiceModules.d.ts +1 -0
  168. package/dist/server/ServiceModules.d.ts.map +1 -1
  169. package/dist/server/ServiceModules.js +9 -3
  170. package/dist/server/ServiceModules.js.map +1 -1
  171. package/dist/server/SkillSource.d.ts +37 -0
  172. package/dist/server/SkillSource.d.ts.map +1 -0
  173. package/dist/server/SkillSource.js +267 -0
  174. package/dist/server/SkillSource.js.map +1 -0
  175. package/dist/server/SkillsFunctionality.d.ts +8 -27
  176. package/dist/server/SkillsFunctionality.d.ts.map +1 -1
  177. package/dist/server/SkillsFunctionality.js +218 -270
  178. package/dist/server/SkillsFunctionality.js.map +1 -1
  179. package/dist/server/WorkerModelResolver.d.ts +1 -2
  180. package/dist/server/WorkerModelResolver.d.ts.map +1 -1
  181. package/dist/server/WorkerModelResolver.js +18 -19
  182. package/dist/server/WorkerModelResolver.js.map +1 -1
  183. package/dist/server/WorkspacePlugins.d.ts +25 -0
  184. package/dist/server/WorkspacePlugins.d.ts.map +1 -0
  185. package/dist/server/WorkspacePlugins.js +43 -0
  186. package/dist/server/WorkspacePlugins.js.map +1 -0
  187. package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
  188. package/dist/server/dispatch-as-plurnk.js +3 -1
  189. package/dist/server/dispatch-as-plurnk.js.map +1 -1
  190. package/dist/server/envelope.js +1 -1
  191. package/dist/server/envelope.js.map +1 -1
  192. package/dist/server/loop-model.d.ts.map +1 -1
  193. package/dist/server/loop-model.js +1 -2
  194. package/dist/server/loop-model.js.map +1 -1
  195. package/dist/server/module-discovery.d.ts +6 -0
  196. package/dist/server/module-discovery.d.ts.map +1 -1
  197. package/dist/server/module-discovery.js +46 -24
  198. package/dist/server/module-discovery.js.map +1 -1
  199. package/dist/server/skills-problems.d.ts +8 -0
  200. package/dist/server/skills-problems.d.ts.map +1 -0
  201. package/dist/server/skills-problems.js +18 -0
  202. package/dist/server/skills-problems.js.map +1 -0
  203. package/dist/service.d.ts.map +1 -1
  204. package/dist/service.js +55 -45
  205. package/dist/service.js.map +1 -1
  206. package/docs/env.md +7 -8
  207. package/docs/members.md +4 -3
  208. package/docs/skills.md +45 -22
  209. package/migrations/012_emission.sql +77 -0
  210. package/package.json +38 -36
  211. package/plurnk.service +29 -0
  212. package/dist/core/PreviousEmission.d.ts +0 -14
  213. package/dist/core/PreviousEmission.d.ts.map +0 -1
  214. package/dist/core/PreviousEmission.js +0 -24
  215. package/dist/core/PreviousEmission.js.map +0 -1
package/SPEC.md CHANGED
@@ -133,7 +133,7 @@ package map. The default installed composition is specified in {§bundled-set}.
133
133
 
134
134
  OpenTelemetry may observe PLURNK; it never becomes product state, failure transport, scheduler input, model teaching, or client protocol. Domain and client activity remain on AG-UI. Reusable packages depend on the OTel API only; the daemon constructs only the explicitly configured trace and metric providers. An unconfigured or standards-valid disabled process loads no SDK or exporter implementation and keeps the API's no-op behavior with bounded overhead. OTel Logs have no provider or initialization path.
135
135
 
136
- Configuration uses the standard `OTEL_*` environment: `OTEL_TRACES_EXPORTER` / `OTEL_METRICS_EXPORTER` select `otlp` or `console` per signal (a missing or `none` value keeps that signal off; no SDK default selects an exporter), `OTEL_SERVICE_NAME` names the service (default `plurnk-service`), case-insensitive `true` in `OTEL_SDK_DISABLED` turns the boundary off, and OTLP exporters honor `OTEL_EXPORTER_OTLP_*`. An unknown exporter name fails daemon boot; a typo never silently disables observation. OTel Logs and direct draft semantic-convention use are excluded. HTTP spans carry only an AG-UI-owned bounded route class, never an input pathname or query. Spans otherwise carry high-cardinality identifiers; metric labels stay low-cardinality. Prompts, reasoning, file bodies, arbitrary URLs, secrets, and plugin payloads are never recorded as attributes or metric values by default. Exporter failure cannot change product results or client lifecycle. Daemon, telemetry, and database teardown are independent reverse-ownership phases; every phase runs and aggregate failure preserves every cause.
136
+ Configuration uses the standard `OTEL_*` environment: `OTEL_TRACES_EXPORTER` / `OTEL_METRICS_EXPORTER` select `otlp` or `console` per signal (a missing or `none` value keeps that signal off; no SDK default selects an exporter), `OTEL_SERVICE_NAME` names the service (default `plurnk-service`), case-insensitive `true` in `OTEL_SDK_DISABLED` turns the boundary off, and OTLP exporters honor `OTEL_EXPORTER_OTLP_*`. An unknown exporter name makes observability unavailable with a configuration diagnostic ({§configuration-repair-path}); offline checking rejects the same input before loading an SDK. OTel Logs and direct draft semantic-convention use are excluded. HTTP spans carry only an AG-UI-owned bounded route class, never an input pathname or query. Spans otherwise carry high-cardinality identifiers; metric labels stay low-cardinality. Prompts, reasoning, file bodies, arbitrary URLs, secrets, and plugin payloads are never recorded as attributes or metric values by default. Exporter failure cannot change product results or client lifecycle. Daemon, telemetry, and database teardown are independent reverse-ownership phases; every phase runs and aggregate failure preserves every cause.
137
137
 
138
138
  §observability-genai-conventions **GenAI convention projection.** Provider
139
139
  request spans follow the [GenAI registry at `c88d504`](https://github.com/open-telemetry/semantic-conventions-genai/tree/c88d504ab3d9879f8e50d3cc87e69775e11db234),
@@ -188,12 +188,13 @@ an explicitly specified subpath, not in the frozen root barrel.
188
188
  ```mermaid
189
189
  flowchart LR
190
190
  LISTENER["Bind client listener<br/>unready: HTTP 503"] --> DB["Acquire daemon lock<br/>and admit SQLite schema"]
191
- DB --> PROVIDER["Resolve and verify<br/>selected provider"]
192
- PROVIDER --> DAEMON["Construct and start<br/>daemon composition"]
191
+ DB --> DAEMON["Construct and start<br/>daemon composition"]
193
192
  DAEMON --> CLIENT["Activate client transport"]
193
+ CLIENT --> SELECT["Worker selects or first uses a model"]
194
+ SELECT --> PROVIDER["Construct and verify<br/>selected provider"]
194
195
  LISTENER -. failure .-> FAIL["Fail startup<br/>durable state untouched"]
195
196
  DB -. failure .-> CLOSE_LISTENER["Close listener"] --> FAIL
196
- PROVIDER -. failure .-> CLOSE["Close database<br/>and release lock"] --> FAIL
197
+ PROVIDER -. failure .-> REPAIR["Return selection failure<br/>client remains available"]
197
198
  DAEMON -. failure .-> TEARDOWN["Close every started owner"] --> FAIL
198
199
  ```
199
200
 
@@ -220,14 +221,17 @@ address by URL, never by port (#641): AG-UI mounts `/` and `/agui`, A2A the
220
221
  well-known card and its endpoint path, on the same address.
221
222
 
222
223
  §startup-admission-order After listener ownership, database admission completes
223
- before provider or capability initialization can perform external work. Every
224
+ before capability initialization can perform external work. Provider construction
225
+ and endpoint verification occur on worker selection or first use, never merely
226
+ because a default selector is configured. Startup validates selectors without
227
+ provider I/O and retains their configuration diagnostics. Every
224
228
  later startup failure closes resources in reverse ownership order while
225
229
  preserving the originating failure: daemon, observability, database, listener.
226
230
 
227
231
  §startup-readiness-line **Readiness is one stdout line.** After the client interface is mounted
228
232
  the service prints exactly one line, `plurnk-service agui=<url> db=<json string> route=<json string>`:
229
233
  the URL brackets an IPv6 host, and the database path and the route (the active model route or
230
- `no model`) are JSON strings, so a path or route containing spaces is exact and a consumer parses
234
+ `no model`, or `invalid model configuration`) are JSON strings, so a path or route containing spaces is exact and a consumer parses
231
235
  the URL as a URL and the strings as JSON; nothing else the service prints on stdout before it has
232
236
  that prefix. Before the line the listener answers `503 service-starting`; after it,
233
237
  `discover` is the identity check a launcher uses to tell this daemon from any other listener. A bind
@@ -378,7 +382,7 @@ direct-entry-plus-directory count; `-1` enables the ordinary markerless page;
378
382
  unset / `0` disables previews. `log://` is absent because the current worker's
379
383
  log already renders in present mode.
380
384
 
381
- §worker-initialization-entry **Model-worker initialization is a real `_plurnk` turn.** A model worker's first loop begins with one packetless `{ producer="_plurnk", kind="initialization" }` turn submitted through {§turn-ops-admission-path}. Its reasoning and program are stored before execution. NOTEs from its reasoning and program, the orienting READ/FIND surveys, and the reasoning READ in {§reasoning-initial-read} execute under {§op-execution-order}. Its program supplies the worked example as the first request's assistant message ({§packet-wire-envelope}); no program READ, actionless source row or simulated READ is added. Every orienting row is structurally classified `_plurnk` and `init`. The namespace surveys and their asides follow {§actor-boundary-catalog-preview}.
385
+ §worker-initialization-entry **Model-worker initialization is a real `_plurnk` turn.** A model worker's first loop begins with one packetless `{ producer="_plurnk", kind="initialization" }` turn submitted through {§turn-ops-admission-path}. Its reasoning and program are stored before execution. NOTEs from its reasoning and program, the orienting READ/FIND surveys, and the reasoning READ in {§reasoning-initial-read} execute under {§op-execution-order}. Its program is announced at `log:///1/1/1/emission` ({§emission-row}) and supplies the worked example as the first request's assistant message ({§packet-wire-envelope}); no program READ, actionless source row or simulated READ is added. Every orienting row is structurally classified `_plurnk` and `init`. The namespace surveys and their asides follow {§actor-boundary-catalog-preview}.
382
386
 
383
387
  Incoming messages publish once as inbound SEND rows in the first model turn
384
388
  ({§message-arrival}); initialization neither READs nor archives them. The turn
@@ -600,15 +604,13 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
600
604
  `"parent": null` at a root, so a worker never infers its rank from silence.
601
605
  - §packet-current-turn **The packet says who and which turn, below the log.** The
602
606
  `## Worker` block is the first section after the log, carrying
603
- `{"path": "worker://<name>", "parent": <address or null>, "loop": L, "turn": T, "previousEmission": <ops address or null>}`:
604
- the actor, whose child it is, the coordinate this packet's response becomes, so `reasoning://<worker>/L/T`
605
- and `ops://<worker>/L/T` are the model's own and `log:///L/T/*` its rows, and the address of the program
606
- the envelope's assistant message carries ({§packet-wire-envelope}), so the rows sharing that coordinate
607
- are its receipts; a model never infers the present from the last row's coordinate, which may or may
608
- not be its own turn. The block
607
+ `{"path": "worker://<name>", "parent": <address or null>, "loop": L, "turn": T}`: the actor,
608
+ whose child it is, and the coordinate this packet's response becomes, so `reasoning://<worker>/L/T`
609
+ and `ops://<worker>/L/T` are the model's own and `log:///L/T/*` its rows; a model never infers the
610
+ present from the last row's coordinate, which may or may not be its own turn. The block
609
611
  changes every turn, so nothing of it precedes the log, and the packet carries no date, time
610
- or zone anywhere. The coordinates and the last program's address only; the other source
611
- addresses stay documented, not taught.
612
+ or zone anywhere. The coordinate only; the source addresses stay
613
+ documented, not taught.
612
614
 
613
615
  Worker control rides the daemon's inject seam (active→fold, idle→enqueue+drain), so the handler creates/branches the worker and hands off; the daemon owns provider + system prompt. FORK/WORK carry the seed task in the body and are their own ops, dispatched to worker control — never the entry-copy path.
614
616
 
@@ -1051,7 +1053,7 @@ boundary.
1051
1053
  - §worker-lifecycle-wake-liveness **A stream conclusion always reaches its worker.** The stream first persists its terminal state. A worker **blocked on a 202 wait** for that stream ({§wait-obligation-matrix}) then **awakens that loop in place** — the blocked loop *is* the continuation, so there is no fresh loop and no summary-as-prompt fiction. An already-active worker needs no injected prompt or second wake because its next packet reads the durable terminal state. A concluded worker receives no synthetic loop from ambient stream closure. The result remains available in the stream's own state under every case.
1052
1054
  - §worker-lifecycle-child-wake **Each child task completion notifies its parent.** Terminal-task publication, including failure and cancellation of a parked task, notifies the direct parent without injecting a prompt. Other unfinished tasks or streams in that child remain independent obligations; they cannot suppress notification. The parent's eligible waits requeue in place under {§loop-wake-identity} and the bounded {§worker-optimistic-settlement} opportunity. Durable revisioning covers completion-before-park and restart; drain teardown and whole-worker quiescence are not completion identities.
1053
1055
  - §worker-optimistic-settlement **Asynchronous settlement receives one bounded worker-local opportunity before model dispatch.** An initiating turn lets only the streams it started settle before program completion; separately, a stream conclusion, direct-child conclusion or addressed reply persists and publishes immediately but holds eligible parked loops' `202→100` requeues while another stream or direct child remains live. Both use `PLURNK_SERVICE_OPTIMISTIC_WAIT_MS`, shipped at five seconds; zero disables the opportunity. The wake hold ends as soon as no sibling obligation remains, never extends its original deadline, and coalesces arrivals within that window into at most one requeue per eligible loop. With no sibling obligation the wake is immediate; at the deadline, surviving work follows the ordinary monitored lifecycle. An arrival after provider dispatch begins retains its next wake, while poll, new-request and operator wakes never open this hold. Only packet/provider dispatch waits: durable state, client events, cancellation and the replying program do not. One redaction-safe span records elapsed time, quiescence versus deadline, and arrival count without entering the packet.
1054
- - §worker-lifecycle-idle-is-concluded **Idle is not unanswered.** An empty WAIT continues; an eligible final response with answered messages, observed results and no held work concludes under {§wait-obligation-matrix}. A concluded worker retains durable history; a later addressed arrival starts a new loop.
1056
+ - §worker-lifecycle-idle-is-concluded **Idle is not completion.** An empty WAIT continues; an eligible parameterless KILL with observed arrivals and results and no held work concludes under {§wait-obligation-matrix}, with or without an answer body. A concluded worker retains durable history; a later addressed arrival starts a new loop.
1055
1057
  - §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.
1056
1058
  - §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. Wake selection rechecks shutdown and worker cancellation before requeuing each parked loop.
1057
1059
  - §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 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 requeues on an unseen completion or when no live obligation remains. Otherwise it stays parked on surviving children; the drain restores inherited stream observation through the same guarded scheduler ({§worker-wait-timing}). 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.
@@ -1095,7 +1097,8 @@ append-only log high-water mark. Every log-targeted KILL in the program resolves
1095
1097
  row membership at or below that same boundary, while prior curation effects still
1096
1098
  compose normally. Message arrivals and other pre-program rows already present in the turn
1097
1099
  remain selectable; preceding and later operation rows cannot be captured by
1098
- their own program. A directly dispatched
1100
+ their own program. The emission row ({§emission-row}) is written after the snapshot, so the
1101
+ program it announces cannot select it; a later program can. A directly dispatched
1099
1102
  single operation captures the equivalent boundary before dispatch. This limits
1100
1103
  only log-row selection: operation phasing and same-turn resource effects retain
1101
1104
  their ordinary contracts.
@@ -1279,10 +1282,10 @@ The parser owns its boundaries; core admits determinate work and exposes its fai
1279
1282
  | Outside response text | Store it as the turn's `outside` source under {§outside-text}; never a row, never delivered, never completion. |
1280
1283
  | Lost boundary after a closed operation | Admit the closed operations and record the boundary diagnostic under {§unparsed-tail-boundary}. |
1281
1284
  | Lost boundary before any closed operation | Reject the attempt; neither outside text nor a reasoning NOTE substitutes for a closed response operation. |
1282
- | Outside text carrying a log-entry heading | Reject the attempt ({§fabricated-log-entry}). |
1285
+ | Outside text carrying a log-entry heading, other than an emission row's | Reject the attempt ({§fabricated-log-entry}). |
1283
1286
  | A response the provider stopped at a repeated line | Reject the attempt with the provider's sentence as its diagnostic ({§repetition-stop}); no provider recovery, notice, or problem row. |
1284
1287
 
1285
- §fabricated-log-entry **Only the harness writes the log.** A line of outside response text that begins with a log-entry heading, `### log:///<loop>/<turn>/<sequence>/` ({§log-wire-format}), is the model continuing the packet's transcript instead of answering it: it writes the receipts it expects and then acts on them. The attempt is rejected under {§invalid-emission-attempts}, so neither that text nor any operation beside it runs or is stored as outside text ({§outside-text}), and its one diagnostic, at the heading's line, reads `` `### log:///2/1/5/READ` is a log entry, and only the harness writes the log. Write the operation, then wait for its receipt. `` Text inside an operation body is not examined, so a SEND or KILL may quote a receipt. In 10,486 recorded emissions, 101 carried such a heading in outside text, every one a fabrication: 85 of 1,675 from deepseek-flash, 81 of them opening with one, and 16 from glm-5.3-flash, deepseek-v4-pro and qwen3.8-flash, which appended an invented `## Log` after their own operations.
1288
+ §fabricated-log-entry **Only the harness writes the log.** A line of outside response text that begins with a log-entry heading, `### log:///<loop>/<turn>/<sequence>/` ({§log-wire-format}), is the model continuing the packet's transcript instead of answering it: it writes the receipts it expects and then acts on them. The attempt is rejected under {§invalid-emission-attempts}, so neither that text nor any operation beside it runs or is stored as outside text ({§outside-text}), and its one diagnostic, at the heading's line, reads `` `### log:///2/1/5/READ` is a log entry, and only the harness writes the log. Write the operation, then wait for its receipt. `` Text inside an operation body is not examined, so a SEND or KILL may quote a receipt. A heading whose leaf is `emission` is exempt: the transcript shows it before each of the worker's own emissions ({§emission-row}), and repeating it invents no receipt, so the attempt is admitted and the heading stays outside text, counted by the digest as an echo. In 10,486 recorded emissions, 101 carried such a heading in outside text, every one a fabrication: 85 of 1,675 from deepseek-flash, 81 of them opening with one, and 16 from glm-5.3-flash, deepseek-v4-pro and qwen3.8-flash, which appended an invented `## Log` after their own operations.
1286
1289
 
1287
1290
  Warnings and closer recovery ({§closer-fallback}) do not reject. `finish=length`
1288
1291
  discloses truncation and precludes completion; it is not independently a rejection.
@@ -1530,6 +1533,8 @@ invalid range, read-only authority, and occupied hidden state without guessing.
1530
1533
 
1531
1534
  §file-directory-target **A directory is named as a directory.** Inside the root, a READ (or other exact-path read), KILL or EDIT whose target is a directory on disk — with or without a trailing slash — is refused `path-is-directory`, never as a missing or non-member file, since admitting it is not what the model needs: READ and KILL answer 404, EDIT 403. The detail is `'<key>' is a directory, not a file; <OP> reads/removes/writes one file.` and the recovery names the listing that reaches its files, `` List its files with `FIND (<key>/)`, then READ one by its path. `` (KILL: `then KILL each by its path`; EDIT: `` Name a file inside it, as `EDIT (<key>/<file>)`; list its files with `FIND (<key>/)`. ``). Beyond the root the disk stays dark and {§membership-read-refusal} holds unchanged.
1532
1535
 
1536
+ §file-find-directory **FIND recognizes existing directories without requiring a trailing slash.** An exact target naming a directory inside the workspace root resolves to the same recursive collection as its slash-suffixed spelling, including content matching and result pagination. Only workspace members appear; an empty directory or one containing only non-members yields a successful empty survey. Exact files stay exact, genuinely absent paths retain their missing-entry error, and this resolution does not inspect disk paths beyond the root. Shared glob and non-file URI semantics are unchanged.
1537
+
1533
1538
  ### §scheme-manifest Manifest
1534
1539
 
1535
1540
  §scheme-manifest-manifest Per the framework-owned author contract ({§manifest}), each registered scheme exposes one closed `SchemeManifest`. `Manifest.of` validates the complete declaration and enforces that `manifest.name` matches `package.json#plurnk.name` before registration.
@@ -2175,7 +2180,7 @@ same transitions the dispatcher's atomic curation event makes, without the row.
2175
2180
  The initialization turn records a short `_plurnk`-authored rationale containing
2176
2181
  a fenced NOTE. The shared reasoning extractor ({§reasoning-notes}) executes that
2177
2182
  NOTE through ordinary dispatch, creating its log item and immutable source.
2178
- The program begins with its own NOTE and READs its reasoning and persisted ops,
2183
+ The program begins with its own NOTE and READs its reasoning,
2179
2184
  demonstrating both NOTE placements and their ordinary results. The initial message arrives separately as an
2180
2185
  inbound SEND ({§message-arrival}). Neither initialization nor later turns
2181
2186
  manufacture a task inventory.
@@ -2199,7 +2204,7 @@ a turn whose emission or reasoning carries a foreign tool-call grammar or leaked
2199
2204
 
2200
2205
  AST: `{ op: "KILL", target, matcher: MatcherBody | null, lineMarker: TextLineMarker | null, body: null }` ({§kill-scope} and {§matcher-option} in the contracts SPEC own the grammar).
2201
2206
 
2202
- KILL deletes context from the **log** (`log:///`, {§packet}). Without a scope it retires the selected rows from the active projection ({§log-history-projection}). With a one-line or inclusive two-line scope it removes only that body's intersecting body-relative physical lines from the readable projection, and the row stays active. An anchor may be one published on that body or one returned by READing its `log:///` coordinate ({§line-anchors}); an anchor absent from the current body selects no line, as with an out-of-bounds numeric line. Scoped KILL is one-way: intervals accumulate, the durable body is untouched, and subsequent access follows {§log-readable-projection}. A scoped KILL on a bodyless row is a friendly 200 no-op with `matched` reported. A KILL that addresses no row is 404 on an exact coordinate and 204 on a sweep ({§log-curation-folder-idiom}). Selection composes target/glob with an optional heading pattern ({§log-curation-set-selection}). Parameterless KILL instead requests completion ({§kill-conclusion}).
2207
+ KILL deletes context from the **log** (`log:///`, {§packet}). Without a scope it retires the selected rows from the active projection ({§log-history-projection}). With a one-line or inclusive two-line scope it removes only that body's intersecting body-relative physical lines from the readable projection, and the row stays active. An anchor may be one published on that body or one returned by READing its `log:///` coordinate ({§line-anchors}); an anchor absent from the current body selects no line, as with an out-of-bounds numeric line. Scoped KILL is one-way: intervals accumulate, the durable body is untouched, and subsequent access follows {§log-readable-projection}. A scoped KILL on a bodyless row is a friendly 200 no-op with `matched` reported. An emission row is curated whole ({§emission-row}): a scope covering every line retires it like an unscoped KILL; on its exact coordinate a narrower scope is 422 `emission-curated-whole`, whose recovery names both forms that retire it; a sweep leaves it intact. A KILL that addresses no row is 404 on an exact coordinate and 204 on a sweep ({§log-curation-folder-idiom}). Selection composes target/glob with an optional heading pattern ({§log-curation-set-selection}). Parameterless KILL instead requests completion ({§kill-conclusion}).
2203
2208
 
2204
2209
  §log-scope-recovery A log-body scope follows the file slicer's range rule ({§range-starts-at-one} in the schemes SPEC): a range starting at 0 — `<0,-1>` included — is refused 416 `range-not-satisfiable` on every body, empty ones too, and never clamped; its detail is the slicer's own sentence, `Range <0,-1> starts at 0, which is not a line; lines are numbered from 1.`, and its recovery names the forms a log body takes — `Write <1,-1> to trim every line of the body; KILL (log:///1/9/2/READ) with no scope retires the whole row.`, or `To trim lines 1 through M, write <1,M>; …` — never the insert and append positions a body cannot take. Every other scope that names no line is 400 `curation-scope-invalid` and likewise names the model's mistake in its coordinates and the forms that work on that row: `<0>` offers `<1>`; an end below 1 offers `<L,-1>`; a backward `<5,3>` offers `<3,5>`; anything else offers `<L>`, `<L,M>` and the unscoped row KILL.
2205
2210
 
@@ -2209,7 +2214,7 @@ A READ carrying active native media is atomic: any KILL scope is ignored and the
2209
2214
 
2210
2215
  | Fact | Owner | Effect |
2211
2216
  | --- | --- | --- |
2212
- | Initial body suppression | Immutable event `initial_folded` | Packet presentation only; explicit retrieval can read an initially hidden body. |
2217
+ | Initial body suppression | Immutable event `initial_folded` | Packet presentation only; explicit retrieval can read an initially hidden body. An emission row's hidden body reaches the packet as its assistant message ({§emission-row}). |
2213
2218
  | Deliberate scoped KILL | Current projection `folded`, initially empty | Packet, READ, FIND, COPY, and search omit those lines; later retrieval cannot undo trimming. |
2214
2219
 
2215
2220
  Packet display combines both masks. Other consumers use only deliberate trimming.
@@ -2243,7 +2248,7 @@ The `## Log` section is a sequence of ordinary Markdown records separated by one
2243
2248
  | facts | One strict JSON object in stable alphabetical order. | Present only when a fact exists. Asides, scopes, opaque invocation metadata and result facts belong here, not on the H3. |
2244
2249
  | body | Coordinate-prefixed lines. | Present when the row is visible. |
2245
2250
 
2246
- Patterns retain their literal spelling; a pattern containing a line break is JSON-quoted to keep the H3 on one physical line. Receipts are descriptive records, not reconstructed operation headings. Absent fields are not invented. Every physical body line retains its canonical numeric `N:` or anchored `@hash N:` coordinate, so source text cannot create a record boundary. The section contains records only, with no leading prose or enclosing fence.
2251
+ An emission row's body is not in its record: it follows the record as the worker's assistant message ({§packet-wire-envelope}). Patterns retain their literal spelling; a pattern containing a line break is JSON-quoted to keep the H3 on one physical line. Receipts are descriptive records, not reconstructed operation headings. Absent fields are not invented. Every physical body line retains its canonical numeric `N:` or anchored `@hash N:` coordinate, so source text cannot create a record boundary. The section contains records only, with no leading prose or enclosing fence.
2247
2252
 
2248
2253
  §log-address-metadata **Addresses name their relationship, not the row's producer.**
2249
2254
 
@@ -2293,7 +2298,7 @@ complete non-retrieval body retains `lines` where no other field supplies its
2293
2298
  navigable extent. None of these spellings changes acquisition, delivery,
2294
2299
  curation, admission, or immutable evidence.
2295
2300
 
2296
- Field absence carries defaults: `origin` is omitted for the owning model, `source` for the owning worker, and `status` for a routine 200. Dispositions always carry their lifecycle status, SEND its delivery status, KILL keeps an explicit 200, and every non-200 stays explicit. A present authored aside appears as `aside`. Every row's accounting follows {§packet-token-accounting}.
2301
+ Field absence carries defaults: `origin` is omitted for the owning model, `source` for the owning worker, and `status` for a routine 200. An emission row renders its author, the turn's producer, as its `origin` ({§emission-row}). Dispositions always carry their lifecycle status, SEND its delivery status, KILL keeps an explicit 200, and every non-200 stays explicit. A present authored aside appears as `aside`. Every row's accounting follows {§packet-token-accounting}.
2297
2302
 
2298
2303
  Authored `metadata` retains its opaque ordered block strings under {§scheme-metadata-modifier}; COPY/MOVE pair them as `{from,to}`. Packet rendering does not interpret scheme options or discard malformed input from a failed operation.
2299
2304
 
@@ -2325,7 +2330,7 @@ Authored `metadata` retains its opaque ordered block strings under {§scheme-met
2325
2330
  records the exact READ coordinates sent without controlling retention. Missing immutable bytes are an
2326
2331
  internal integrity failure, never silently dropped content. No ejection message or permanent teaching is
2327
2332
  added. These stable curation weights are not provider-token measurements ({§tokenomics-render-weight-budget}).
2328
- - §packet-token-accounting Every row reports one `logTokens` charge on its H3 ({§log-wire-format}): its complete materialized H3, facts, visible body, and selected native attachment. The completed record is measured to a fixed point, including the accounting field itself. No `tokensBody`, `tokensMetadata`, or `tokensActive` field is serialized. Hidden text is not charged; metadata-only rows still have a reclaimable charge. Source/FIND-item `tokens` measure source content, not the observation's context footprint. A FIND's nonzero `itemsTokenTotal` weighs the complete matched set; a nonzero `returnedItemsTokenTotal` appears only when the returned page differs. All use stable curation weights, not provider tokens or dollars. Native component accounting follows {§packet-attachment-parts}; ordinary addressability and truthful errors follow {§log-wire-format}.
2333
+ - §packet-token-accounting Every row reports one `logTokens` charge on its H3 ({§log-wire-format}): its complete materialized H3, facts, visible body, selected native attachment, and the emission it delivers outside its record ({§emission-row}). The completed record is measured to a fixed point, including the accounting field itself. No `tokensBody`, `tokensMetadata`, or `tokensActive` field is serialized. Hidden text is not charged; metadata-only rows still have a reclaimable charge. Source/FIND-item `tokens` measure source content, not the observation's context footprint. A FIND's nonzero `itemsTokenTotal` weighs the complete matched set; a nonzero `returnedItemsTokenTotal` appears only when the returned page differs. All use stable curation weights, not provider tokens or dollars. Native component accounting follows {§packet-attachment-parts}; ordinary addressability and truthful errors follow {§log-wire-format}.
2329
2334
 
2330
2335
  ### §retrieval-packet-metadata READ/FIND packet metadata
2331
2336
 
@@ -2371,14 +2376,34 @@ single line past the end, a reversed range, empty content, a command's log row
2371
2376
 
2372
2377
  ### §turn-ops-entry The admitted turn program
2373
2378
 
2374
- §turn-ops-log-curation A source-backed turn preserves its **exact admitted Plurnk program**, including ignored interstitial text, before dispatch. `turn_sources` records that source once, separately from the curatable log and optional provider evidence. Retention does not manufacture a log row. Ordinary READ creates a receipt governed by {§log-readable-projection}; curation of that receipt never changes the source. Initialization reads its own already-persisted source under {§worker-initialization-entry}.
2379
+ §turn-ops-log-curation A source-backed turn preserves its **exact admitted Plurnk program**, including ignored interstitial text, before dispatch. `turn_sources` records that source once, separately from the curatable log and optional provider evidence. Retention does not manufacture a log row; the one row an admitted emission gains is its announcement ({§emission-row}). Ordinary READ creates a receipt governed by {§log-readable-projection}; curation of either never changes the source.
2380
+
2381
+ ### §emission-row The emission row
2382
+
2383
+ Every admitted emission is announced by one row of its own turn, so the transcript carries the
2384
+ worker's emissions in chronological place ({§packet-wire-envelope}) and the worker curates them
2385
+ like any other row.
2386
+
2387
+ | Surface | Contract |
2388
+ |---|---|
2389
+ | When | An inference turn that admitted at least one statement from its provider content, and turn zero's survey ({§worker-initialization-entry}). A programmatic batch, an empty turn ({§empty-turn}), a client operation and a rejected attempt ({§rejected-emission-entry}) announce nothing. |
2390
+ | Place | After the turn's inputs (arrivals, deltas, open-path READs) and before its reasoning NOTEs and operations, written after the selection snapshot ({§turn-ops-selection-snapshot}): the emission sits between what the worker had seen and what it caused. |
2391
+ | Row | A `_plurnk` READ of the turn's own source, `ops://<worker>/L/T`, with `attrs.kind="emission"` and the canonical leaf `/emission`: `### log:///L/T/S/emission → ops://<worker>/L/T · N`. It renders its author, the turn's producer, as `origin`, so a model's row carries none. It is no operation: no receipt, tool call or strike, and outside the op mix. |
2392
+ | Body | Frozen at announcement: the canonical rendering ({§statement-rendering}) of every admitted content statement, in order, each in a closed fence. Each body appears within the shared preview bound ({§body-projection}), `PLURNK_SERVICE_PREVIEW_LINES` and `PLURNK_SERVICE_PREVIEW_CHARS`. A longer body keeps its head, and its closing fence carries `<!-- Automatically truncated op body: READ (ops://<worker>/L/T) to retrieve in full -->`; nothing the harness writes enters a fence. Absent and empty bodies remain empty. All heading operands, scopes, metadata, patterns and asides remain. Free text and unadmitted forms are absent; a recovered native call ({§native-tool-calls}) appears as the operation it was read as; an operation whose receipt failed stays. Turn zero uses the same projection of its survey. No body text is inspected for nested operations. |
2393
+ | Sources and memory | Dispatch and immutable `ops://` sources retain complete bodies ({§turn-source-resources}); an explicit source READ returns them normally. The wire omits NOTE and WAIT blocks, whose own rows show them whole ({§body-projection}), so curating a NOTE row removes its text; an emission of only NOTE and WAIT delivers nothing, its row stands, and no assistant message follows it ({§packet-wire-envelope}); reasoning-only NOTEs never enter this projection. |
2394
+ | Stability | The projection is fixed from its first appearance, never aged or resized under budget pressure. Already frozen announcements and historical request captures are not rewritten. |
2395
+ | Presentation | Born folded: the record shows its header, and its body follows the record as the worker's assistant message. |
2396
+ | Accounting | `logTokens` charges the record and the emission the wire delivers, truncation asides included, never the omitted NOTE and WAIT blocks or the cut remainder ({§packet-token-accounting}). An explicit source READ has its own ordinary charge. |
2397
+ | Curation | Curated whole ({§log-kill-scope}): KILL retires it, and so does a scope covering every line (`<1,-1>`); on its exact coordinate a narrower scope is 422 `emission-curated-whole`, and a sweep whose scope would only trim it leaves it intact. |
2398
+ | Schema | Migration 12 admits `kind="emission"` only on this shape: one per turn, the turn's newest row when written, frozen, and curated whole. A database from before version 12 keeps its rows and gains no announcement. FORK copies it with the inherited turns, still naming its writer. |
2399
+ | Echoes | A worker that repeats the heading in its own text is tolerated ({§fabricated-log-entry}); the digest counts the echoes. A worker that copies the truncation aside onto its own closer keeps its body intact; the aside is outside text ({§outside-text}). |
2375
2400
 
2376
2401
  ### §turn-source-resources Immutable turn-source resources
2377
2402
 
2378
2403
  | Surface | Contract |
2379
2404
  |---|---|
2380
2405
  | Identity | `ops://<worker>/<loop>/<turn>`, `reasoning://<worker>/<loop>/<turn>`, and `note://<worker>/<loop>/<turn>/<item>` name a worker in the current workspace and its durable coordinates. A note's item is its dispatched NOTE ordinal. The `outside` source ({§outside-text}) has no address: no `outside://` scheme exists, and it is reached only through `outside/event`, FORK and the digest. The worker authority is required and case-sensitive; userinfo, ports, and queries are invalid. Source identity never depends on the reading worker. `log:///` remains local; READ, FIND and KILL reject log authorities, userinfo, ports and queries with 400, never substitute the caller's log. |
2381
- | Source | `ops` is exact admitted `text/vnd.plurnk`; `reasoning` is `text/plain` containing the selected original provider reasoning or a non-model producer's authored rationale. Producer identity comes from the owning turn; a harness rationale is not provider evidence. The turn decides existence and the source decides content: a turn that exists but has no source of that kind reads as the ordinary empty resource (204, empty body), never a fabricated one; a worker or turn that does not exist is 404. |
2406
+ | Source | `ops` is exact admitted `text/vnd.plurnk`, and its turn's emission row carries the preview-bounded projection ({§emission-row}); `reasoning` is `text/plain` containing the selected original provider reasoning or a non-model producer's authored rationale. Producer identity comes from the owning turn; a harness rationale is not provider evidence. The turn decides existence and the source decides content: a turn that exists but has no source of that kind reads as the ordinary empty resource (204, empty body), never a fabricated one; a worker or turn that does not exist is 404. |
2382
2407
  | Notes | Each dispatched NOTE stores its exact literal body as an immutable `text/plain` source and returns its worker-qualified address. There may be multiple notes in a turn, from reasoning, content, or another producer. A missing note is 404, not an empty invented note. Sharing its URI uses ordinary SEND; the receiver deliberately READs it. NOTE itself sends no ambient update. |
2383
2408
  | Retention | One ops source, one reasoning source and one outside source per turn; one note source per NOTE ordinal. An optional inference-call link records provenance. Source removal follows deletion of its owning turn, never log curation. |
2384
2409
  | Operations | Ordinary scoped READ, FIND, content search and COPY from any named worker's source within the workspace. FIND accepts authority and path patterns, retaining complete worker-qualified identities in results and folder selectors. READ returns data and never executes it. Sources are read-only for every actor and have no edit hashes. |
@@ -2386,11 +2411,11 @@ single line past the end, a reversed range, empty content, a command's log row
2386
2411
  | FORK | Sources copy with the inherited turns at identical loop/turn/item coordinates under the fork's own authority. Bytes and embedded source references are preserved verbatim; an explicit reference still names its original worker. Branch receipt curation is independent; neither branch can rewrite source evidence. |
2387
2412
  | Forensics | Digest assistant artifacts read source directly, independently of receipt presence or curation. Original provider responses retain all attempts and opaque fields separately. |
2388
2413
 
2389
- §rejected-emission-entry A rejected provider response is not `turnOps`: it never became an admitted turn program. The one bounded invalid-emission recovery item under {§emission-admission} has `attrs.kind="emissionAttempt"`, `origin="model"`, the canonical model-facing `/attempt` leaf, and the exact latest rejected response. The packet does not duplicate that identity as `kind` metadata. It is born durably body-suppressed and projected visibly only in the informed recovery packet; every other rejected attempt remains forensic-only.
2414
+ §rejected-emission-entry A rejected provider response is not `turnOps`: it never became an admitted turn program, and no emission row announces it ({§emission-row}). The one bounded invalid-emission recovery item under {§emission-admission} has `attrs.kind="emissionAttempt"`, `origin="model"`, the canonical model-facing `/attempt` leaf, and the exact latest rejected response. The packet does not duplicate that identity as `kind` metadata. It is born durably body-suppressed and projected visibly only in the informed recovery packet; every other rejected attempt remains forensic-only.
2390
2415
 
2391
- - §log-coordinate-hierarchy **Log coordinates are a hierarchical prefix; the trailing slash is optional** — a coordinate is `loop/turn/sequence`, and a PARTIAL coordinate selects its descendants: `log:///1` = loop 1's rows, `log:///1/2` = turn 1/2's rows, `log:///1/2/3` = the one row. A full coordinate is always three parts, so a one- or two-part path is unambiguously a prefix — the trailing slash is an optional alias (`log:///1/2` ≡ `log:///1/2/`), uniform with ```` ```READ (worker:///docs/) ````. A complete `[start-end]` segment in any numeric coordinate slot selects that inclusive decimal interval; brackets elsewhere retain ordinary path-glob meaning. Every rendered row appends one canonical model-facing leaf: the native operation name or invoked executor name, `/attempt` for a rejected emission. An executor leaf is derived from the durable submitted statement (its `runtime`), never an internal dispatch type or the current tool registry. Digits and punctuation in executor names remain part of the leaf. The leaf names identity rather than adding a resource level. Exact consumers tolerate the unsuffixed three-part shorthand; when supplied, the case-insensitive leaf is authoritative and a disagreement resolves 404. READ anchors use the canonical suffixed identity even when addressed by shorthand. Typed entry materialization therefore resolves as `/READ` while retaining its durable `EDIT` event ({§exec-entry-sink}). `log:///1/2/*` still selects the turn's item rows, while `log:///**/READ`, `log:///**/python3`, and `log:///**/attempt` deliberately filter canonical leaves. Executor outputs instead use workspace-wide claims such as `sh:///ab3d5678#stdout` ({§execution-output-identity}); their source operation has log coordinates, but resource lifetime and identity are independent of that observation. Error pointers, Problem instances, source attribution, and search use this same identity; client stream coordinates retain the numeric triple. Within a turn, sequence is arrival order. Inbound SEND rows publish before the program runs ({§message-arrival}); a turn receiving messages holds the first at `log:///L/T/1/SEND`, followed by further arrivals oldest first, then the model's operations ({§packet-current-turn} names `L/T`).
2392
- - §log-curation-folder-idiom **Log curation speaks the folder idiom; a zero-match sweep is a no-op success** — KILL takes a concrete coordinate or a path-glob, and a **trailing slash or a partial coordinate means "the contents"** ({§log-coordinate-hierarchy}), like a folder-scoped FIND: ```` ```KILL (log:///1/2) <1,-1> ```` suppresses turn 1/2's bodies. A **well-formed selection that matches nothing is 204 with `matched: 0`**; a successful sweep's rx carries `matched: N`. Parameterless KILL instead requests completion ({§kill-conclusion}).
2393
- - §log-curation-set-selection **Row selection and body scope are independent** — target/glob and an optional heading pattern (```` ```KILL (log:///**) [{"pattern": "~stale"}] ````, every dialect a FIND over rows accepts) compose by intersection into the affected row set. An optional `<L>` or `<SL,EL>` then intersects each selected canonical body; it never paginates or changes the selected set. Thus ```` ```KILL (log:///**/READ) <17,-1> ```` may change long READs and no-op on short ones while reporting every selected row in `matched`.
2416
+ - §log-coordinate-hierarchy **Log coordinates are a hierarchical prefix; the trailing slash is optional** — a coordinate is `loop/turn/sequence`, and a PARTIAL coordinate selects its descendants: `log:///1` = loop 1's rows, `log:///1/2` = turn 1/2's rows, `log:///1/2/3` = the one row. A full coordinate is always three parts, so a one- or two-part path is unambiguously a prefix — the trailing slash is an optional alias (`log:///1/2` ≡ `log:///1/2/`), uniform with ```` ```READ (worker:///docs/) ````. A complete `[start-end]` segment in any numeric coordinate slot selects that inclusive decimal interval; brackets elsewhere retain ordinary path-glob meaning. Every rendered row appends one canonical model-facing leaf: the native operation name or invoked executor name, `/attempt` for a rejected emission, `/emission` for an admitted one's announcement ({§emission-row}). An executor leaf is derived from the durable submitted statement (its `runtime`), never an internal dispatch type or the current tool registry. Digits and punctuation in executor names remain part of the leaf. The leaf names identity rather than adding a resource level. Exact consumers tolerate the unsuffixed three-part shorthand; when supplied, the case-insensitive leaf is authoritative and a disagreement resolves 404. READ anchors use the canonical suffixed identity even when addressed by shorthand. Typed entry materialization therefore resolves as `/READ` while retaining its durable `EDIT` event ({§exec-entry-sink}). `log:///1/2/*` still selects the turn's item rows, while `log:///**/READ`, `log:///**/python3`, `log:///**/attempt`, and `log:///**/emission` deliberately filter canonical leaves. Executor outputs instead use workspace-wide claims such as `sh:///ab3d5678#stdout` ({§execution-output-identity}); their source operation has log coordinates, but resource lifetime and identity are independent of that observation. Error pointers, Problem instances, source attribution, and search use this same identity; client stream coordinates retain the numeric triple. Within a turn, sequence is arrival order. Inbound SEND rows publish before the program runs ({§message-arrival}); a turn receiving messages holds the first at `log:///L/T/1/SEND`, followed by further arrivals oldest first, then its emission's announcement, then the model's operations ({§packet-current-turn} names `L/T`).
2417
+ - §log-curation-folder-idiom **Log curation speaks the folder idiom; a zero-match sweep is a no-op success** — KILL takes a concrete coordinate or a path-glob, and a **trailing slash or a partial coordinate means "the contents"** ({§log-coordinate-hierarchy}), like a folder-scoped FIND: ```` ```KILL (log:///1/2) <1,-1> ```` suppresses turn 1/2's bodies and retires its emission ({§emission-row}). A **well-formed selection that matches nothing is 204 with `matched: 0`**; a successful sweep's rx carries `matched: N`. Parameterless KILL instead requests completion ({§kill-conclusion}).
2418
+ - §log-curation-set-selection **Row selection and body scope are independent** — target/glob and an optional heading pattern (```` ```KILL (log:///**) [{"pattern": "~stale"}] ````, every dialect a FIND over rows accepts) compose by intersection into the affected row set. An optional `<L>` or `<SL,EL>` then intersects each selected canonical body; it never paginates or changes the selected set. An emission row is curated whole instead: the scope retires it when it covers every line and otherwise leaves it ({§emission-row}). Thus ```` ```KILL (log:///**/READ) <17,-1> ```` may change long READs and no-op on short ones while reporting every selected row in `matched`.
2394
2419
 
2395
2420
  §log-kill-meta-operation **A log KILL changes working context, never the underlying resources or execution history.** Receipt visibility depends on the target and result, not the producer, attribution, or age of the turn:
2396
2421
 
@@ -2614,7 +2639,7 @@ Log history preserved — `log_entries` stores path tuple as text, not FK to `en
2614
2639
  - §find-line-anchors **A FIND regex anchors each line**, as READ, EDIT and KILL do ({§read-pattern}, {§edit-pattern}): `^` and `$` are a line's ends in every FIND content match — over entries, log rows, turn sources and a binary channel's bytes — so ```` ```FIND (django/urls/resolvers.py) /^from|^import/ ```` locates the same import lines a READ with that pattern shows, never a false 204.
2615
2640
  - §find-candidate-containment **One candidate's crash is that candidate's problem** — arbitrary member content can crash a mimetype handler mid-match (an unbalanced template partial crashed Readability and killed a 1,916-file FIND as a blank 500, #449). `Matcher.matchCandidates` contains a per-candidate handler throw: the candidate drops out exactly like unsupported content, the cause goes to daemon stderr, and only a FIND whose every candidate crashed reports a 415 whose Problem names the first crashing member and handler. The operation's other candidates always answer.
2616
2641
 
2617
- - §find-scope-prefix-filter Filters entries within scope. A **bare** path is the exact entry; an explicit **shell glob**, classified once by {§path-glob}, expands to a scope. Path globs use segment semantics: `*` and `?` never cross `/`; `**` does — in every spelling: a `**` glued to a name (`**.go`, `src/**.ts`) is matched as `**/*.go` / `src/**/*.ts`, never demoted to a one-level `*` the way a native matcher reads it. Terminal `*` and `**` are structural catalog selectors and include dot-prefixed entries, so a complete map does not hide `.env.defaults` or `.github`; richer patterns retain native shell behavior. SQLite prefix queries may reduce the candidate set but never decide the match. A trailing slash is a recursive FIND scope only for a scheme whose manifest declares `folderScopes: true`; otherwise it is ordinary resource syntax. This is an explicit plugin contract, never inferred from URL punctuation.
2642
+ - §find-scope-prefix-filter Filters entries within scope. A **bare** path is the exact entry unless the file scheme resolves a directory under {§file-find-directory}; an explicit **shell glob**, classified once by {§path-glob}, expands to a scope. Path globs use segment semantics: `*` and `?` never cross `/`; `**` does — in every spelling: a `**` glued to a name (`**.go`, `src/**.ts`) is matched as `**/*.go` / `src/**/*.ts`, never demoted to a one-level `*` the way a native matcher reads it. Terminal `*` and `**` are structural catalog selectors and include dot-prefixed entries, so a complete map does not hide `.env.defaults` or `.github`; richer patterns retain native shell behavior. SQLite prefix queries may reduce the candidate set but never decide the match. A trailing slash is a recursive FIND scope only for a scheme whose manifest declares `folderScopes: true`; otherwise it is ordinary resource syntax. This is an explicit plugin contract, never inferred from URL punctuation.
2618
2643
 
2619
2644
  Resource-authority globs select authorities independently of the path scope.
2620
2645
  Matching resources retain their full addresses through pattern matching,
@@ -2631,7 +2656,7 @@ Log history preserved — `log_entries` stores path tuple as text, not FK to `en
2631
2656
  - §find-fulltext-selection Every matcher operates only over the candidate set selected by `(target)`; indexed matchers do not bypass that selection. `~query` passes the native FTS5 expression to SQLite and ranks matching candidates by ascending BM25, with resource identity breaking ties. Native BM25 uses the shared index's term statistics; candidate visibility, owner, channel and target filters determine which resources can be returned. The ordinary FIND pager selects resources for broad targets or match locations for exact targets: markerless search uses {§markerless-first-page}, `<N>` selects position N and `<N,M>` selects an inclusive range. Fractions are invalid result coordinates, not similarity thresholds. Results expose addressable matched text regions; neither cosine scores nor percentage similarity is invented. Native query-syntax failures return 400 with SQLite's diagnostic; database and implementation failures propagate.
2632
2657
  - §fts-word-phrase **A word with inner punctuation is the phrase of its tokens.** FTS5 barewords hold only letters, digits, `_` and non-ASCII, so before the query reaches SQLite each word outside a quoted string or `NEAR(…)` group that is not a bareword (with optional leading `^` and trailing `*`) is quoted as a phrase: `~inherited-members` searches `"inherited-members"` — the adjacent tokens `inherited members` — instead of failing as `no such column: members`, and `c++`, `x.y`, `a/b` likewise. FTS5's own syntax passes untouched: `AND`/`OR`/`NOT`/`NEAR`, `+`, quoted phrases, parentheses, and column filters (a word containing `:`, `{` or `}`, or opening with `-`). A column-filter failure keeps SQLite's diagnostic and its recovery says what the filter is and gives the bare-word and `NOT` forms: `` `members:` and `-members` are FTS5 column filters, and the index has one column; to search for a word write it bare, as `~members`, and to exclude one write `NOT` between terms, as `~a NOT members`. ``
2633
2658
  - §find-scoped-isolation Workspace + scheme scoped — no cross-workspace/cross-scheme leakage.
2634
- - §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 }`:
2659
+ - §find-result-projection **The resolved target selection determines the result unit; result cardinality never changes it** ({§find-result-unit}). Returns `FindResult { status, content, mimetype, results, range, matchingPathCount, matchLocationCount, itemsWeightTotal, returnedItemsWeightTotal }`:
2635
2660
 
2636
2661
  | Target | Matcher | `range.unit` | Result rows |
2637
2662
  |---|---|---|---|
@@ -2717,10 +2742,9 @@ same durable liveness.
2717
2742
  | Fresh operation/parser failure, without an authored WAIT | Continue before any automatic parking. |
2718
2743
  | Neither an authored WAIT nor an eligible completion request ({§kill-conclusion}) | Continue, regardless of earlier replies or live work. |
2719
2744
  | Live work and either WAIT or an eligible completion request | Park the same loop; message arrival, child or stream settlement, or stream cadence wakes it. No final-answer body is delivered while joining. |
2720
- | WAIT without live work | Continue; never invent a future wake. The first such WAIT is an honest yield and its row says only `Nothing is in flight. Continuing.`; a second in the same loop is the model waiting on a wake nothing can send, so its own row instead names what WAIT is for and what to reach for — `WAIT doesn't wait unless there's a child worker or stream to wait on. Use schedule for specific timing decisions.` The correction rides the operation's own result, which is the surface the model is certain to read. |
2721
- | Unanswered messages | Continue. |
2745
+ | WAIT without live work | Continue; never invent a future wake. Its row says only `Nothing is in flight. Continuing.`, however often the loop yields this way. |
2722
2746
  | Unobserved operation results, failures, child results or stream conclusions | Continue; the next packet presents them. |
2723
- | Eligible completion request with no outstanding messages, live work or unobserved results | Conclude successfully. |
2747
+ | Eligible completion request with no unpublished arrivals, live work or unobserved results | Conclude successfully, whether or not it delivers an answer. |
2724
2748
 
2725
2749
  An empty emission is handled by {§empty-turn}. Ordinary strikes, cycles and
2726
2750
  execution limits remain independent. NOTE and successful targeted KILL do not themselves
@@ -2780,21 +2804,22 @@ accounting and model-visible failure evidence remain separately owned by
2780
2804
  outstanding condition. Valid sibling operations always execute. Only an admitted
2781
2805
  KILL delivers its literal body through {§send-response-receipt}; a deferred body
2782
2806
  remains forensic evidence, never a stored draft to replay automatically. An empty
2783
- KILL concludes without repeating an already-delivered answer, but cannot abandon an
2784
- unanswered message. SEND, NOTE and targeted KILL never request successful
2807
+ KILL concludes silently even when an observed message has no delivered answer. It neither
2808
+ invents delivery nor replaces an earlier answer; immutable input and reply history remain intact.
2809
+ SEND, NOTE and targeted KILL never request successful
2785
2810
  completion. New arrivals still guard the terminal transition atomically
2786
- ({§completion-defers-to-messages}); an arrival concurrent with an accepted reply
2787
- remains unanswered and keeps the loop running. No implicit successful exit exists.
2811
+ ({§completion-defers-to-messages}); an unobserved arrival concurrent with completion
2812
+ keeps the loop running. No implicit successful exit exists.
2788
2813
  - §outside-text **Text outside the operations is the turn's `outside` source: stored, weighed, never a row.**
2789
2814
  The spans {§response-text} supplies are stored verbatim as one immutable `outside` turn source
2790
2815
  per admitted emission, in source order joined by a blank line ({§turn-source-resources}); no
2791
2816
  operation is minted for them, a repetitive or length-cut response stays one source, and nothing
2792
2817
  filters what is stored. The model hears only the weight: the next packet's Notices section
2793
2818
  carries `outside_text: N tokens emitted outside OPs. Discarded.`, N by {§tokenomics-agnostic-ruler};
2794
- the text itself never enters a packet, is never delivered and never concludes.
2819
+ the text itself never enters a packet, not even inside its turn's emission ({§emission-row}), is never delivered and never concludes.
2795
2820
  {§empty-turn} still strikes a turn that holds only text, with unchanged reasoning recovery
2796
2821
  ({§reasoning-empty-turn-read}), reply accounting and completion rules. A log-entry heading in
2797
- outside text still rejects the attempt ({§fabricated-log-entry}); an unfenced operation line is
2822
+ outside text still rejects the attempt unless it echoes an emission row ({§fabricated-log-entry}); an unfenced operation line is
2798
2823
  not response text ({§unfenced-operation}) and so never reaches the source; `KnownToxins` guards
2799
2824
  only the read-back ({§reasoning-empty-turn-read}). Clients receive the text once through
2800
2825
  `outside/event` ({§notifications-outside-event}, {§agui-outside-text}); FORK snapshots the
@@ -2804,10 +2829,11 @@ accounting and model-visible failure evidence remain separately owned by
2804
2829
  {§response-text} alone owns which bytes are operations, quotations or outside text.
2805
2830
  - §loop-answer **A loop's address is what it said.** READ `ops://<worker>/<loop>` resolves to
2806
2831
  the latest reply the loop gave to the message that started it: the body of a SEND
2807
- or accepted final KILL that answered that message. A running loop without one is 425; a loop that
2808
- ended without one is its terminal problem (404 when it ended 2xx) — and that problem cites what
2809
- the model last left unconcluded, so the loop's own address never reports silence from a loop that
2810
- spoke ({§terminal-evidence}). `ops://<worker>/<loop>/<turn>`
2832
+ or accepted final KILL that answered that message. A failed terminal takes precedence over
2833
+ an earlier reply and retains its exact Problem, including {§terminal-evidence}. A running loop
2834
+ without a reply is 425; a concluded loop without one returns its terminal outcome, including
2835
+ successful silence. Completion never fabricates an answer or a missing-resource failure.
2836
+ `ops://<worker>/<loop>/<turn>`
2811
2837
  remains that turn's emission. A concluded child's `loop_termination` row to its parent
2812
2838
  READs this same loop resource. Witness: `test/intg/loop-answer.test.ts`.
2813
2839
  - §empty-turn **No authored response operation is a recoverable turn, never completion.**
@@ -3030,13 +3056,15 @@ the workspace snapshot. Installed siblings form the immutable base:
3030
3056
  they are discovered and probed at startup, and availability is cached.
3031
3057
  Workspace Functionality providers may atomically overlay additional names under
3032
3058
  {§module-workspace-capabilities}; a name has one owner within a workspace, while
3033
- independent workspaces may use the same name. An absent or empty tag selects
3034
- `sh`; a non-empty tag selects exactly that registered executable tool. Unknown
3035
- tags are refused 501 with the advertised catalogue and are never reinterpreted
3036
- as shell command words. The common mistaken `[shell]` alias is narrowly told to
3037
- omit that signal for the default shell; arbitrary unknown tags receive no guessed
3038
- alternative intent. An unavailable runtime is also 501 and carries the probe
3039
- `detail`.
3059
+ independent workspaces may use the same name. The fence name selects exactly
3060
+ that registered executable tool. Unknown tags are refused 400 with the
3061
+ advertised catalogue and are never reinterpreted as shell command words.
3062
+ A runtime unavailable after an ordinary probe is 501 with the probe `detail`.
3063
+ Typed configuration failures preserve the declaration, with no executor instance
3064
+ or output scheme; invocation returns the exact 503 configuration Problem
3065
+ ({§configuration-repair-path}). No executor, including `sh`, is required for
3066
+ daemon startup. Internal constructor defects remain failures, not unavailable
3067
+ configuration verdicts.
3040
3068
 
3041
3069
  For a family runtime, `ExecutorRegistry.toolRegistry(tag, workspaceId)`
3042
3070
  validates the one executor-owned snapshot used by packet presentation,
@@ -3213,8 +3241,8 @@ Each layer uses the same value and masking rules. A worker's list includes works
3213
3241
  defaults by reference with `origin: "workspace"`; worker overrides and masks remain
3214
3242
  worker-owned. Enabling an inherited entry clears this layer's mask, not a mask in a
3215
3243
  lower layer; an explicit local value can override that lower layer. A mask follows
3216
- the name even when its lower-layer origin changes. Removing an override reveals the lower entry disabled, as for a service
3217
- baseline. Forking copies only worker state, not the workspace defaults. Workspace edits
3244
+ the name even when its lower-layer origin changes. Removing an override restores the
3245
+ lower entry and its enabledness ({§configuration-definition-resolution}). Forking copies only worker state, not the workspace defaults. Workspace edits
3218
3246
  affect subsequent launches, not existing processes or other workspaces. A shared
3219
3247
  capability never acquires an invoking worker's overrides or ownership.
3220
3248
 
@@ -3414,11 +3442,19 @@ was said ({§methods-loop-run-fold-consistency}). `LoopPolicies.compose` then ma
3414
3442
  once. `PLURNK_SERVICE_ATTENDED` answers an unstated attendance, and the attendance picks which
3415
3443
  knob answers an unstated disposition: `PLURNK_SERVICE_PROPOSALS` for an attended loop,
3416
3444
  `PLURNK_SERVICE_UNATTENDED_PROPOSALS` for an unattended one, whose vocabulary has no `review`.
3417
- Every panel state is therefore lawful, and an invalid knob fails boot by its name. No code,
3445
+ Every valid panel state is therefore lawful; an invalid knob is diagnosed at startup
3446
+ and refuses a loop that needs it ({§configuration-repair-path}). No code,
3418
3447
  schema or column holds a default ({§operator-config-only-home}): `loops.policy` and
3419
- `loops.max_turns` carry none, so every insert states both, and an administrative loop — a
3420
- client's direct statements, the runtime's own narration — states the panel's policy like any
3421
- other loop whose creator said nothing.
3448
+ `loops.max_turns` carry none, so every insert states both. Client-authored administrative
3449
+ loops use the same composition.
3450
+
3451
+ §runtime-bookkeeping-policy **Runtime bookkeeping has no reviewer and cannot acquire
3452
+ new authority.** Its administrative loops explicitly state
3453
+ `{ attended: false, proposals: "reject" }`; this is a runtime invariant, not an
3454
+ interactive default. Generated reference publication and audit narration therefore
3455
+ do not depend on client policy configuration. Runtime-authored proposals do not
3456
+ use effect-policy auto-admission; bookkeeping proposals settle as failures through
3457
+ the ordinary proposal lifecycle, never wait for a client or auto-accept.
3422
3458
 
3423
3459
  §loop-policy-effective-read `loops.policy` persists one complete immutable
3424
3460
  `LoopPolicy`; every runtime policy read validates that snapshot before use.
@@ -3521,7 +3557,7 @@ No generator. SQLite-optimal: STRICT (3.37+), `INTEGER PRIMARY KEY` aliasing, ex
3521
3557
  | §db-maintenance-optimize Statistics at shutdown | The daemon's last database step before the caller closes SQLite is `PRAGMA optimize` on the writer (`maintenance_optimize`), so `sqlite_stat1` reflects tables the connection planned against, bounded by SQLite's own analysis limit; a failure there is a reported shutdown error, never silent. Retention runs before it under the operator's policy ({§retention-policy}) and ends with a WAL truncation ({§db-space-reclamation}); no periodic `ANALYZE` runs. |
3522
3558
  | §db-space-reclamation The daemon keeps its own file healthy | `PLURNK_SERVICE_AUTO_VACUUM` (`incremental` or `none`) names the mode the daemon keeps its file in. At start, before any drain, a database in another mode is converted (set the mode, one `VACUUM`, which rewrites the file and needs free disk about its size) and the journal says so with page counts before and after. Under `incremental`, every retention pass ends by stepping `PRAGMA incremental_vacuum` to completion once free pages reach `PLURNK_SERVICE_RECLAIM_MIN_FREE_BYTES` (0 = every pass), and reports `reclaimedPages`; below the floor, free pages stay for SQLite to reuse. Under `none` the file never shrinks and freed pages are reused. No operator step is involved beyond the knobs. The WAL stays bounded by SQLite's automatic checkpoint (#764). |
3523
3559
  | §content-store Every body is stored once | `contents` holds each settled body once, addressed by its SHA-256, however many channels, workspaces, forks or derivations carry it; rows are immutable. `entry_channel_rows` points a settled channel at its body and keeps an active stream's body as a private buffer until it settles, when it is interned. Every reader and writer uses the `entry_channels` view, whose `INSTEAD OF` triggers intern bodies, refuse a bound `content_hash` that is not the content's, and write each column group only when it changed, so a search attachment is never a representation write. SQLite counts no changes for a view, so a write that must know whether its channel exists returns the channel's name; an outer join cannot flatten the view, so the two statements that need one read `entry_channel_rows` and `contents` directly. `derivation_fts` is an external-content index over `derivation_texts` (a derivation joined to its body); `derivations.content_id` names the indexed text, and the triggers in `_entry-fts.sql` move the index with it and forget it on delete. A body no channel holds and no derivation indexes is collected by retention under `PLURNK_SERVICE_COLLECT_CONTENTS`. Witnesses: `test/intg/retention.test.ts`, `test/intg/entries.test.ts`, `test/intg/fulltext-index.test.ts`. |
3524
- | §retention-policy Retention is the operator's policy; information is kept by default | `Retention` (`src/server/Retention.ts`, statements in `Retention.sql`) reads ten knobs from `.env.defaults` once at daemon construction (the two storage knobs are {§db-space-reclamation}) and runs four set statements in dependency order — on `PLURNK_SERVICE_RETENTION_INTERVAL_MS` cadence while the daemon runs (0 = shutdown only) and once more at shutdown before `PRAGMA optimize`. `PLURNK_SERVICE_RETAIN_PACKET_TURNS` (-1 = every packet) and `PLURNK_SERVICE_RETAIN_PACKET_MS` (-1 = no age limit) retire a completed turn's packet composition (`turn_sections`, {§packet-items}) once it is beyond the newest N packet-bearing turns of its loop or older than the age; the turn, its bag, its log rows and its accounting stay, and an open turn is never retired. `PLURNK_SERVICE_COLLECT_PACKET_ITEMS` collects items no composition references. `PLURNK_SERVICE_COLLECT_CONTENTS` collects stored bodies nothing holds ({§content-store}), after the collectors that release them. `PLURNK_SERVICE_COLLECT_DERIVATIONS` collects derivations no channel, turn source, or log row cites — superseded editions — with their symbols (cascade) and their full-text shadow (`derivations_delete_fts`, a process trigger beside the FTS statements, on every delete path). `PLURNK_SERVICE_RETAIN_RESPONSE_TURNS` and `PLURNK_SERVICE_RETAIN_RESPONSE_MS` retire a settled call's response body (`model_call_responses`) once it is beyond the newest N body-bearing calls of its loop or its turn is older than the age; the call's identity, failure, capacity, admission and accounting stay, and the digest renders such a call request-only. The durable record is kept; packets and response bodies — transient evidence — age out, so a daemon left running for months stops growing (#788). A malformed knob refuses daemon construction. Witness: `test/intg/retention.test.ts`. |
3560
+ | §retention-policy Retention is the operator's policy; information is kept by default | `Retention` (`src/server/Retention.ts`, statements in `Retention.sql`) reads ten knobs from `.env.defaults` once at daemon start (the two storage knobs are {§db-space-reclamation}) and runs four set statements in dependency order — on `PLURNK_SERVICE_RETENTION_INTERVAL_MS` cadence while the daemon runs (0 = shutdown only) and once more at shutdown before `PRAGMA optimize`. `PLURNK_SERVICE_RETAIN_PACKET_TURNS` (-1 = every packet) and `PLURNK_SERVICE_RETAIN_PACKET_MS` (-1 = no age limit) retire a completed turn's packet composition (`turn_sections`, {§packet-items}) once it is beyond the newest N packet-bearing turns of its loop or older than the age; the turn, its bag, its log rows and its accounting stay, and an open turn is never retired. `PLURNK_SERVICE_COLLECT_PACKET_ITEMS` collects items no composition references. `PLURNK_SERVICE_COLLECT_CONTENTS` collects stored bodies nothing holds ({§content-store}), after the collectors that release them. `PLURNK_SERVICE_COLLECT_DERIVATIONS` collects derivations no channel, turn source, or log row cites — superseded editions — with their symbols (cascade) and their full-text shadow (`derivations_delete_fts`, a process trigger beside the FTS statements, on every delete path). `PLURNK_SERVICE_RETAIN_RESPONSE_TURNS` and `PLURNK_SERVICE_RETAIN_RESPONSE_MS` retire a settled call's response body (`model_call_responses`) once it is beyond the newest N body-bearing calls of its loop or its turn is older than the age; the call's identity, failure, capacity, admission and accounting stay, and the digest renders such a call request-only. The durable record is kept; packets and response bodies — transient evidence — age out, so a daemon left running for months stops growing (#788). An invalid policy withholds retention, including storage conversion and shutdown collection, and reports its configuration diagnostic without blocking the client ({§configuration-repair-path}). Storage I/O failures remain ordinary startup or shutdown failures. Witness: `test/intg/retention.test.ts`. |
3525
3561
 
3526
3562
  - DDL = storage truth; JSON Schemas = wire truth. They are allowed to differ where ergonomics demand.
3527
3563
  - §entry-identity-no-null **Identity components are never NULL.** `(workspace_id, scheme, authority, pathname)` is a unique key. `workspace_id` references the workspace directly with cascading deletion. Namespace schemes use empty authority; resource schemes retain their canonical authority. File members use nonempty `scheme="file"` and render as bare paths. Registration refuses `storedScheme: null`.
@@ -3667,11 +3703,14 @@ and is ignored rather than resolved against the working directory.
3667
3703
 
3668
3704
  | Class | Base | Plurnk member |
3669
3705
  |---|---|---|
3670
- | Configuration | `$XDG_CONFIG_HOME` (default `~/.config`) | `plurnk/.env`, `plurnk/AGENTS.md` |
3706
+ | Configuration | `$XDG_CONFIG_HOME` (default `~/.config`) | `plurnk/.env`, `plurnk/AGENTS.md`, plurnk-only MCP definitions `plurnk/mcp.json`, Agent Skills `plurnk/skills/<name>/SKILL.md`, and Agent Plugins `plurnk/plugins/<plugin>/` |
3671
3707
  | Durable user data | `$XDG_DATA_HOME` (default `~/.local/share`) | `plurnk/plurnk.db` and SQLite sidecars |
3672
3708
  | Persistent operational state | `$XDG_STATE_HOME` (default `~/.local/state`) | On-demand workspace/module directories ({§module-workspace-directory}). |
3673
3709
  | Reproducible cache | `$XDG_CACHE_HOME` (default `~/.cache`) | Reserved; no directory is created without an owned artifact. |
3674
3710
  | Shared global Agent Skills | User home | `.agents/skills/<name>/SKILL.md` |
3711
+ | Shared global MCP definitions | User home | `.agents/mcp.json` |
3712
+ | Shared global Agent Plugins | User home | `.agents/plugins/<plugin>/` ({§agent-plugins-hosting}) |
3713
+ | A plugin's `PLUGIN_DATA` | `$XDG_DATA_HOME` | `plurnk/plugins/<plugin>/` |
3675
3714
 
3676
3715
  §state-root **A private daemon has one root.** `PLURNK_SERVICE_STATE_ROOT` (absolute; a leading
3677
3716
  `~/` expands; a relative value fails hard by name) replaces the data, state, cache and runtime homes
@@ -3693,15 +3732,32 @@ ordinary precedence; XDG variables themselves require absolute paths.
3693
3732
  |---------:|------------------------------------|-----------------------------------------------------------|
3694
3733
  | 1 | Assembled package `.env.defaults` | Set-if-unset floor; one owner per key. |
3695
3734
  | 2 | `$XDG_CONFIG_HOME/plurnk/.env` | User-level ambient configuration. |
3696
- | 3 | `./.env` | Working-directory ambient configuration. |
3697
- | 4 | `--config=<path>` | Singular service-owned explicit file. |
3698
- | 5 | `--env-file*` | Repeatable explicit files; later selected files win. |
3699
- | 6 | Initial shell environment | Preserved over every file layer. |
3700
- | 7 | Derived service CLI flags | Assigned last. |
3735
+ | 3 | `--config=<path>` | Singular service-owned explicit file. |
3736
+ | 4 | `--env-file*` | Repeatable explicit files; later selected files win. |
3737
+ | 5 | Initial shell environment | Preserved over every file layer. |
3738
+ | 6 | Derived service CLI flags | Assigned last. |
3739
+
3740
+ A working directory's `.env` configures that directory's application, never plurnk (#926); a
3741
+ project's variables reach its commands through the workspace environment ({§workspace-env}).
3701
3742
 
3702
3743
  Node's pre-script env-file form and the executable's post-script form share the same later-file-wins ordering. `--env-file-if-exists` skips an absent file without changing the order of selected files.
3703
3744
 
3704
- §operator-config-env-defaults **Every package owns its knobs — `.env.defaults` is the standard.** Each package in the daemon's ecosystem — internal or third-party — ships a `.env.defaults` at its package root declaring its own knobs; the file is the package's configuration reference, traveling in the tarball and changing with the code that reads it. At boot the daemon assembles every installed member's file into one floor (membership = the `@plurnk/*` scope or a `plurnk` package.json field, gated by `PLURNK_PLUGINS_TRUSTED_ONLY` with discover()'s exact semantics) and applies it set-if-unset under every operator source. `plurnk-service config defaults` renders the same complete, owner-labelled aggregate to stdout on demand, preserving comments and optional declarations without persisting a second copy or exposing effective secret values. A key claimed by two packages fails boot naming both. With the reader-declares discipline, each key has one implementation and one defaults owner.
3745
+ §operator-config-env-defaults **Every package owns its knobs — one assembled floor.**
3746
+
3747
+ | Source | Panel | Admission |
3748
+ |---|---|---|
3749
+ | Platform capability package | `.env.defaults` at the package root | `@plurnk/*` or a `plurnk` package field; {§plugin-trust-boundary} |
3750
+ | Agent Plugin native extension | `ai.plurnk/.env.defaults` | The winning daemon-wide plugin in {§agent-plugins-hosting}, a valid native declaration, and the same trust gate |
3751
+
3752
+ Root and trust flags apply before collection. A project plugin contributes no native panel.
3753
+ Non-module native panels follow their npm-only family discovery ({§plugin-manifest-read});
3754
+ a plain-folder declaration does not suppress an installed capability's panel.
3755
+ The file travels with its code and is its configuration reference. All admitted files compose
3756
+ one floor, applied set-if-unset beneath operator sources. `plurnk-service config defaults`
3757
+ renders those same owner-labelled files, preserving comments and optional declarations without
3758
+ persisting another copy or exposing effective values. Duplicate key ownership fails naming both
3759
+ owners. Invalid optional native panels are diagnosed and prevent that extension from loading;
3760
+ they do not block the remaining floor or the repair path ({§configuration-repair-path}).
3705
3761
 
3706
3762
  §operator-config-only-home **The cascading environment is the only home for a choice.** The principle and its reasons are ARCHITECTURE.md's (*Configuration authority*); this is what `scripts/env-surface-policy.mjs` enforces in `root:lint`, over the source Git tracks:
3707
3763
 
@@ -3733,6 +3789,34 @@ or provider request. The seeded `.env`, first-run diagnostic, service help, and
3733
3789
  missing-model recovery all signpost `plurnk-service config defaults` as the
3734
3790
  complete installed option catalog.
3735
3791
 
3792
+ §operator-config-offline-validation **`config check` and runtime use the same
3793
+ owning configuration readers, with different failure boundaries.** An offline
3794
+ check rejects invalid configuration with a nonzero exit. Runtime contains
3795
+ optional-family errors according to {§configuration-repair-path}. Failure
3796
+ names the offending variable or file/entry and retains its cause.
3797
+
3798
+ | Owner | Offline validation |
3799
+ |---|---|
3800
+ | Core | Model selection, file-creation/effect/loop policy, members definitions and controls, skill-fetch settings and root selection |
3801
+ | MCP | Whole definitions from the environment and selected files (cwd is the project for this check), future-alias controls, catalog settings, timeouts, retry pacing and registry URL |
3802
+ | A2A | Whole outbound definitions and controls, timeout/diagnostic bounds, configured inbound exposure |
3803
+ | Schedule | Whole definitions and controls, recurrence syntax, time zone and preview count |
3804
+ | Hooks | Command/argument/event configuration and delivery bounds |
3805
+
3806
+ Disabled definitions and controls without a resource are validated, not skipped.
3807
+ Checking creates no database, starts no process or listener, arms no schedule,
3808
+ and contacts no provider or endpoint. Symbolic credential references remain
3809
+ symbolic: availability belongs to workspace preparation, not offline validation.
3810
+
3811
+ First-run seeding publishes the complete private configuration directory atomically.
3812
+ Concurrent initializers adopt the winning seed; a failed initializer removes only
3813
+ its own staging directory. An existing operator directory is never reseeded.
3814
+
3815
+ §systemd-user-unit The service package ships `plurnk.service` as an example
3816
+ systemd user unit. Installation and enablement are explicit operator actions;
3817
+ package installation performs neither. The template documents executable-path
3818
+ and environment adjustments instead of introducing a service-management command.
3819
+
3736
3820
  Model selection uses one selector vocabulary in `ProviderRegistry` ({§provider-instantiation}). `PLURNK_MODEL_<alias>=<provider>/<model-id>` optionally declares a friendly route and tuning scope; `PLURNK_MODEL=<selector>` selects either that alias or an exact provider/model route. `PLURNK_MODEL_CHILD=<selector>` uses the same vocabulary for the default child provider; unset means inherit the spawning loop's provider. Operator selections and alias declarations live in `.env`, not `.env.defaults`.
3737
3821
 
3738
3822
  Each knob's value lives on its panel and nowhere else (`plurnk-service config defaults` prints them all); this table says what the service's knobs mean.
@@ -3853,7 +3937,7 @@ the policy renders in exactly one packet section. Every other tier runs the
3853
3937
  test cascade, so shipped-default regressions are otherwise invisible by
3854
3938
  construction.
3855
3939
 
3856
- §operator-config-real-model-profile **Real-model gate profile.** `plurnk-core/.env.test` is committed source and is the single shared profile for live, demo, and the candidate daemon used by benchlets. Live/demo load it after operator files; the candidate daemon loads it below its inherited environment. Direct shell/benchmark overrides win in both paths. Its exact allowlist is limited to gate-wide service posture that is identical on every machine: complete catalog orientation, automatic Git membership when the operator ceiling permits Git, ambient operator-file docs/packet notes cleared, ambient MCP selections and schedules disabled, and `PLURNK_EXECS_QUESTION=0` for unattended runs. The ordinary executor switch removes the question tool and its teaching; an explicit override can opt into an attended drill. Configuration with a narrower or variable owner stays outside it:
3940
+ §operator-config-real-model-profile **Real-model gate profile.** `plurnk-core/.env.test` is committed source and is the single shared profile for live, demo, and the candidate daemon used by benchlets. Live/demo load it after operator files; the candidate daemon loads it below its inherited environment. Direct shell/benchmark overrides win in both paths. Its exact allowlist is limited to gate-wide service posture that is identical on every machine: complete catalog orientation, automatic Git membership when the operator ceiling permits Git, ambient operator-file docs/packet notes cleared, the operator's installed skills and plugins left unread ({§agent-roots}), ambient MCP/A2A and schedules default disabled, and `PLURNK_EXECS_QUESTION=0` for unattended runs. The ordinary executor switch removes the question tool and its teaching; an explicit override can opt into an attended drill. The drivers also project per-alias `ENABLED=0` overrides for named MCP, A2A, schedule, membership, and skill resources in the operator's config file. Definitions remain inspectable; explicit shell/benchmark controls win. Mock-tier bootstrap clears these ambient families before loading its fixture floor. No operator file is rewritten. Configuration with a narrower or variable owner stays outside it:
3857
3941
 
3858
3942
  | Owner | Configuration |
3859
3943
  |---|---|
@@ -3936,36 +4020,55 @@ proceeds. `setup` is the readiness boundary for every capability registered
3936
4020
  with Core: recovery may demand a workspace provider before `start`. For a
3937
4021
  pre-bound client interface, requests remain unavailable until `start`; every
3938
4022
  other module opens its module-owned exterior ingress only after recovery. No
3939
- registered capability may depend on exterior ingress. Shutdown begins started
3940
- and self-closing module closure in reverse order and surfaces aggregated close
3941
- failures.
3942
-
3943
- §module-discovery **Third-party daemon-module composition is manifest
3944
- discovery.** A package declares `plurnk: { kind: "module", module:
3945
- "<export-subpath>" }`; the export is one DaemonModule (an object, or a no-arg
3946
- factory returning one). At boot, core scans installed packages under the
3947
- executor family's discovery and trust rules ({§plugin-discovery}) and
3948
- registers every trusted declaring module before any module setup runs, in
3949
- package-name order. The service's explicit composition — the AG-UI,
3950
- hooks, and MCP modules — carries init options and is wired in service.ts;
3951
- discovery never duplicates those packages. An untrusted declaring package is
3952
- skipped with a boot warning, never executed. A module export that is neither
3953
- an object nor a no-arg factory, a factory returning a non-object, or an object
3954
- with a non-function lifecycle member fails boot loudly.
4023
+ registered capability may depend on exterior ingress. A module, and any distinct
4024
+ lifetime object returned by `start`, may implement the following phases:
4025
+
4026
+ | Phase | Obligation |
4027
+ |---|---|
4028
+ | `stop()` | Reject new ingress, stop timers and other producers, and settle owned work that can emit core events. Keep event subscriptions and resources needed for settlement alive. |
4029
+ | `close()` | Unsubscribe observers and release remaining resources after producer settlement; await admitted notification deliveries. Do not start new core work. |
4030
+
4031
+ Both phases are optional and idempotent; repeated calls join the same work.
4032
+ Core tracks a module before `setup` so partially acquired resources are released
4033
+ even if setup fails. A returned object identical to its module is tracked once.
4034
+
4035
+ §module-discovery **Daemon modules compose through their shared lifecycle.**
4036
+
4037
+ | Source | Declaration | Lifetime |
4038
+ |---|---|---|
4039
+ | Platform capability package | `package.json#plurnk` with `kind: "module"` and `module` | Daemon-wide |
4040
+ | Agent Plugin | `plugin.json#extensions.ai.plurnk` with `kind: "module"` and a `module` path under `ai.plurnk/` | Daemon-wide; npm and selected user roots only |
4041
+ | Project Agent Plugin | Portable components only | Workspace-scoped; native code is not imported |
4042
+
4043
+ The export is one DaemonModule object or no-argument factory. Standard bundles follow
4044
+ {§agent-plugins-hosting} source order, then other installed module packages load in package-name
4045
+ order. All trusted modules register before setup. The service's explicit AG-UI, hooks and MCP
4046
+ composition is never duplicated. Untrusted modules are reported and not imported. Invalid
4047
+ declarations, unavailable module files and configuration errors during construction are diagnosed
4048
+ at the affected native extension; healthy siblings remain available. A factory validates startup
4049
+ configuration before `setup` acquires resources. Failures after registration begins follow
4050
+ {§module-lifecycle} cleanup, not a partial-registration fallback.
4051
+ An invalid module object, factory result or lifecycle member is an implementation contract failure,
4052
+ not configuration, and fails loudly. Native capabilities register through their owning public
4053
+ interfaces and release registrations during {§module-lifecycle} resource teardown.
3955
4054
 
3956
4055
  §module-shutdown-order `Daemon.stop()` first rejects new capability demand and
3957
- aborts proposals, branches, derivations, and worker scopes. It simultaneously
3958
- begins every module closer in reverse registration order, allowing exterior
3959
- listeners to stop accepting work while active requests observe those
3960
- cancellations. It then settles branches, drains, module closers, streaming
3961
- producers, derivations, mimetypes, and schemes before its final worker-settlement
3962
- barrier. The supervisor owns each asynchronous cancellation and wake task from
4056
+ aborts proposals, branches, derivations, and worker scopes. It begins module
4057
+ `stop()` calls in reverse registration order without serially awaiting them,
4058
+ so every producer is asked to stop even if another stalls. Core settles drains,
4059
+ capability publications, module producers, streaming producers, derivations,
4060
+ mimetypes, schemes, and the final worker-settlement barrier while observers
4061
+ remain subscribed. Only then does it begin and join module `close()` calls in
4062
+ reverse order. Failures do not skip later phases and join one shutdown aggregate.
4063
+ The supervisor owns each asynchronous cancellation and wake task from
3963
4064
  acceptance through settlement, including immediately acknowledged and explicitly
3964
4065
  awaited cancellation; a task failure participates in the shutdown aggregate.
3965
4066
  After asynchronous selection, the supervisor rechecks shutdown before creating
3966
4067
  a drain or installing a timer; parked-loop wake mutations also recheck worker
3967
4068
  cancellation under {§worker-lifecycle-durable-disposition}.
3968
- The database may be released only after the final settlement barrier resolves.
4069
+ The database remains available through observer closure and final maintenance.
4070
+ The shared deadline bounds every phase, including observer delivery; forced
4071
+ shutdown may therefore lose notifications and reports the unfinished phase.
3969
4072
 
3970
4073
  §crash-only-stop The settle sequence is deadline-bounded
3971
4074
  (`PLURNK_SERVICE_STOP_TIMEOUT_MS`, default 30000): past the deadline each wait
@@ -3979,21 +4082,22 @@ backstop, not the exit.
3979
4082
  ```mermaid
3980
4083
  flowchart LR
3981
4084
  stop[Begin stop] --> abort[Abort core producers]
3982
- stop --> moduleClose[Begin reverse module closure]
4085
+ stop --> moduleStop[Begin reverse module stop]
3983
4086
  abort --> drains[Settle worker drains]
3984
- drains --> joined[Settle module closures]
3985
- moduleClose --> joined
4087
+ drains --> joined[Settle module producers]
4088
+ moduleStop --> joined
3986
4089
  joined --> producers[Settle streaming producers]
3987
4090
  producers --> resources[Dispose derivations,<br/>mimetypes, and schemes]
3988
4091
  resources --> settlement[Settle cancellations and wakes]
3989
- settlement --> database[Release database]
4092
+ settlement --> observers[Close observers and resources]
4093
+ observers --> database[Maintain and release database]
3990
4094
  ```
3991
4095
 
3992
4096
  | Setup function | Contract |
3993
4097
  |---|---|
3994
4098
  | `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. |
3995
4099
  | `registerScheme(name, handler)` | Adds one process-wide addressable scheme handler; scheme readiness and model-facing capability publication remain core-owned. |
3996
- | §module-action-registration `registerModuleAction({ name, scope, inputSchema, outputSchema, handler })` | Adds one non-empty, extension-unique action with resolvable JSON Schemas. `scope` is exactly `worldless`, `workspace`, or `worker`; the handler receives schema-validated params and a separate matching context. Scoped contexts contain trusted bound identifiers, never client parameters. A client-interface module decides whether and how the name becomes public, validates successful output, and owns collisions with its built-ins. |
4100
+ | §module-action-registration `registerModuleAction({ name, scope, residency, inputSchema, outputSchema, handler })` | Adds one non-empty, extension-unique action with resolvable JSON Schemas. `scope` is exactly `worldless`, `workspace`, or `worker`; the handler receives schema-validated params and a separate matching context. `residency` is explicitly `required` or `none`: only the former acquires workspace capabilities and reconciles worker documents. Worldless actions require `none`. Scoped contexts contain trusted bound identifiers, never client parameters. A client-interface module decides whether and how the name becomes public, validates successful output, and owns collisions with its built-ins. |
3997
4101
  | §module-workspace-provider `registerWorkspaceCapabilityProvider(namespaceOwner, provider)` | Registers one extension-unique Functionality provider. `activate({ workspaceId, retain })` reconstructs the workspace snapshot; idempotent `deactivate({ workspaceId })` releases process resources. Core coalesces demand and supplies residency leases for work that outlives its caller. |
3998
4102
  | §module-workspace-state `readWorkspaceModuleState(workspaceId, namespaceOwner)` | Reads one nullable JSON state value per workspace and provider. Core owns storage and lifecycle; the provider owns its schema. Store symbolic credential references, not copied secrets. A worker-scoped family's coordinator reads and replaces the same shape per worker in `worker_module_state` ({§functionality-scope}). |
3999
4103
  | `readWorkspaceEnvironment(workspaceId)` | Captures the workspace env layer ({§workspace-env}) and returns its composer. No argument uses admitted host values; a supplied environment supplies a module's reference-resolution context. Both apply the same captured values and masks, without worker overrides. |
@@ -4043,7 +4147,7 @@ A conflicting alias is rejected explicitly; it never produces a hidden second
4043
4147
  definition for the submitting client or worker.
4044
4148
 
4045
4149
  §module-workspace-residency **Persistence is not residency.** Model execution,
4046
- capability-aware operations, scoped module actions, and retained provider work
4150
+ capability-aware operations, module actions declaring required residency, and retained provider work
4047
4151
  lease the workspace's Functionality. Boot, workspace or worker creation,
4048
4152
  attachment, listing, naming, idle clients, and parked state alone do not.
4049
4153
  After the last lease releases, `PLURNK_SERVICE_WORKSPACE_WARM_MS` and
@@ -4073,10 +4177,10 @@ registry. Deleting the workspace cascades its state; worker lifecycle does not.
4073
4177
  ## Workspace Functionality
4074
4178
 
4075
4179
  §functionality-coordinator **One coordinator owns the common lifecycle.**
4076
- Agent Skills, MCP, outbound A2A agents, and membership are adapters beneath
4180
+ Functionality families are adapters beneath
4077
4181
  `list | discover | add | enable | disable | remove`. State and mutations
4078
- serialize per workspace and family. Client actions
4079
- `workspace.<family>.<verb>` and model manager executors invoke the same
4182
+ serialize per workspace and family. Scope-bound client actions
4183
+ `workspace.<family>.<verb>` / `worker.<family>.<verb>` and model manager executors invoke the same
4080
4184
  coordinator. Families do not invent another management grammar, proposal
4081
4185
  policy, or hotload path.
4082
4186
 
@@ -4084,12 +4188,98 @@ Retryability describes the actual failed condition, not its numeric status.
4084
4188
 
4085
4189
  | Verb | Common contract |
4086
4190
  |---|---|
4087
- | `list` | Project definitions, origin, enabledness, and preparation outcome: disabled, active, unavailable with its Problem, or authorization-required. No credential values. |
4191
+ | `list` | Project definitions, ownership (`origin`), winning configuration source (`provenance`), enabledness, and published preparation outcome: disabled, dormant, active, unavailable with its Problem, or authorization-required. No credential values. |
4088
4192
  | `discover` | Return inert candidates. Never install, persist, enable, or execute them. |
4089
- | `add` | Admit and persist a workspace definition, prepare it, and enable it atomically. It may override the service baseline. Reapplying the same workspace definition enables it idempotently (200); a different definition for that alias fails 409 without replacing it. |
4193
+ | `add` | Admit and persist a local definition, prepare it, and enable it atomically. It may override an inherited definition. Reapplying the same local definition enables it idempotently (200); a different local definition for that alias fails 409 without replacing it. |
4090
4194
  | `enable` | Publish an available definition; retry preparation if unavailable. |
4091
4195
  | `disable` | Withdraw live capability; retain its definition and saved results. |
4092
- | `remove` | Disable and forget the workspace definition. A same-alias service baseline reappears disabled. Service definitions are disable-only. Saved results remain. |
4196
+ | `remove` | Forget the locally owned definition and its enabledness override. Restore any inherited definition with its inherited enabledness. Inherited definitions cannot be removed at this scope. Saved results remain. |
4197
+
4198
+ §configuration-definition-resolution **Named resource definitions replace whole;
4199
+ independent behavior controls remain independent.** Source readers and scope
4200
+ overlays apply the same boundary:
4201
+
4202
+ | Value | Resolution |
4203
+ |---|---|
4204
+ | Named definition | Select the complete definition from the highest-precedence source or scope declaring that alias. Omitted fields, arrays and nested objects never inherit from a lower definition. |
4205
+ | Environment resource declaration | Use {§resource-environment}: absence inherits, an empty definition is invalid, and an explicit enabledness switch disables without erasing the definition. |
4206
+ | Definition validity | Validate the selected definition against its family's schema. Missing required fields are errors, not requests to fill from a lower definition; rejected live changes preserve the previous publication. |
4207
+ | Independently declared behavior control | Resolve its own value through its cascade. An enabledness override does not copy or patch the definition it controls. |
4208
+ | Local definition removal | Remove this scope's definition and enabledness override. Restore the current inherited definition and enabledness, or leave no entry if none exists. Do not persist a replacement or disabling mask; subsequent inherited changes remain effective. Restoration follows the same preparation and publication failure policy as other mutations ({§functionality-publication}). |
4209
+
4210
+ §configuration-provenance **Inspection names the winning definition's input, not
4211
+ its owner or runtime.** `provenance` uses the same `{kind, source, reference?}`
4212
+ shape as discovery candidates. Source readers contribute it; the coordinator
4213
+ preserves it through inheritance, enabledness changes, and every readiness state.
4214
+
4215
+ | Definition source | Inspection |
4216
+ |---|---|
4217
+ | Assembled environment | `kind: environment`, `source`: exact definition key; never its value or an inferred dotenv filename. |
4218
+ | Discovered skill root | `kind: file`, `source`: the winning `SKILL.md` path. |
4219
+ | Standalone MCP file | `kind: file`, `source`: the winning `mcp.json` path; `reference`: its entry's JSON Pointer. |
4220
+ | Local workspace/worker definition or host-provided tree with no configuration input | No fabricated provenance; `origin` identifies ownership and the family definition describes the resource. |
4221
+ | Local override | Replaces inherited provenance with the local definition; removal restores the current inherited provenance. |
4222
+
4223
+ Only the winning definition's source is reported. Shadowed definitions, secrets,
4224
+ and environment-file loading history are not tracked. Source metadata is derived
4225
+ on inspection, not persisted in the local overlay or used as runtime identity.
4226
+
4227
+ §configuration-repair-path **Invalid optional configuration cannot remove the
4228
+ agent's repair environment.** Capability owners reject typed operator input
4229
+ errors. The launcher and shared coordinator contain them at their respective
4230
+ composition boundaries, not arbitrary exceptions:
4231
+
4232
+ | Boundary | Outcome |
4233
+ |---|---|
4234
+ | Optional startup integration (hooks, hosted A2A, observability) cannot be configured | Withhold that integration and retain its exact configuration diagnostic. Activate the client interface and unrelated capabilities; never invent a replacement setting. |
4235
+ | Invalid default model or child selector | Retain its diagnostic at startup; keep client inspection and explicit selection available. A request relying on the invalid selector fails with `daemon:configuration/configuration-invalid` (503), naming its key. Never substitute another model or silently inherit a child model. |
4236
+ | Model construction or endpoint verification fails | Reject selection/use before inference or committing the selection. Keep the client available; a later selection can retry or choose another route. |
4237
+ | Invalid effect, file-creation, or loop-default policy | Retain the startup diagnostic. Reject the affected operation or unresolved loop policy as `daemon:configuration/configuration-invalid` (503); no guessed admission policy, external effect, or orphan approval wait. Independent operations and explicitly supplied valid loop policies remain usable. |
4238
+ | Invalid retention configuration | Withhold collection and automatic storage conversion, including shutdown collection. Preserve stored evidence and expose the diagnostic; do not substitute a deletion policy. |
4239
+ | Invalid packet configuration or retired capacity knobs | Retain the startup diagnostic and keep client inspection available. Reject affected packet construction before inference with `daemon:configuration/configuration-invalid` (503). Never guess a capacity or projection setting. |
4240
+ | Invalid executor construction/probe configuration | Keep the installed declaration and its diagnostic, but no executable instance or output scheme. Other executors and ordinary READ/EDIT remain usable. Invoking the unavailable tag returns its exact 503 configuration Problem. |
4241
+ | Invalid execution scheduling, input, or scratch configuration | Diagnose at startup and validate on the affected execution path before admission, stream creation, or external effects. Scratch configuration applies only to resource-backed execution; inline programs remain independent. Never substitute concurrency, timeout, or directory settings. |
4242
+ | Invalid model-alias catalog | Fail catalog inspection explicitly; omit its unavailable snapshot rather than report an empty catalog. Exact provider/model selection does not depend on aliases. |
4243
+ | Offline `config check` | Validate the same inputs without activating integrations; an invalid setting remains a nonzero failure. |
4244
+ | Family configuration cannot be resolved | Keep its manager available, identify the configuration failure in its generated documentation, and preserve durable definitions. Do not publish the family's operational capabilities or pretend its catalog is empty. Other families and ordinary model work remain usable. |
4245
+ | Inspection or mutation of an unresolved family | Return the exact configuration Problem, naming the key and required correction, through both client and model paths. No silent source fallback or change to stored settings. |
4246
+ | Invalid live mutation | Reject atomically and preserve the preceding publication. |
4247
+ | Previously valid family becomes invalid | Withdraw its operational capabilities at normal publication, then release the old snapshot. Keep the manager and diagnostic. |
4248
+ | Configuration source is corrected or removed | Inspection resolves the current source immediately, even in the repairing turn; a previous source-resolution error cannot override a successful read. Not-yet-published definitions remain dormant. Normal publication restores capabilities; inspection does not activate them. |
4249
+ | Preparation configuration is corrected | The published preparation failure remains until ordinary preparation succeeds. Successful source inspection alone does not establish runtime readiness. Environment-file edits follow their ordinary process lifetime, not an implicit reload. |
4250
+ | Internal invariant, state, or implementation failure | Preserve the exception; never reclassify it as an operator configuration error. |
4251
+
4252
+ Client discovery and passive synchronization report startup diagnostics through the
4253
+ existing Notice channel, even without a usable model. The first turn of a drain, and a changed diagnostic
4254
+ thereafter, reports unresolved configuration to both client and model. An
4255
+ unchanged diagnostic is not repeated every turn. Operation failures remain Problems.
4256
+
4257
+ §functionality-inspection **Inspection is not demand.** `list` and `discover` do not
4258
+ acquire residency, join preparation, reconcile worker documents, or extend warm
4259
+ retention. An enabled definition no resident publication has prepared is `dormant`: every one while
4260
+ the family is cold, and one that arrived or changed out of band until the next turn publishes it
4261
+ ({§functionality-hotload}). During replacement the preceding publication remains authoritative;
4262
+ the candidate is never presented as active. A published outcome belongs to the
4263
+ complete definition prepared, not merely its alias. Cooling leaves durable definitions
4264
+ inspectable. Mutations and protocol continuations retain their residency rules.
4265
+
4266
+ §functionality-preparation-visibility **Preparation is workspace activity, not
4267
+ model context.** The coordinator owns a current `FunctionalityPreparationActivity`
4268
+ per preparing family. `workspacePreparationStatus(workspaceId)` returns that same
4269
+ state without demand; `workspace/preparation` broadcasts
4270
+ `{ workspaceId, preparation: [...] }` whenever it changes.
4271
+
4272
+ | Boundary | Visible state |
4273
+ |---|---|
4274
+ | Preparation begins | Family, `phase: preparing`, `alias: null`, UTC `since` |
4275
+ | Adapter calls `progress(alias)` | Enabled alias being prepared; a fresh `since` |
4276
+ | Prepared candidate enters publication | `phase: publishing`, `alias: null` |
4277
+ | Commit, rejection, or rollback settles | Family removed; empty array means no preparation |
4278
+
4279
+ Preparation reports neither definitions nor credentials, does not alter the
4280
+ publication contract, and creates no log entries or model Notices. Published
4281
+ failures retain their exact Problems in `list`. Concurrent consumers share the
4282
+ workspace activity; a client disconnect does not clear another consumer's work.
4093
4283
 
4094
4284
  §functionality-adapter **An adapter owns protocol truth.** It declares its
4095
4285
  family, namespace owner, definition schema, contributed defaults, discovery,
@@ -4099,16 +4289,66 @@ family declares, at admission, in the service projection, and on persisted
4099
4289
  state, so an environment variable's name is an alias exactly as a skill name
4100
4290
  is. Admission distinguishes explicit client
4101
4291
  actions from model operations where the family contract requires it
4102
- ({§members-model-scope}). Preparation returns runtimes, documents, per-alias
4292
+ ({§members-model-scope}). Preparation receives each complete definition with optional
4293
+ adapter-owned interpretation context. Context is source semantics, not policy or provenance;
4294
+ it participates in runtime identity and hot-load comparisons, is never projected as configuration
4295
+ or persisted into a workspace override, and cannot survive replacement by a local definition.
4296
+ Removing that override restores the current inherited definition and context together.
4297
+ Descriptive provenance alone does not change runtime identity. Preparation returns runtimes, documents, per-alias
4103
4298
  outcomes, and a snapshot with `commit`/`abort`. Successful publication commits;
4104
4299
  failure aborts; cooling tears down. Protocol continuations remain ordinary
4105
4300
  module actions. Optional `forget` releases an installed or provisioned
4106
- definition before removal; failure rejects removal ({§skills-remove}). The
4301
+ definition before removal; failure rejects removal. The
4107
4302
  seam's shapes — the identity a verb acts under, its options, definition
4108
4303
  sources, outcomes, preparation, the prepared result and the family handle —
4109
4304
  are declared once in `plurnk-contracts` and imported by core and every
4110
4305
  module; core adds only its own face of the seam, the runtime registration a
4111
4306
  resident family prepares and the scheme facet it may expose.
4307
+ An adapter may expose current partial-source `configurationNotices`; these join the ordinary
4308
+ workspace diagnostics without preventing independently valid definitions from preparing.
4309
+
4310
+ §functionality-hotload **Out-of-band state is admitted before the next turn.** An adapter whose
4311
+ `available` reads state that changes outside the daemon, such as skill roots ({§skills-hotload}) or
4312
+ installed plugins ({§agent-plugins-hosting}), implements `refreshIfChanged`. Turn admission calls it
4313
+ for every family under the workspace gate before packet assembly. The family handle's `refresh` with
4314
+ `ifChanged` republishes a resident family only when the enabled definitions it would prepare differ
4315
+ from the ones its publication prepared. The coordinator makes that comparison because it alone knows
4316
+ what it published, so a change `list` saw first is still published at the next turn. A family whose
4317
+ definitions do not capture its published content, such as a skill's files, republishes
4318
+ unconditionally when that content changed. An unchanged family dispatches nothing.
4319
+
4320
+ §agent-plugins-hosting **Installed Agent Plugins are found like skills.** A workspace's plugins are
4321
+ the immediate child directories of its project's `.agents/plugins`, then
4322
+ `$XDG_CONFIG_HOME/plurnk/plugins` (plurnk alone), then `~/.agents/plugins` (every agent), loaded and
4323
+ validated by `@plurnk/plurnk-agent-plugins` ({§agent-plugins-roots}), followed by standard plugin
4324
+ bundles in the installed npm graph. An earlier source shadows a later plugin of the same manifest
4325
+ name, regardless of distribution or directory name. Native discovery uses that same cascade with
4326
+ the project root omitted; workspace discovery includes it. A plugin's `PLUGIN_DATA` is
4327
+ `$XDG_DATA_HOME/plurnk/plugins/<name>/<sha256(canonical-root)>`, kept across in-place updates and
4328
+ moved with a state root ({§state-root}). Distinct installations never share data by name alone;
4329
+ workspaces referencing the same canonical installation share its data. Modules receive a workspace's plugins,
4330
+ in precedence order, through the setup seam's `readWorkspacePlugins`, with one signature that changes
4331
+ exactly when a plugin, its manifest, its MCP configuration, or its skills change
4332
+ ({§functionality-hotload}), and the roots the workspace has. This read-only source loader does
4333
+ not install or delete plugins. MCP's own lifecycle is independent ({§mcp-definitions}).
4334
+
4335
+ Portable components use each family's existing management and publication path. Within a source
4336
+ scope, standalone definitions precede bundled components; nearer scopes precede farther scopes,
4337
+ with npm last. Complete environment definitions override those inputs, followed by workspace
4338
+ definitions and enabledness. Inspection names the winning component file as plugin provenance.
4339
+ Removing a workspace override restores inheritance; it never deletes the installed bundle.
4340
+ Current plugin-source diagnostics join the workspace's configuration notices before inference.
4341
+
4342
+ §agent-roots **A daemon reads the roots `PLURNK_SERVICE_ROOTS` names.** A comma list drawn from
4343
+ `project`, `plurnk` and `global`, nearest first, selects which Agent Skills, Agent Plugins and standalone MCP roots a
4344
+ daemon reads; the default names all three. The real-model gate profile selects
4345
+ its discovery roots explicitly ({§operator-config-real-model-profile}), so
4346
+ operator roots do not shape a gate. Root selection does not restrict explicit
4347
+ source definitions. Skills mutations change workspace bindings, not these roots
4348
+ ({§skills-functionality}); MCP mutations likewise remain workspace-owned. The module setup seam's
4349
+ `workspaceConfigurationDirectories` supplies selected `<project>/.agents`,
4350
+ `$XDG_CONFIG_HOME/plurnk`, and `~/.agents` directories in precedence order, omitting
4351
+ project when no project is bound. Modules own their file formats; core owns discovery roots.
4112
4352
 
4113
4353
  An adapter may expose a `scheme` facet beneath its family's runtime namespace
4114
4354
  ({§runtime-resource-binding}). A facet claims a path subtree and is the scheme's
@@ -4128,9 +4368,10 @@ operator's ceiling ({§exec-env-scoped}, service origin) precede workspace defau
4128
4368
  worker overrides ({§workspace-env}). `add` takes
4129
4369
  the name as the alias and `{ "value": "…" }` as the definition, used verbatim with no
4130
4370
  interpolation. `disable` withholds a name in the selected scope while retaining it;
4131
- `remove` forgets a locally-owned entry and a same-name lower baseline reappears
4132
- disabled, so removal never silently changes what the next spawn sees. Definitions
4133
- from a lower layer are disable-only in the current scope.
4371
+ `remove` forgets a locally-owned entry and restores any same-name inherited value
4372
+ and enabledness ({§configuration-definition-resolution}). Use `disable` to keep
4373
+ an inherited name out of subsequent launches. Definitions from a lower layer
4374
+ cannot be removed in the current scope.
4134
4375
 
4135
4376
  `list` projects effective values with their origin. Values are shown: the ceiling is the security
4136
4377
  boundary, not the projection, and any admitted name is already readable by every command the
@@ -4539,6 +4780,7 @@ adding a loop to it. LOOK text anchors resolve through the same
4539
4780
  | §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}. |
4540
4781
  | §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. |
4541
4782
  | §notifications-workspace-created `workspace/created` | `{ id, name, projectRoot }` | A workspace is created. This is the only current global event. |
4783
+ | `workspace/preparation` | `{ workspaceId, preparation: FunctionalityPreparationActivity[] }` | Workspace capability preparation changes; snapshot and clearing semantics follow {§functionality-preparation-visibility}. |
4542
4784
  | §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 initiating actor used for conversation routing, never entry ownership or access control. `target` is the canonical resource URI. Optional numeric coordinates identify the causal log item, independently of that URI. 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 by canonical workspace address. |
4543
4785
  | §notifications-stream-concluded `stream/concluded` | `{ entryId, workerId, target, subscriptionId, scheme, result, summary, wakeAction, loop_seq?, turn_seq?, sequence? }` | A subscription closes. `workerId` identifies the initiating actor; `target` is the canonical resource URI. Optional numeric fields identify the causal log item, never parsed from `target`. Exact result truth is preserved. `wakeAction` reports `wake-pending` before settlement, `no-op-active-loop` when work is already executing, `no-loop`, or `skipped-aborted`/`skipped-cancelled` for an aborted worker scope. A pending wake predicts neither execution nor recipient count; subsequent ordinary loop events report actual progress and completion. |
4544
4786
  | §notifications-notice-event `notice/event` | `{ workerId, loopId, notice: Notice }` | A transient observation or progress notice occurs. `workerId` owns loop activity; only workspace derivation progress uses `null` with `loopId=0`. It cannot alter durable history, scheduling, recovery, or model-visible failure truth. |
@@ -4596,23 +4838,23 @@ flowchart LR
4596
4838
 
4597
4839
  ### §packet-wire-envelope The wire envelope
4598
4840
 
4599
- The packet reaches the provider under the roles the model was tuned on, its bytes unchanged:
4841
+ The packet reaches the provider as a transcript, under the roles the model was tuned on, its bytes unchanged:
4600
4842
 
4601
4843
  | Message | Role | Content |
4602
4844
  |:--|:--|:--|
4603
4845
  | 1 | `system` | the system slot, as rendered |
4604
- | 2 … | `user` | the log's records, one message per completed turn in record order; the first opens with `## Log` |
4605
- | next | `assistant` | the canonical rendering ({§statement-rendering}) of every statement the parser admitted from the worker's most recent program that admitted any ({§turn-source-resources}, kind `ops`), in order and alone: free text and unadmitted forms are absent, a recovered native call ({§native-tool-calls}) appears as the operation it was read as, and an operation whose receipt failed stays, since it produced its row; turn zero's survey ({§worker-initialization-entry}) is the first, so every model request carries one |
4606
- | last | `user` | the current turn's records, then the remaining user sections in {§packet-cache-monotone} order; native parts ride here ({§packet-attachment-parts}) |
4607
-
4608
- Only role boundaries are added. Curation governs every record as before, so a KILLed
4609
- row is absent from its turn's message; the one program is bounded and the model's own last operations,
4610
- a demonstration of the grammar beside what the log made of it. Whatever sits under the assistant marker
4611
- is what the model writes next, for better and for worse: shown its own slip, a model repeats it, so the
4612
- slot carries the grammar's reading and never the bytes as typed. On the first request it is turn zero's
4613
- survey, the worked example in the model's own place. The prefix through the last completed
4614
- turn stays reusable across requests; the assistant message and the closing user message are the
4615
- changing tail. The digest's packet artifacts record the packet; the envelope is its projection (#903).
4846
+ | 2, 4, … | `user` | the user slot's text up to and including the record of the next placed emission that delivers text ({§emission-row}); the first opens with `## Log` |
4847
+ | 3, 5, … | `assistant` | that row's frozen emission projection less its NOTE and WAIT blocks, as the worker's own message ({§emission-row}) |
4848
+ | last | `user` | the records after the last emission delivered, then the remaining user sections in {§packet-cache-monotone} order; native parts ride here ({§packet-attachment-parts}) |
4849
+
4850
+ Only role boundaries are added: joined by blank lines, the user messages are the user slot's
4851
+ bytes, in record order. An emission is placed exactly when its row is present in the final log
4852
+ section, so curation governs the transcript: a KILLed emission row, or one a trusted transform
4853
+ removed ({§packet-plugin-transform}), takes its emission with it, and a log without emission rows
4854
+ is one user message. An emission of only NOTE and WAIT delivers nothing, so its record runs on
4855
+ into the next user message. The Worker block and the status clump always follow the log, so a
4856
+ request never ends on an emission, and the projection refuses one that would. The digest's packet
4857
+ artifacts record the sections, and `.wire.json` the messages ({§share-packet-names}).
4616
4858
 
4617
4859
  ### §packet-cache-monotone Default order and cache locality
4618
4860
 
@@ -4624,7 +4866,7 @@ Conditional absence never reorders the surviving default sections.
4624
4866
  | 2 | system | `system-policy` | Operator policy; empty content is omitted on the wire. |
4625
4867
  | 3 | system | `inject` | Present only when operator notes are configured. |
4626
4868
  | 4 | user | `log` | Append-mostly model-visible history; the first user section, so the cached prefix ends inside it. |
4627
- | 5 | user | `worker` | `Worker`: `{"path": "worker://alice", "parent": <address or null>, "loop": L, "turn": T, "previousEmission": <ops address or null>}`, the actor, the coordinate this packet's response becomes and the address of its previous program ({§packet-current-turn}). |
4869
+ | 5 | user | `worker` | `Worker`: `{"path": "worker://alice", "parent": <address or null>, "loop": L, "turn": T}`, the actor and the coordinate this packet's response becomes ({§packet-current-turn}). |
4628
4870
  | 6 | user | `delegation` | `Delegation`: per-turn `{workers, streams}` pointers; always present, each list `[]` when empty ({§packet-empty-sections}). |
4629
4871
  | 7 | user | `errors` | Per-turn failure pointers; empty content is omitted. |
4630
4872
  | 8 | user | `notices` | Per-turn observations; empty content is omitted. |
@@ -4635,7 +4877,9 @@ Conditional absence never reorders the surviving default sections.
4635
4877
 
4636
4878
  The order favors prefix-cache locality where semantics permit: the definition
4637
4879
  and privileged policy lead operator notes, while the append-mostly
4638
- log leads the volatile user-status clump. It does **not** claim that every system byte is
4880
+ log leads the volatile user-status clump. An emission sits at the row that announces it
4881
+ ({§packet-wire-envelope}), so the reusable prefix runs through every retained emission, and
4882
+ retiring one breaks the prefix at its row like any other curation. It does **not** claim that every system byte is
4639
4883
  immutable or that the complete packet is globally monotone in volatility:
4640
4884
  operator notes and policies can change. Trust is a separate
4641
4885
  admission rule. The system slot contains trusted control-plane material;
@@ -4651,6 +4895,8 @@ Each initial or returned list passes the schemes-owned validator, including
4651
4895
  unique-name enforcement, before the next transformer or renderer. Each
4652
4896
  transformer may inspect the section content and add, remove, or reorder
4653
4897
  sections. It receives no separate engine, database, actor, or request context.
4898
+ Emission placement reads the final log section, so a transform that removes an
4899
+ emission row's record removes its emission from the transcript ({§packet-wire-envelope}).
4654
4900
 
4655
4901
  This is strictly a trusted in-process seam, admitted through the common plugin
4656
4902
  trust gate; an external client action cannot invoke it. Whole-list transformation is
@@ -4664,13 +4910,13 @@ time of measurement.
4664
4910
 
4665
4911
  | Fact | Owner and unit | Time | Contract |
4666
4912
  |:-----|:---------------|:-----|:---------|
4667
- | Core curation weight | `contentWeight = ceil(chars/2)` over channel content, canonical log bodies, and rendered packet slots | Write/build | Stable, model-independent pressure and curation savings; never a tokenizer claim. |
4913
+ | Core curation weight | `contentWeight = ceil(chars/2)` over channel content, canonical log bodies, rendered packet slots, and placed emissions | Write/build | Stable, model-independent pressure and curation savings; never a tokenizer claim. |
4668
4914
  | §tokenomics-context-envelope-admission Provider input capacity | Provider model limits and configured output reservation, 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. |
4669
4915
  | Provider generation envelope | Provider response grant and optional reasoning subset, in provider tokens | Before every logical request | The reservation includes hidden reasoning; its strict reasoning subset is never additive. The response grant follows {§provider-flexed-allowance}. |
4670
4916
  | 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. |
4671
4917
 
4672
4918
  - §tokenomics-weight-stored-at-write **Curation weight, stored at write.** `entry_channels.weight` weighs the complete channel content. `log_entries.weight` weighs the complete canonical `LogBody` content before coordinate and packet presentation; persistence `tx`/`rx` envelopes contribute nothing merely by existing, and proposal settlement recomputes the value when the canonical result changes. Bodyless rows therefore weigh zero. The stored number is a stable content-depth measurement, not a provider-token prediction. `entry_channels.lines` is the channel's line count beside it, a stored generated column SQLite keeps on every write as the persisted mirror of {§logical-line-count} (a trailing newline terminates the last line; empty content has none), so a catalog lists extent without reading bodies.
4673
- - §tokenomics-render-weight-budget **Packet curation budget.** `logTokensTotal` measures the *complete assembled packet* after section transforms and readout substitution; it is not a sum of log-row `logTokens` fields. Core measures minimum-width probes, monotonically expands fields that do not fit, then right-aligns final values into those widths; final substitution is length-invariant and the displayed total equals the stored request weight. Receipt, FIND-item, pressure-inventory, total, and ceiling figures all use the same curation ruler. A `SUM` of stored content weights measures a different artifact and cannot substitute for packet render weight.
4919
+ - §tokenomics-render-weight-budget **Packet curation budget.** `logTokensTotal` measures the *complete assembled packet* after section transforms and readout substitution, its rendered slots and the emissions it places ({§emission-row}); it is not a sum of log-row `logTokens` fields. Core measures minimum-width probes, monotonically expands fields that do not fit, then right-aligns final values into those widths; final substitution is length-invariant and the displayed total equals the stored request weight. Receipt, FIND-item, pressure-inventory, total, and ceiling figures all use the same curation ruler. A `SUM` of stored content weights measures a different artifact and cannot substitute for packet render weight.
4674
4920
  - §tokenomics-calibrated-readout **Convert capacity, never content costs.** Before packet assembly, Core obtains the answering model's last five settled emission responses pairing a measured packet weight with a provider-reported prompt count. The conversion factor is `sum(reported) / sum(weight)`; fewer than three samples use 1. `logTokensMax = floor(inputCapacity / factor)` converts provider capacity into curation units. Zero means no whole curation unit fits; unknown input capacity remains `null`. The built packet captures this allowance once for its readout, pressure inventory, overflow admission, and persisted client gauge. Later responses cannot change that packet's allowance. Samples are model-keyed, not worker-local; a model with no samples starts at 1. Calibration never changes stored weights, rendered receipt costs, or the immutable request history ({§tokenomics-agnostic-ruler}).
4675
4921
  - §tokenomics-window-partition **One capacity derivation; no service-side token budget.** The provider owns model limits, the configured output reservation, and each call's response grant. Its resolved `inputCapacity` supplies the physical denominator exposed to clients and the boundary conversion into curation units ({§tokenomics-calibrated-readout}). Core shapes context in curation units; provider request-shaped evidence alone admits or rejects physical I/O. `PLURNK_SERVICE_PROMPT_BUDGET`, `PLURNK_SERVICE_SAFETY`, and the additive reasoning/completion reserve knobs are retired; local and custom deployments tune context window, total output budget, optional reasoning subset, and prompt-projection percentage at their owning layers.
4676
4922
  - §tokenomics-prompt-projection-share **Prompt projection is stable packet policy.**
@@ -4876,7 +5122,7 @@ leaves the request-only record, while rejected exchanges remain in their
4876
5122
  | Turn state | `turns.packet` (the bag) + `turn_sections` rows |
4877
5123
  | ----------------------------- | ----------------------------------------------- |
4878
5124
  | No admitted model request (including initialization and local capacity rejection) | SQL `NULL`, no rows |
4879
- | Request assembled | `{ weight, attributions }` + the sections as items |
5125
+ | Request assembled | `{ weight, attributions }` + the sections as items; the emissions it placed stay in their rows ({§emission-row}) |
4880
5126
  | Response admitted | `{ weight, attributions, assistant, assistantRaw }` + the sections as items |
4881
5127
 
4882
5128
  §packet-items **Sections are rows over content-addressed items; the bag never holds them.**
@@ -4932,6 +5178,7 @@ consumer reconstructs a name. A name that cannot be a file name, or two turns sh
4932
5178
  |----------|--------------|-----------|
4933
5179
  | `<stem>.assistant.md` | The turn has an `ops` source | Exact `turn_sources.content`, independent of log rows |
4934
5180
  | `<stem>.system.md`, `<stem>.user.md` | The turn stored a provider request | Stored text sections projected through `PacketWire`; native parts are not Markdown |
5181
+ | `<stem>.wire.json` | The turn stored a provider request | The request's text messages in order, its worker's emission rows placed ({§packet-wire-envelope}); `<stem>.wire.invalid.json` names a stored log that cannot be projected |
4935
5182
  | `digest.json` turn `attachments` | Every turn | Stored native attachment descriptors; `[]` means a request without attachments, `null` means no valid stored request. Selection is not proof of provider acceptance. |
4936
5183
  | `<stem>.assistantRaw.json` | The request has an admitted provider response | Stored opaque provider response |
4937
5184
  | `<stem>.response.md`, attempt artifacts | The request received no admitted response | Stored request and attempt state |
@@ -5053,7 +5300,7 @@ ordered set exactly once. Recovery retries complete the same queued loop and nev
5053
5300
  mint duplicate work. Output withholding preserves readable arrival rows; explicit
5054
5301
  KILL follows the ordinary log contract.
5055
5302
 
5056
- §completion-defers-to-messages **Conclusion does not cross an unanswered arrival.** The end-of-program check includes messages that arrived during inference. The final database transition rechecks unanswered messages atomically. An arrival that wins the race continues the current loop; one admitted after conclusion belongs to a new loop. Orphan recovery preserves messages accepted before an independently forced termination.
5303
+ §completion-defers-to-messages **Conclusion does not cross an unobserved arrival.** The end-of-program check includes messages that arrived during inference. The final database transition atomically requires every inbox message to be published and the loop's observed wake revision to equal its worker's current revision. Answer delivery is not this barrier. An arrival that wins the race continues the current loop; one admitted after conclusion belongs to a new loop. Orphan recovery preserves messages accepted before an independently forced termination.
5057
5304
 
5058
5305
  §packet-catalog **Catalogs are query results, not packet state.** The packet
5059
5306
  stores no materialized manifest. Complete and one-level entry directories,
@@ -5151,11 +5398,25 @@ retain distinct contracts and lifetimes.
5151
5398
 
5152
5399
  §digest-wire-line **Wire health aggregated.** Each worker summary renders a `Wire:` line — total physical provider requests, error-outcome count, and the error percentage when nonzero. Provider-level failures are absorbed by retries below the packet stream, so without this aggregate a rate-limit storm is invisible in every summary while the model's experience stays clean.
5153
5400
 
5154
- §digest-cache-ledger **Cacheable versus cached, per request.** For every physical provider request the digest computes its cacheable prefix: the longest common prefix, in characters, between the prompt its turn stored (the packet's rendered `system` text followed by its `user` text — the bytes `.system.md` and `.user.md` carry) and the prompt of the previous provider request in the same loop, taken as that prefix's share of the whole prompt in the packet's own token estimate ({§tokenomics-agnostic-ruler}) and applied to the provider's reported input count, so `cacheableTokens` sits in the same units as the cache read beside it; a loop's first request has 0, and a request whose turn stores no valid packet or whose provider reported no input count has none. Beside it sit the provider's reported cache read as `cachedTokens` (`provider_requests.usage_input_cache_read`) and `inputTokens` (`usage_input`). `digest.json` carries the three on every provider-request row; an inference turn line carries `cache=<cached>/<cacheable>` summed over the turn's requests; each workspace heading is followed by `Cache: <cached> of <cacheable> cacheable tokens reported (<pct>%) over <n> requests`. A provider that reported no cache field at all (null, not 0) renders `?` on the turn line and is counted apart on the workspace line (`· <k> unreported (cached=?)`), outside both sums and the percentage; a request without a stored packet or without a reported input count is likewise counted apart. The prefix measures what the daemon kept identical between consecutive requests; it is no claim about the provider's tokenization or cache-block alignment, so a provider that under-caches an identical prefix reads as such, apart from a prefix the daemon itself broke.
5401
+ §digest-cache-ledger **Measured cache reuse and estimated prompt overlap are separate.**
5402
+
5403
+ | Projection | Meaning |
5404
+ |---|---|
5405
+ | `digest.json` provider-request `cachedTokens`, `inputTokens` | Exact provider-reported cache reads and input tokens; absent quantities remain `null`, reported zero remains zero. Every physical request counts, including retries and first requests of new loops. Stored packet availability is irrelevant to these counters. |
5406
+ | Turn `cache=<cached>/<input>` | Sum each measured quantity over that turn's requests. If any request omits a quantity, that sum is `?`. |
5407
+ | Workspace `Cache: <cached> of <input> reported input tokens read from cache (<pct>%) over <n> requests` | Sum only requests reporting both counters. Percentage is cache reads / input, rounded to one decimal; zero input is `n/a`. Requests missing either counter are counted separately as `missing input or cache usage (excluded)`. |
5408
+ | `digest.json` provider-request `adjacentPrefixTokensEstimate` | Optional loop-local diagnostic: the longest common character prefix with the preceding request, weighted under {§tokenomics-agnostic-ruler} as a share of the current stored prompt, multiplied by reported input tokens. First request: `0`; missing current/preceding packet or current input: `null`. Empty prompts have zero overlap. |
5409
+
5410
+ The prefix estimate uses the stored emission packet's wire message order, roles
5411
+ and content. A BARE request's input is not that packet; its prefix estimate and
5412
+ the following request's comparison are unknown. The estimate is
5413
+ neither provider tokenization nor a cache ceiling, and never supplies a cache-ratio
5414
+ denominator. Caching across loops or against other provider-resident prefixes
5415
+ does not make the measured counters inconsistent.
5155
5416
 
5156
5417
  §digest-edit-census **Every model EDIT by the form it authored, how it landed, and whether it came back.** For each worker the digest reads every model-authored EDIT row and classifies the form from the row's stored marker and the durable statement's pattern: `hash` (one anchor), `line` (one line number), `range` (two marks), `insert` (the zero-width `<L,1,L,1>` form, {§zero-width-column-one-insert}), `column` (any other four-mark region), `prepend` / `append` (`<0>` / `<-1>`), `offset` (a tolerated anchor offset, {§anchor-offset}), `pattern` (a selection matcher), `whole` (no marker: a creation when it lands 201). It counts the EDITs, those refused (status ≥ 400), and the *revisits*: an EDIT of a path the same worker had edited within its previous two model turns — the shape of a repair without the claim of one. Each worker summary renders `EDITs: <n> · <form>=<count>… · refused=<k> · revisits=<r>` (`(no edits)` for none); `digest.json` carries the census as `edit_census` on every worker and stamps every EDIT log entry with its `edit_form` and `edit_revisit`. A form is a fact about what was written, never about intent; the bench sheet reads the counts as friction and leaves the judgement to the reader.
5157
5418
 
5158
- §digest-forensic-fidelity **Forensic fidelity and cardinality.** The digest's machine-readable JSON preserves every log event with its initial and current projection, causal `source`, and structured `attrs`; every exact log-KILL target effect; the exact Problem on every failed row; each loop's exact terminal result, settlement time, scheduled due time, recurring interval, and recurrence lineage; and every ordered physical provider request. Programs still produce chronological `assistant.md` artifacts after every READ receipt is KILLed; source is independent of log curation. Each stored packet validates independently: one malformed historical packet remains exact raw evidence with its complete validation error chain and never prevents healthy turns from being projected. Accounting on broader rows is the shared exact derivation from that ledger, never a second stored fact. A worker's Cost line names how many settled requests carry no usage at all (errored or aborted exchanges) — their server-side spend is unrecorded rather than silently priced as zero. The reasoning chronology distinguishes readable reasoning content from provider-reported reasoning usage: when tokens were reported but no readable content was returned, it states both facts instead of implying that no reasoning occurred. The human Markdown waterfall shows a present causal source and may preview only the Problem detail because it remains a triage projection, not the machine record. Targets reconstruct the model-visible address, including hostname, port, serialized query, and fragment; an authority-bearing URL must never degrade from `https://host/path` to `https:///path`, and durable resource coordinates render back to their authority form. Its human Markdown waterfall groups identical per-turn op outcomes and typed `entry_materialized` narrations, reporting the exact count and sequence span (`xN (seq A-B)`). Grouping keys include source and the complete target, so distinct causes, authorities, or channels never collapse. Thus amplification is conspicuous without making the diagnostic artifact itself pathological; valid packet files remain byte-identical records of what the model saw.
5419
+ §digest-forensic-fidelity **Forensic fidelity and cardinality.** The digest's machine-readable JSON preserves every log event with its initial and current projection, causal `source`, and structured `attrs`; every exact log-KILL target effect; the exact Problem on every failed row; each loop's exact terminal result, settlement time, scheduled due time, recurring interval, and recurrence lineage; and every ordered physical provider request. Programs still produce chronological `assistant.md` artifacts after every READ receipt is KILLed; source is independent of log curation. Each worker summary's `Emissions:` line counts its announced emission rows, those the worker KILLed, and the headings it echoed ({§emission-row}); the op mix leaves the announcements out. Each stored packet validates independently: one malformed historical packet remains exact raw evidence with its complete validation error chain and never prevents healthy turns from being projected. Accounting on broader rows is the shared exact derivation from that ledger, never a second stored fact. A worker's Cost line names how many settled requests carry no usage at all (errored or aborted exchanges) — their server-side spend is unrecorded rather than silently priced as zero. The reasoning chronology distinguishes readable reasoning content from provider-reported reasoning usage: when tokens were reported but no readable content was returned, it states both facts instead of implying that no reasoning occurred. The human Markdown waterfall shows a present causal source and may preview only the Problem detail because it remains a triage projection, not the machine record. Targets reconstruct the model-visible address, including hostname, port, serialized query, and fragment; an authority-bearing URL must never degrade from `https://host/path` to `https:///path`, and durable resource coordinates render back to their authority form. Its human Markdown waterfall groups identical per-turn op outcomes and typed `entry_materialized` narrations, reporting the exact count and sequence span (`xN (seq A-B)`). Grouping keys include source and the complete target, so distinct causes, authorities, or channels never collapse. Thus amplification is conspicuous without making the diagnostic artifact itself pathological; valid packet files remain byte-identical records of what the model saw.
5159
5420
 
5160
5421
  Unrecognized actionless log rows are retained and labelled as such, not
5161
5422
  interpreted as executable turnOps or allowed to prevent the remaining digest.
@@ -5197,7 +5458,7 @@ USD, and token totals across every physical exchange the turn paid for, failed
5197
5458
  calls included. It is the shared exact derivation from the ledger, never a second
5198
5459
  stored fact, so a live watcher accrues running loop cost per turn (#465).
5199
5460
 
5200
- §notice-content-offset-pointer **Content-offset position.** A non-fatal diagnosis on an accepted emission (for example `grammar_unenforced` or `parse_advisory`) carries `position: { type: "content-offset", line, column }` into the model's exact `ops://<worker>/<loop>/<turn>` source. A bounded hard parse error becomes a durable failed operation whose Problem Details preserve its line, column, source, and parser-owned diagnostic. Hard errors that make the frame untrustworthy remain only with their rejected forensic attempt.
5461
+ §notice-content-offset-pointer **Content-offset position.** A non-fatal diagnosis on an accepted emission (for example `grammar_unenforced` or `parse_advisory`) carries `position: { type: "content-offset", line, column }` into the model's exact `ops://<worker>/<loop>/<turn>` source; the transcript shows that turn's canonical emission instead ({§emission-row}), so the position names a line of the source, which READ of its address shows. A bounded hard parse error becomes a durable failed operation whose Problem Details preserve its line, column, source, and parser-owned diagnostic. Hard errors that make the frame untrustworthy remain only with their rejected forensic attempt.
5201
5462
 
5202
5463
  ### Executable tool resources
5203
5464
 
@@ -5272,8 +5533,10 @@ enable | disable | remove`, `workspace.members.<verb>` for the client,
5272
5533
  ```` ```members (<verb>) ```` for the model — for what the model may see, exactly as they do for skills and
5273
5534
  MCP servers. A definition is one gitignore-style glob, `{ glob }`, relative to the project
5274
5535
  root; a leading `!` excludes matching members, and an exclusion wins over every inclusion.
5275
- The coordinator's provenance (`service-configuration`, `client-action`, `model-proposal`)
5276
- rides the definition; its alias is a short name, suggested from the glob. `list` shows each
5536
+ Admission provenance (`service-configuration`, `client-action`, `model-proposal`)
5537
+ rides the definition to enforce {§members-model-scope}; configuration-source
5538
+ provenance belongs to the shared inspection projection ({§configuration-provenance}).
5539
+ The alias is a short name, suggested from the glob. `list` shows each
5277
5540
  definition with what it resolved to — `include` or `exclude`, the pattern, the members it
5278
5541
  admits or removes (count and a bounded sample), and for a model's inclusion the matches the
5279
5542
  repository's ignore rules refused — so the model sees what its glob did and adapts.
@@ -5282,10 +5545,12 @@ repository's ignore rules refused — so the model sees what its glob did and ad
5282
5545
  untracked, absent); a glob previews what `add` would include or exclude. Names only, never
5283
5546
  content; nothing is added.
5284
5547
 
5285
- §members-configuration *Available definitions.* The operator's `PLURNK_MEMBERS_<ALIAS>=<glob>`
5286
- (`!glob` excludes) and `PLURNK_MEMBERS_ENABLED=[…]` (`[]` enables none) are the
5287
- service-origin definitions, the shape `PLURNK_MCP_*` already has; an empty glob, a bare `!`,
5288
- or an unknown enabled alias fails the daemon at boot.
5548
+ §members-configuration *Available definitions.* `PLURNK_MEMBERS_<alias>=<glob>`
5549
+ declares one service-origin rule (`!glob` excludes). The shared naming and
5550
+ enabledness dialect is {§resource-environment}; `PLURNK_MEMBERS_ENABLED` supplies
5551
+ the panel default and `PLURNK_MEMBERS_<alias>_ENABLED` overrides one rule.
5552
+ An empty glob or a bare `!` fails validation. Controls may precede their rule;
5553
+ they are validated without creating a definition ({§resource-environment}).
5289
5554
 
5290
5555
  §members-model-scope *The model's authority.* A model's `add` is admitted against
5291
5556
  `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` in the file-creation lattice `none < root <
@@ -5317,53 +5582,98 @@ verbs are these verbs.
5317
5582
  §skills-functionality **Agent Skills are one workspace Functionality family.**
5318
5583
  Core registers the `skills` family with the coordinator ({§functionality-coordinator});
5319
5584
  its adapter owns protocol truth for standard Agent Skills and nothing else. A
5320
- definition is `SkillDefinition` — the standard skill `name`, its source
5321
- `scope` (`project` = `<projectRoot>/.agents/skills`, `global` =
5322
- `~/.agents/skills`, `service` = a host-provided resource tree), and for a workspace-installed skill the standard installer
5323
- `source` that provides it. Plurnk seeds no universal root and mutates none
5324
- absent an explicit `add`/`remove`.
5585
+ definition is `SkillDefinition`: the standard skill `name`, its `source`, and
5586
+ optional Git `ref`/resolved `commit` ({§skills-sources}). Only a host-provided
5587
+ resource tree omits `source`. Source location is not mutation ownership: standard
5588
+ project/global roots are read-only configuration inputs; live changes belong to
5589
+ the workspace. Plurnk neither installs into nor deletes from those roots.
5325
5590
 
5326
5591
  *Available definitions.* The filesystem is the only truth about installation:
5327
- every `<root>/<name>/SKILL.md` directory under the project then the global
5328
- root is one service-origin definition, enabled by default, project shadowing
5329
- global and then host-provided trees by name; when the standard installer's `skills-lock.json` records a
5330
- source it rides the definition. The workspace's durable state owns enablement
5331
- ({§functionality-state}); a disabled skill stays client-visible and leaves no
5332
- model-facing trace.
5333
-
5334
- *Discovery is inert.* `discover {query}` searches the ecosystem registry
5335
- (`PLURNK_SERVICE_SKILLS_REGISTRY_URL`; empty disables it
5336
- with 501 `registry-not-configured`) and returns one candidate per hit with
5337
- `registry` provenance and the exact `owner/repo` source. `discover {source}`
5338
- lists the skills one standard package reference contains with `source`
5339
- provenance. Neither installs, persists, or enables. Client `configuration`
5340
- contributes nothing and is refused with 400.
5341
-
5342
- *Admission.* `add {alias, definition}` requires `alias = name`, a `source`,
5343
- and a project root when `scope` is `project`; the workspace definition may
5344
- shadow a service skill of the same name. The family's aliases use the standard
5345
- skill-name grammar ({§agent-skills-name}), including digit-leading and Unicode
5346
- names, rather than the coordinator's generic default.
5592
+ every `<root>/<name>/SKILL.md` directory under the project, plurnk, then global
5593
+ root is one service-origin definition, a nearer root shadowing a farther one and
5594
+ all of them shadowing host-provided trees by name. Each filesystem definition
5595
+ names its actual source directory. The workspace's durable state owns
5596
+ enablement ({§functionality-state}); a disabled skill stays client-visible and
5597
+ leaves no model-facing trace.
5598
+
5599
+ §skills-configuration **Skills use the shared definition cascade.**
5600
+
5601
+ | Layer, low to high | Definition source |
5602
+ |---|---|
5603
+ | Service | Host-provided trees |
5604
+ | Standard locations | Global, plurnk, then project roots selected by {§agent-roots} |
5605
+ | Cascading environment | `PLURNK_SKILLS_<name>={"name":"<name>","source":"…","ref"?:"…"}` replaces the complete definition |
5606
+ | Live workspace | `skills (add)` creates a workspace definition through {§functionality-coordinator} |
5607
+
5608
+ `PLURNK_SKILLS_ENABLED` supplies default enabledness; `<name>_ENABLED` overrides
5609
+ it independently ({§resource-environment}). The decoded environment alias must
5610
+ equal the standard skill name, including digit-leading and Unicode names.
5611
+ Blank definitions are invalid; controls may precede a definition. Environment
5612
+ validation checks shape, names, remote URL rules, and Git-only `ref` without
5613
+ fetching or opening a source; `commit` is service-recorded, not an input.
5614
+
5615
+ *Discovery is inert.* `discover {source}` lists the standard skills one source
5616
+ carries, each a candidate with `source` provenance and the exact definition to
5617
+ add; it never installs, persists, or enables. Agent Skills have no standard
5618
+ registry, so `discover {query}` is 400 `query-unsupported`, naming the source
5619
+ forms. Client `configuration` contributes nothing and is refused with 400.
5620
+
5621
+ *Admission.* `add {alias, definition}` requires `alias = name` and a `source`;
5622
+ the workspace definition may shadow a service skill of the same name. Relative
5623
+ sources require a project root; absolute sources work in headless workspaces.
5624
+ A local source is recorded as its absolute path.
5625
+ A git source records the `commit` its `ref` names at admission, or its default
5626
+ branch's when no `ref` is given; `ref` belongs to git sources, and a supplied
5627
+ `commit` is refused because the service records it. The family's aliases use
5628
+ the standard skill-name grammar ({§agent-skills-name}), including digit-leading
5629
+ and Unicode names, rather than the coordinator's generic default.
5347
5630
 
5348
5631
  *Preparation.* For each enabled alias the adapter selects the host-provided
5349
- tree for `service` scope or locates the directory at the filesystem scope;
5350
- a workspace definition whose directory is absent is installed
5351
- through the standard CLI (`PLURNK_SERVICE_SKILLS_CLI`, invoked as
5352
- `<cli> add <source> --agent universal --skill <name> --yes [--global]`, run with
5353
- `HOME` set to the service's user home so the installer's `~` is the global
5354
- root) and the installed `SKILL.md` — never the installer's output — is the
5355
- evidence.
5632
+ tree or resolves the complete source definition ({§skills-sources}). Local
5633
+ folders and their `SKILL.md` files remain live references, including supporting
5634
+ resources and symlink retargeting. Git/archive sources are materialized only
5635
+ inside {§module-workspace-directory}; different workspaces and complete source
5636
+ definitions cannot reuse each other's materializations accidentally.
5637
+ The first fetched Git/archive copy remains stable across enable, cooling, and
5638
+ restart until the complete source definition changes. In particular, an
5639
+ operator-configured symbolic Git ref is not an implicit update subscription.
5356
5640
  Each admitted skill requires standard `name` and `description` frontmatter
5357
5641
  with `name` matching its directory. A missing, uninstallable, or invalid skill
5358
- is `unavailable` with its exact Problem (`skill-missing`, `install-failed`,
5359
- `skill-invalid`) under the coordinator's failure policy
5360
- ({§functionality-model-mutation}); one bad skill never fails the family.
5642
+ is `unavailable` with its exact Problem ({§problems-functionality}) under the
5643
+ coordinator's failure policy ({§functionality-model-mutation}); one bad skill
5644
+ never fails the family.
5645
+
5646
+ Removal follows {§skills-remove}.
5361
5647
 
5362
- Installer provenance follows the upstream lock locations: project
5363
- `skills-lock.json`; global `$XDG_STATE_HOME/skills/.skill-lock.json` when
5364
- configured, otherwise `~/.agents/.skill-lock.json`. A lock's source belongs to
5365
- its scope, never a same-named installation in another root. Missing locks mean
5366
- unknown provenance; malformed or unreadable locks surface their cause.
5648
+ §skills-sources **A source is a git remote, a folder, or a file, read with standard tools.**
5649
+ Fetching runs nothing it fetched. Materialized copies cannot contain references
5650
+ outside their skill; live resources retain {§agent-skills-directory} containment.
5651
+
5652
+ | Source | How it is read |
5653
+ |---|---|
5654
+ | Git remote: a full `https://` or `ssh://` URL, or `user@host:path` | `git ls-remote` resolves the ref at admission; preparation shallow-clones it with hooks and submodules off, and a checkout at any other commit is 409 `source-moved` |
5655
+ | Folder: absolute, `~/`, or relative to the project root | Live reference; standard directory/name matching applies |
5656
+ | A file named `SKILL.md` | Live reference to its skill directory and supporting resources |
5657
+ | A `.zip`, `.tar`, `.tgz`, `.tar.gz`, `.tar.bz2`, `.tar.xz`, or `.tar.zst` archive | Unpacked into private staging with `unzip` or `tar`; a lone top-level directory is the source's root |
5658
+
5659
+ Any other scheme, plain `http`, `owner/repo` shorthand, and an https URL carrying
5660
+ credentials are refused with `source-invalid` or `source-missing`: shorthand names no
5661
+ forge, and a recorded source is listed to every client. Git runs with the
5662
+ operator's configuration, credentials, and SSH agent, never plurnk's secrets, and
5663
+ never prompts; `PLURNK_SERVICE_SKILLS_FETCH_TIMEOUT_MS` bounds each fetch. The
5664
+ retired vendor-installer knobs (`PLURNK_SERVICE_SKILLS_CLI`, `_CLI_TIMEOUT_MS`,
5665
+ `_REGISTRY_URL`, `_REGISTRY_LIMIT`, `_REGISTRY_TIMEOUT_MS`) make the skills family
5666
+ unavailable when set, each naming what replaced it ({§configuration-repair-path}).
5667
+
5668
+ A source's skills are the directories holding a `SKILL.md`, found by walking
5669
+ from its root without entering `.git` or a skill already found. A fetched skill at
5670
+ the root is named by its frontmatter ({§agent-skills-name}); local references and
5671
+ directories below the root use the standard folder rule. A source with `plugin.json` at its root is an
5672
+ Agent Plugin and is refused with 422 `source-is-plugin`, so its skills keep the
5673
+ plugin's identity. Materialization copies the named skill beside its workspace-owned destination
5674
+ and renames it to `<root>/<name>`; a copy that holds a link out of the skill, or
5675
+ anything but files, directories, and inward links, is refused with 422
5676
+ `source-unsafe` and leaves nothing behind.
5367
5677
 
5368
5678
  §skills-resources **A skill is a resource tree, not a rewritten document.**
5369
5679
  The family exposes enabled, available {§agent-skills-tree} sources through
@@ -5380,13 +5690,13 @@ serialized URI address the same resource, not separate skill identities.
5380
5690
  | `READ (skill://<name>/SKILL.md)` | Original frontmatter and Markdown, unchanged; relative links remain relative to the source layout. |
5381
5691
  | `READ` / `FIND` below the authority | References, scripts, and assets retain their source paths and ordinary pattern, channel, byte, and multimodal semantics. Acquisition observes current source contents, including disappearance. |
5382
5692
  | ```` ```runtime (skill://<name>/scripts/program.ext) ```` | Ordinary resource execution and proposal policy; preserve the native file and its siblings under {§exec-source-temporary}. Discovery and READ never execute scripts. |
5383
- | Model mutation | Read-only; no EDIT, SEND, or KILL of installed resources. Manage installation and enablement through ```` ```skills ````. |
5693
+ | Model mutation | Read-only; no EDIT, SEND, or KILL of skill resources. Manage definitions and enablement through ```` ```skills ````. |
5384
5694
  | Disable / unavailable / remove | Withdraw the authority from new resource access and discovery. Existing log receipts remain historical evidence. |
5385
5695
  | WORK / FORK | Use the same workspace Functionality, not copied definitions or resource caches. |
5386
5696
 
5387
5697
  Explicit skill URIs address these resources; bare operation paths still address
5388
5698
  project files, with no implicit current-skill directory. Source resolution follows
5389
- {§agent-skills-directory}, including installer symlinks and containment of references.
5699
+ {§agent-skills-directory}, including symlinked skill directories and containment of references.
5390
5700
  An uninstalled Git skill is not manufactured by repository detection.
5391
5701
 
5392
5702
  §plurnk-skill **Plurnk's own reference is an ordinary service-provided skill.**
@@ -5399,24 +5709,22 @@ same {§operator-config-env-defaults} renderer as the operator command, never th
5399
5709
  effective environment. Native chapter files retain their owners and locations;
5400
5710
  runtime-generated bytes have no invented disk location. Disable/enable,
5401
5711
  shared workspace visibility, and project/global shadowing use the ordinary Skills lifecycle.
5402
- Service-provided skills are not installer targets; service definitions are
5403
- disable-only under {§skills-remove}.
5404
-
5405
- §skills-remove **`remove` uninstalls the workspace definition's installation.** Before the
5406
- coordinator forgets a workspace-origin skill definition the adapter removes that
5407
- skill from the definition's scope through the standard CLI (`remove <name>
5408
- --yes [--global]`), verified by the directory's absence; a failed removal
5409
- rejects the mutation. A same-named skill at a lower-precedence root is then
5410
- revealed as a service definition, disabled ({§functionality-coordinator}).
5411
- Service definitions are disable-only.
5412
-
5413
- §skills-hotload **Out-of-band installers are admitted at the next turn.** The
5414
- family keeps one signature of both installed roots, source locations, frontmatter
5415
- sources, and installer provenance per resident workspace; turn
5416
- admission recomputes it under the workspace gate before packet assembly and
5417
- republishes the family through the coordinator when it changed, so a skill
5418
- installed or removed by any other tool is discoverable in the first subsequent
5419
- model turn while an unchanged set dispatches nothing. The model manages skills
5712
+ Service-provided skills are not installer targets; removal follows {§skills-remove}.
5713
+
5714
+ §skills-remove **`remove` forgets the workspace binding, not the source.**
5715
+ It withdraws that definition and restores any inherited definition and enabledness
5716
+ ({§functionality-coordinator}). External folders are never deleted. Fetched
5717
+ materializations remain workspace-owned operational state, reusable only for the
5718
+ same complete definition; they confer no availability without a definition.
5719
+ Inherited definitions cannot be removed here; their enabledness can be overridden.
5720
+
5721
+ §skills-hotload **Skills placed out of band are admitted at the next turn** ({§functionality-hotload}). The
5722
+ family keeps one signature of the discovered roots, configured definitions, source locations, and `SKILL.md` sources
5723
+ per resident workspace, read before a publication loads the skills it describes. Turn admission
5724
+ recomputes it under the workspace gate before packet assembly: a changed signature republishes the
5725
+ family, and an unchanged one republishes only when the skills the coordinator would publish differ
5726
+ from the published ones. A skill installed, edited or removed by any other tool is therefore
5727
+ discoverable in the first subsequent model turn, while an unchanged set dispatches nothing. The model manages skills
5420
5728
  only through the generated ```` ```skills ```` family
5421
5729
  ({§functionality-model-projection}); it is never taught a package manager.
5422
5730
 
@@ -5875,6 +6183,7 @@ Every Problem code core mints is named here under its family ({§problem-error-c
5875
6183
  |---|---:|---|
5876
6184
  | `service-starting` | 503 | The PLURNK service owns this listener but has not admitted its client interface yet. |
5877
6185
  | `configuration-unsupported` | 400 | Environment discovery reads this installation's declared configuration; client configuration contributes nothing. |
6186
+ | `configuration-invalid` | 503 | The owning configuration reader's diagnostic, naming the invalid key. Recovery: Correct the named configuration input. Other capabilities remain available. |
5878
6187
  | `name-reserved` | 400 | '*alias*' is plurnk's own: PLURNK_* configuration and provider credential names never reach a subprocess. |
5879
6188
  | `value-invalid` | 400 | '*alias*' needs a string value. |
5880
6189
  | `env-invalid` | 400 | `env` must be an object of string values; '*name*' is not a name a shell can export. |
@@ -5882,14 +6191,19 @@ Every Problem code core mints is named here under its family ({§problem-error-c
5882
6191
  | `headless` | 409 | The workspace has no project root, so there are no file members. Recovery: Open the workspace on a project root. |
5883
6192
  | `definition-invalid` | 400 | A members definition is { glob }: a gitignore-style pattern, `!glob` to exclude; a skill definition names an installable skill. |
5884
6193
  | `model-scope` | 403 | The model may not change membership here: the members scope is none. Recovery: `git add` the file so git tracks it, or ask the operator to add it (/members add) or raise PLURNK_SERVICE_MEMBERS_MODEL_SCOPE. |
5885
- | `registry-unreachable` | 502 | Skills registry *url* could not be reached. |
5886
- | `registry-rejected` | 502 | Skills registry *url* answered *status*. |
5887
- | `registry-invalid` | 502 | Skills registry *url* returned no skills array. |
5888
- | `discover-failed` | 502 | Agent Skills source '*source*' could not be listed: *cause*. |
6194
+ | `query-unsupported` | 400 | Agent Skills have no standard registry to search; discover takes a source: a git remote as a full https or ssh URL, a folder, a lone SKILL.md, or a zip or tar archive. |
6195
+ | `source-invalid` | 400 | '*source*' is not a valid git remote URL; an https source carries no credentials (git's credential helper supplies them); is not a source: a git remote is a full https or ssh URL; is relative, and this workspace has no project root to resolve it against; or is neither a folder, a SKILL.md, nor a zip or tar archive. |
6196
+ | `source-missing` | 404 | No folder or file is at '*source*' (; owner/repo shorthand names no forge, so give the repository's full https or ssh URL). |
6197
+ | `source-unreadable` | 422 | '*source*' cannot be read: *cause*; or '*path*' could not be unpacked: *reason*. |
6198
+ | `source-unreachable` | 502 | git could not reach '*remote*', or fetch it (at '*ref*'): *reason*. |
6199
+ | `ref-missing` | 404 | '*remote*' has no branch or tag '*ref*', or names no default branch. |
6200
+ | `source-moved` | 409 | '*source*' *ref* now names *current*; this skill was added at *commit*. Recovery: Remove the skill and add it again to take the current commit. |
6201
+ | `source-is-plugin` | 422 | '*source*' is an Agent Plugin; install it as a plugin, so its skills keep the plugin's identity and servers. |
6202
+ | `source-unsafe` | 422 | '*path*' links outside its skill, or is neither a file, a directory, nor an inward link. |
6203
+ | `skill-not-found` | 404 | '*source*' carries no Agent Skill named '*name*'. |
6204
+ | `skill-ambiguous` | 409 | '*source*' carries *count* skills named '*name*'. |
5889
6205
  | `alias-mismatch` | 400 | Alias '*alias*' must equal the skill name '*name*'. |
5890
- | `scope-not-installable` | 400 | Service-provided skills can be enabled or disabled; adding a skill requires project or global scope. Recovery: Add it with scope "global" or open a workspace rooted in a project. |
5891
- | `source-required` | 400 | Adding '*alias*' requires the standard installer source that provides it. |
5892
- | `uninstall-failed` | 502 | Agent Skill '*name*' could not be removed from its *scope* root: *cause*. |
6206
+ | `source-required` | 400 | Adding '*alias*' requires the source that provides it. |
5893
6207
  | `workspace-not-found` | 404 | Workspace *id* does not exist. |
5894
6208
  | `state-not-json` | 400 | Worker module state is not JSON-serializable. |
5895
6209
  | `workspace-busy` | 409 | Workspace *id* is running an operation or another capability change. Recovery: Settle the current operation and retry the capability change. |
@@ -5902,9 +6216,8 @@ Every Problem code core mints is named here under its family ({§problem-error-c
5902
6216
  | `loop-policy-invalid` | 400 | An unattended loop cannot hold a proposal for review: nobody is present to answer. Recovery: State proposals accept or reject, or attend the loop. |
5903
6217
  | `scope-cancelled` | 499 | The worker scope was cancelled: *reason*. |
5904
6218
  | `range-not-satisfiable` | 416 | `Range <0,-1>` starts at 0, which is not a line; lines are numbered from 1. Recovery: Write `<1,-1>` to trim every line of the body; `KILL (log:///…/READ)` with no scope retires the item. |
5905
- | `registry-not-configured` | 501 | Skills registry search is disabled; PLURNK_SERVICE_SKILLS_REGISTRY_URL is empty. |
5906
- | `install-failed` | 502 | Agent Skill '*name*' could not be installed from '*source*': *cause* (or the installer reported it but its SKILL.md does not exist). |
5907
- | `skill-missing` | 404 | Agent Skill '*alias*' is not installed under its *scope* root, or is not provided by this service. |
6219
+ | `install-failed` | 500 | Agent Skill '*name*' could not be placed under *root*: *cause*. |
6220
+ | `skill-missing` | 404 | Agent Skill '*alias*' is not provided by this service. |
5908
6221
  | `skill-invalid` | 422 | Agent Skill '*alias*' is not a valid standard skill: *cause*. |
5909
6222
 
5910
6223
  §pinned-wording-core **Pinned wording.** Verbatim sentences tests pin: each is contract, and a change here is a change of contract.