@plurnk/plurnk-service 1.21.1 → 1.23.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.
- package/.env.defaults +16 -3
- package/INSTALL.md +2 -2
- package/README.md +1 -1
- package/SPEC.md +519 -213
- package/dist/build-info.json +1 -1
- package/dist/content/body-preview.js +1 -1
- package/dist/content/body-preview.js.map +1 -1
- package/dist/content/edit-receipt.d.ts.map +1 -1
- package/dist/content/edit-receipt.js +7 -13
- package/dist/content/edit-receipt.js.map +1 -1
- package/dist/content/index.d.ts +2 -7
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +2 -5
- package/dist/content/index.js.map +1 -1
- package/dist/content/line-anchors.d.ts.map +1 -1
- package/dist/content/line-anchors.js +7 -9
- package/dist/content/line-anchors.js.map +1 -1
- package/dist/content/line-marker.d.ts +1 -0
- package/dist/content/line-marker.d.ts.map +1 -1
- package/dist/content/line-marker.js +2 -0
- package/dist/content/line-marker.js.map +1 -1
- package/dist/content/matcher.d.ts +1 -1
- package/dist/content/matcher.d.ts.map +1 -1
- package/dist/content/matcher.js +8 -10
- package/dist/content/matcher.js.map +1 -1
- package/dist/content/pattern-edits.d.ts +6 -0
- package/dist/content/pattern-edits.d.ts.map +1 -1
- package/dist/content/pattern-edits.js +14 -0
- package/dist/content/pattern-edits.js.map +1 -1
- package/dist/content/read-projector.d.ts.map +1 -1
- package/dist/content/read-projector.js +2 -1
- package/dist/content/read-projector.js.map +1 -1
- package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
- package/dist/core/AdmittedTurnExecutor.js +7 -2
- package/dist/core/AdmittedTurnExecutor.js.map +1 -1
- package/dist/core/BudgetReadout.js +1 -1
- package/dist/core/BudgetReadout.js.map +1 -1
- package/dist/core/DataStatementRunner.js +1 -1
- package/dist/core/DataStatementRunner.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +3 -8
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +9 -16
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/Dispatcher.sql +1 -1
- package/dist/core/EditMutations.js +6 -6
- package/dist/core/EditMutations.js.map +1 -1
- package/dist/core/EditSequence.d.ts.map +1 -1
- package/dist/core/EditSequence.js +2 -1
- package/dist/core/EditSequence.js.map +1 -1
- package/dist/core/Engine.d.ts +9 -17
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +27 -19
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +22 -0
- package/dist/core/ErrorDetail.d.ts +3 -4
- package/dist/core/ErrorDetail.d.ts.map +1 -1
- package/dist/core/ErrorDetail.js +3 -18
- package/dist/core/ErrorDetail.js.map +1 -1
- package/dist/core/ExecutorRegistry.d.ts +1 -0
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +5 -1
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/FabricatedLog.d.ts +8 -0
- package/dist/core/FabricatedLog.d.ts.map +1 -0
- package/dist/core/FabricatedLog.js +16 -0
- package/dist/core/FabricatedLog.js.map +1 -0
- package/dist/core/HostPaths.d.ts +1 -0
- package/dist/core/HostPaths.d.ts.map +1 -1
- package/dist/core/HostPaths.js +39 -13
- package/dist/core/HostPaths.js.map +1 -1
- package/dist/core/KnownToxins.d.ts +0 -1
- package/dist/core/KnownToxins.d.ts.map +1 -1
- package/dist/core/KnownToxins.js +3 -12
- package/dist/core/KnownToxins.js.map +1 -1
- package/dist/core/LogBody.d.ts.map +1 -1
- package/dist/core/LogBody.js +12 -10
- package/dist/core/LogBody.js.map +1 -1
- package/dist/core/LogVisibility.d.ts +3 -1
- package/dist/core/LogVisibility.d.ts.map +1 -1
- package/dist/core/LogVisibility.js +30 -16
- package/dist/core/LogVisibility.js.map +1 -1
- package/dist/core/LoopDriver.d.ts.map +1 -1
- package/dist/core/LoopDriver.js +3 -4
- package/dist/core/LoopDriver.js.map +1 -1
- package/dist/core/LoopOutcome.d.ts +0 -1
- package/dist/core/LoopOutcome.d.ts.map +1 -1
- package/dist/core/LoopOutcome.js +1 -1
- package/dist/core/LoopOutcome.js.map +1 -1
- package/dist/core/LoopPolicies.js +1 -1
- package/dist/core/LoopPolicies.js.map +1 -1
- package/dist/core/OutsideEvent.d.ts +10 -0
- package/dist/core/OutsideEvent.d.ts.map +1 -0
- package/dist/core/OutsideEvent.js +5 -0
- package/dist/core/OutsideEvent.js.map +1 -0
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +7 -3
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/PatternSelection.d.ts +1 -1
- package/dist/core/PatternSelection.d.ts.map +1 -1
- package/dist/core/PatternSelection.js +8 -6
- package/dist/core/PatternSelection.js.map +1 -1
- package/dist/core/PreviousEmission.d.ts +14 -0
- package/dist/core/PreviousEmission.d.ts.map +1 -0
- package/dist/core/PreviousEmission.js +24 -0
- package/dist/core/PreviousEmission.js.map +1 -0
- package/dist/core/ProposalLifecycle.d.ts +9 -13
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +7 -11
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts +4 -4
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +24 -25
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/ProviderRecovery.d.ts +1 -1
- package/dist/core/ProviderRecovery.d.ts.map +1 -1
- package/dist/core/ProviderRecovery.js +5 -8
- package/dist/core/ProviderRecovery.js.map +1 -1
- package/dist/core/ResourceSelector.js +1 -1
- package/dist/core/ResourceSelector.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +2 -1
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +10 -2
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/StoredPacket.d.ts +1 -1
- package/dist/core/StoredPacket.d.ts.map +1 -1
- package/dist/core/StoredPacket.js +6 -15
- package/dist/core/StoredPacket.js.map +1 -1
- package/dist/core/StrikeRail.d.ts +2 -0
- package/dist/core/StrikeRail.d.ts.map +1 -1
- package/dist/core/StrikeRail.js +7 -1
- package/dist/core/StrikeRail.js.map +1 -1
- package/dist/core/Turn.d.ts +1 -1
- package/dist/core/Turn.d.ts.map +1 -1
- package/dist/core/Turn.js.map +1 -1
- package/dist/core/TurnDispositionHandler.d.ts.map +1 -1
- package/dist/core/TurnDispositionHandler.js +1 -4
- package/dist/core/TurnDispositionHandler.js.map +1 -1
- package/dist/core/TurnMaterialization.d.ts.map +1 -1
- package/dist/core/TurnMaterialization.js +5 -7
- package/dist/core/TurnMaterialization.js.map +1 -1
- package/dist/core/TurnRunner.d.ts +6 -8
- package/dist/core/TurnRunner.d.ts.map +1 -1
- package/dist/core/TurnRunner.js +126 -60
- package/dist/core/TurnRunner.js.map +1 -1
- package/dist/core/TurnSources.sql +15 -0
- package/dist/core/WorkerControlHandler.d.ts.map +1 -1
- package/dist/core/WorkerControlHandler.js +6 -1
- package/dist/core/WorkerControlHandler.js.map +1 -1
- package/dist/core/WorkerName.sql +2 -2
- package/dist/core/attachments.d.ts +0 -3
- package/dist/core/attachments.d.ts.map +1 -1
- package/dist/core/attachments.js +3 -3
- package/dist/core/attachments.js.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.js +1 -2
- package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
- package/dist/core/file-materialization.d.ts.map +1 -1
- package/dist/core/file-materialization.js +4 -3
- package/dist/core/file-materialization.js.map +1 -1
- package/dist/core/fork.sql +2 -2
- package/dist/core/git-env.d.ts.map +1 -1
- package/dist/core/git-env.js +2 -7
- package/dist/core/git-env.js.map +1 -1
- package/dist/core/git-state.js +1 -1
- package/dist/core/git-state.js.map +1 -1
- package/dist/core/notifications.d.ts +17 -0
- package/dist/core/notifications.d.ts.map +1 -0
- package/dist/core/notifications.js +2 -0
- package/dist/core/notifications.js.map +1 -0
- package/dist/core/packet-inject.d.ts.map +1 -1
- package/dist/core/packet-inject.js +2 -3
- package/dist/core/packet-inject.js.map +1 -1
- package/dist/core/packet-wire.d.ts +4 -4
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +94 -16
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +0 -1
- package/dist/core/plurnk-uri.d.ts.map +1 -1
- package/dist/core/plurnk-uri.js +1 -1
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/teaching.js +1 -1
- package/dist/core/teaching.js.map +1 -1
- package/dist/core/worker-ops.sql +1 -1
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +25 -14
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/DigestRender.d.ts +3 -1
- package/dist/digest/DigestRender.d.ts.map +1 -1
- package/dist/digest/DigestRender.js +235 -29
- package/dist/digest/DigestRender.js.map +1 -1
- package/dist/digest/DigestRequiem.d.ts.map +1 -1
- package/dist/digest/DigestRequiem.js +10 -14
- package/dist/digest/DigestRequiem.js.map +1 -1
- package/dist/digest/digest-paths.d.ts.map +1 -1
- package/dist/digest/digest-paths.js +5 -14
- package/dist/digest/digest-paths.js.map +1 -1
- package/dist/digest/digest-rows.d.ts +29 -1
- package/dist/digest/digest-rows.d.ts.map +1 -1
- package/dist/digest/digest-rows.js +1 -1
- package/dist/digest/digest-rows.js.map +1 -1
- package/dist/digest/digest.sql +12 -1
- package/dist/launch/Launch.d.ts +49 -0
- package/dist/launch/Launch.d.ts.map +1 -0
- package/dist/launch/Launch.js +179 -0
- package/dist/launch/Launch.js.map +1 -0
- package/dist/observe/spans.d.ts +1 -1
- package/dist/observe/spans.d.ts.map +1 -1
- package/dist/observe/spans.js +5 -64
- package/dist/observe/spans.js.map +1 -1
- package/dist/schemes/EffectPolicy.js +2 -2
- package/dist/schemes/EffectPolicy.js.map +1 -1
- package/dist/schemes/Exec.d.ts +2 -2
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +46 -22
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecScratch.d.ts +10 -0
- package/dist/schemes/ExecScratch.d.ts.map +1 -0
- package/dist/schemes/ExecScratch.js +43 -0
- package/dist/schemes/ExecScratch.js.map +1 -0
- package/dist/schemes/ExecutionInput.d.ts.map +1 -1
- package/dist/schemes/ExecutionInput.js +2 -5
- package/dist/schemes/ExecutionInput.js.map +1 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +44 -5
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +5 -4
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/TurnSource.js +2 -2
- package/dist/schemes/TurnSource.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +2 -3
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +2 -1
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-fts.d.ts +1 -0
- package/dist/schemes/_entry-fts.d.ts.map +1 -1
- package/dist/schemes/_entry-fts.js +34 -2
- package/dist/schemes/_entry-fts.js.map +1 -1
- package/dist/schemes/_entry-graph.d.ts.map +1 -1
- package/dist/schemes/_entry-graph.js +2 -6
- package/dist/schemes/_entry-graph.js.map +1 -1
- package/dist/schemes/_entry-manifest.js +1 -1
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +3 -2
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_path-scope.d.ts.map +1 -1
- package/dist/schemes/_path-scope.js +2 -3
- package/dist/schemes/_path-scope.js.map +1 -1
- package/dist/schemes/_search-index.d.ts.map +1 -1
- package/dist/schemes/_search-index.js +2 -8
- package/dist/schemes/_search-index.js.map +1 -1
- package/dist/schemes/exec-abort.js +1 -1
- package/dist/schemes/exec-abort.js.map +1 -1
- package/dist/schemes/exec-lifetime.d.ts.map +1 -1
- package/dist/schemes/exec-lifetime.js +2 -1
- package/dist/schemes/exec-lifetime.js.map +1 -1
- package/dist/server/ClientReads.d.ts +1 -1
- package/dist/server/ClientReads.d.ts.map +1 -1
- package/dist/server/ClientReads.js +1 -1
- package/dist/server/ClientReads.js.map +1 -1
- package/dist/server/Daemon.d.ts +35 -20
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +101 -61
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/DaemonModule.d.ts +2 -63
- package/dist/server/DaemonModule.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.d.ts +3 -3
- package/dist/server/DrainSupervisor.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.js +13 -4
- package/dist/server/DrainSupervisor.js.map +1 -1
- package/dist/server/EnvFunctionality.d.ts +2 -2
- package/dist/server/EnvFunctionality.d.ts.map +1 -1
- package/dist/server/EnvFunctionality.js +1 -1
- package/dist/server/EnvFunctionality.js.map +1 -1
- package/dist/server/Functionality.d.ts +2 -1
- package/dist/server/Functionality.d.ts.map +1 -1
- package/dist/server/Functionality.js.map +1 -1
- package/dist/server/MembersFunctionality.d.ts +2 -10
- package/dist/server/MembersFunctionality.d.ts.map +1 -1
- package/dist/server/MembersFunctionality.js +1 -1
- package/dist/server/MembersFunctionality.js.map +1 -1
- package/dist/server/SkillsFunctionality.d.ts +2 -1
- package/dist/server/SkillsFunctionality.d.ts.map +1 -1
- package/dist/server/SkillsFunctionality.js +1 -1
- package/dist/server/SkillsFunctionality.js.map +1 -1
- package/dist/server/WorkerModelResolver.d.ts +8 -8
- package/dist/server/WorkerModelResolver.d.ts.map +1 -1
- package/dist/server/WorkerModelResolver.js +46 -45
- package/dist/server/WorkerModelResolver.js.map +1 -1
- package/dist/server/WorkspaceResidency.d.ts +2 -1
- package/dist/server/WorkspaceResidency.d.ts.map +1 -1
- package/dist/server/WorkspaceResidency.js.map +1 -1
- package/dist/server/client-input.d.ts +2 -1
- package/dist/server/client-input.d.ts.map +1 -1
- package/dist/server/client-input.js +7 -0
- package/dist/server/client-input.js.map +1 -1
- package/dist/server/drain.sql +6 -6
- package/dist/server/envelope.d.ts +2 -9
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +2 -2
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/envelope.sql +12 -9
- package/dist/server/logEntry.d.ts +1 -31
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/loopDocs.js +1 -1
- package/dist/server/loopDocs.js.map +1 -1
- package/dist/server/model-catalog.js +3 -3
- package/dist/server/model-catalog.js.map +1 -1
- package/dist/server/model-route.d.ts +2 -2
- package/dist/server/model-route.d.ts.map +1 -1
- package/dist/server/model-route.js +5 -5
- package/dist/server/model-route.js.map +1 -1
- package/dist/server/module-discovery.d.ts.map +1 -1
- package/dist/server/module-discovery.js +4 -22
- package/dist/server/module-discovery.js.map +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +45 -7
- package/dist/service.js.map +1 -1
- package/dist/share/Share.d.ts +15 -0
- package/dist/share/Share.d.ts.map +1 -0
- package/dist/share/Share.js +106 -0
- package/dist/share/Share.js.map +1 -0
- package/dist/share/share.sql +6 -0
- package/docs/env.md +18 -0
- package/migrations/001_workspaces.sql +2 -2
- package/migrations/002_workers.sql +4 -6
- package/migrations/003_loops.sql +2 -2
- package/migrations/004_inference.sql +2 -2
- package/migrations/005_entries.sql +2 -2
- package/migrations/006_log.sql +2 -2
- package/migrations/007_subscriptions.sql +2 -2
- package/migrations/008_interactions.sql +2 -2
- package/migrations/009_effort.sql +7 -0
- package/migrations/010_outside_text.sql +41 -0
- package/migrations/011_settled.sql +133 -0
- package/package.json +46 -37
- package/dist/core/Knob.d.ts +0 -9
- package/dist/core/Knob.d.ts.map +0 -1
- package/dist/core/Knob.js +0 -50
- package/dist/core/Knob.js.map +0 -1
package/SPEC.md
CHANGED
|
@@ -92,7 +92,7 @@ Independent axes on entries and channels. Confusion across them is a recurring s
|
|
|
92
92
|
| Term | Meaning |
|
|
93
93
|
|---|---|
|
|
94
94
|
| **writer** | The identity authoring a write. One of `model \| client \| _plurnk \| plugin`. Carried on `ctx.writer` for schemes; engine enforces `manifest.writableBy`. |
|
|
95
|
-
| **origin** | Synonym for writer in log_entries (`log_entries.origin`).
|
|
95
|
+
| **origin** | Synonym for writer in log_entries (`log_entries.origin`). Synonym for writer. |
|
|
96
96
|
| **writable_by** | The set of writers a scheme accepts. Subset of `{model, client, _plurnk, plugin}`. Engine rejects writes outside the set with 403; the rejection is logged as the action-entry ({§subscriptions} action-entry-as-outcome). |
|
|
97
97
|
|
|
98
98
|
### Execution terms
|
|
@@ -224,6 +224,32 @@ before provider or capability initialization can perform external work. Every
|
|
|
224
224
|
later startup failure closes resources in reverse ownership order while
|
|
225
225
|
preserving the originating failure: daemon, observability, database, listener.
|
|
226
226
|
|
|
227
|
+
§startup-readiness-line **Readiness is one stdout line.** After the client interface is mounted
|
|
228
|
+
the service prints exactly one line, `plurnk-service agui=<url> db=<json string> route=<json string>`:
|
|
229
|
+
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
|
|
231
|
+
the URL as a URL and the strings as JSON; nothing else the service prints on stdout before it has
|
|
232
|
+
that prefix. Before the line the listener answers `503 service-starting`; after it,
|
|
233
|
+
`discover` is the identity check a launcher uses to tell this daemon from any other listener. A bind
|
|
234
|
+
failure is an exit with the originating address error and means *occupied*, not *foreign* — another
|
|
235
|
+
plurnk-service may be starting there, and only `discover` says which.
|
|
236
|
+
|
|
237
|
+
§daemon-launch **The service ships its own launcher; launchers own policy.** `@plurnk/plurnk-service/launch`
|
|
238
|
+
spawns a daemon argv with the caller's environment, an optional {§state-root}, host and port, and
|
|
239
|
+
resolves on the readiness line with the published address, database path, route and a `stop()` that
|
|
240
|
+
is SIGTERM, a stated grace, then SIGKILL, resolving when the process has ended. It holds no timing of
|
|
241
|
+
its own: the caller states the readiness timeout and the stop grace. A start that fails — spawn error,
|
|
242
|
+
exit before readiness, or timeout — is stopped and awaited before the failure is thrown with its kind
|
|
243
|
+
and both output streams; the helper never creates, keeps or removes state. A shared daemon survives
|
|
244
|
+
the launcher that started it (scheduled deliveries, other clients and inbound A2A depend on it); a
|
|
245
|
+
private daemon is its launcher's child and ends with it under managed shutdown. The launcher
|
|
246
|
+
spawns either: `lifetime: "private"` (the default) pipes both streams to the launcher; `lifetime:
|
|
247
|
+
"shared"` puts the daemon in its own process group with both streams appended to the caller's
|
|
248
|
+
`logFile`, reads readiness from that file, and releases the process once ready, so the launcher may
|
|
249
|
+
exit while the daemon runs on and no output accumulates in a launcher that has left; until
|
|
250
|
+
readiness the launcher owns it either way, and `stop()` ends it while the launcher lives. Shell and container
|
|
251
|
+
launchers consume the same contract by reading the line themselves.
|
|
252
|
+
|
|
227
253
|
## §actor-boundary Workers and workspace boundaries
|
|
228
254
|
|
|
229
255
|
```mermaid
|
|
@@ -352,7 +378,7 @@ direct-entry-plus-directory count; `-1` enables the ordinary markerless page;
|
|
|
352
378
|
unset / `0` disables previews. `log://` is absent because the current worker's
|
|
353
379
|
log already renders in present mode.
|
|
354
380
|
|
|
355
|
-
§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
|
|
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}.
|
|
356
382
|
|
|
357
383
|
Incoming messages publish once as inbound SEND rows in the first model turn
|
|
358
384
|
({§message-arrival}); initialization neither READs nor archives them. The turn
|
|
@@ -496,7 +522,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
|
|
|
496
522
|
| `READ` | existing literal name | Collect the named worker's deliverable. |
|
|
497
523
|
| `KILL` | existing literal name | Terminate the named worker or caller. |
|
|
498
524
|
|
|
499
|
-
- §worker-scheme-spawn **Spawn** — ```` ```WORK (worker://<name>)? ```` with a task body creates a new child worker (empty log) and starts it with that task on its first loop. WORK/FORK are the worker-creation verbs: EDIT is file/entry only, so EDIT on the bare worker entity is a **400** steering to WORK/FORK — the entity is not an entry. Names remain unique within a workspace for the lifetime of retained Worker rows, including after termination. An existing name returns 409; concurrent claims cannot redirect a published address or expose a raw uniqueness failure.
|
|
525
|
+
- §worker-scheme-spawn **Spawn** — ```` ```WORK (worker://<name>)? ```` with a task body creates a new child worker (empty log) and starts it with that task on its first loop. WORK/FORK are the worker-creation verbs: EDIT is file/entry only, so EDIT on the bare worker entity is a **400** steering to WORK/FORK — the entity is not an entry. Names remain unique within a workspace for the lifetime of retained Worker rows, including after termination. An existing name returns 409 whose receipt names the working forms — `SEND (worker://<name>)` with the task as the body to give the live worker more work, or a name no worker holds for another; concurrent claims cannot redirect a published address or expose a raw uniqueness failure.
|
|
500
526
|
- §worker-spawn-prompt-resource **The spawn slot is overloaded by scheme.** A `worker://` path is
|
|
501
527
|
the child's address and keeps the address rules ({§worker-control-addressing}). A path of any
|
|
502
528
|
other scheme is the child's prompt resource: it is read whole (`<1,-1>`) under the caller's read capabilities, composed with
|
|
@@ -506,7 +532,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
|
|
|
506
532
|
resource with no body is `422 spawn-prompt-empty`. Naming the child and giving a resource in one
|
|
507
533
|
statement is not expressible; the body can READ the resource instead. Taught in the deep
|
|
508
534
|
reference only.
|
|
509
|
-
- §worker-scheme-irc **irc** — ```` ```SEND (worker://<name>) ```` with a message body or attachments delivers it to an existing worker, the **voice door** ({§actor-boundary-two-doors}): an active worker folds it into its next turn, an idle one wakes ({§actor-boundary-passive-wake}). No text beyond whitespace and no attachments is 422 `message-empty`, before message admission or recipient loop creation. Nonempty text is delivered verbatim; attachment-only delivery uses {§send-resource-attachments}. A fresh receiving loop retains that worker's durable model, spawn override, and
|
|
535
|
+
- §worker-scheme-irc **irc** — ```` ```SEND (worker://<name>) ```` with a message body or attachments delivers it to an existing worker, the **voice door** ({§actor-boundary-two-doors}): an active worker folds it into its next turn, an idle one wakes ({§actor-boundary-passive-wake}). No text beyond whitespace and no attachments is 422 `message-empty`, before message admission or recipient loop creation. Nonempty text is delivered verbatim; attachment-only delivery uses {§send-resource-attachments}. A fresh receiving loop retains that worker's durable model, spawn override, and effort; the sender and daemon default do not re-select it. The caller addresses itself by its literal name; a literal name with no worker in the workspace is 404.
|
|
510
536
|
- §worker-scheme-fork **Fork** — ```` ```FORK (worker://<name>)? ```` with a task body branches the
|
|
511
537
|
current worker into a **named** child: its log is deep-copied
|
|
512
538
|
({§machine-processes-fork-copies-the-log}), which continues with `task`; the
|
|
@@ -541,8 +567,7 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
|
|
|
541
567
|
missing name is 404. The model therefore reads the worker itself for its
|
|
542
568
|
outcome or a wait rather than guessing a scratch path to "check on" it.
|
|
543
569
|
- §worker-loop-result `ops://<name>/<sequence>` selects one worker-local positive safe-integer
|
|
544
|
-
loop sequence ({§loop-answer};
|
|
545
|
-
both what a loop said and how it ended). It is a read-only resource, not an actor control
|
|
570
|
+
loop sequence ({§loop-answer}; one address serves both what a loop said and how it ended). It is a read-only resource, not an actor control
|
|
546
571
|
address: READ, FIND and COPY use ordinary projections; EDIT, MOVE-source, and KILL cannot change
|
|
547
572
|
it. No query, userinfo, or port is accepted. A coordinate that is not a positive safe integer is
|
|
548
573
|
400; a missing loop 404; a loop that has not answered and has not concluded 425. A concluded
|
|
@@ -575,19 +600,22 @@ Every admitted authority is a literal `workers.name`; self-addressing uses the c
|
|
|
575
600
|
`"parent": null` at a root, so a worker never infers its rank from silence.
|
|
576
601
|
- §packet-current-turn **The packet says who and which turn, below the log.** The
|
|
577
602
|
`## Worker` block is the first section after the log, carrying
|
|
578
|
-
`{"path": "worker://<name>", "parent": <address or null>, "loop": L, "turn": T}`:
|
|
579
|
-
whose child it is,
|
|
580
|
-
and `ops://<worker>/L/T` are the model's own and `log:///L/T/*` its rows
|
|
581
|
-
|
|
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
|
|
582
609
|
changes every turn, so nothing of it precedes the log, and the packet carries no date, time
|
|
583
|
-
or zone anywhere
|
|
584
|
-
|
|
585
|
-
documented, not taught.
|
|
610
|
+
or zone anywhere. The coordinates and the last program's address only; the other source
|
|
611
|
+
addresses stay documented, not taught.
|
|
586
612
|
|
|
587
613
|
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.
|
|
588
614
|
|
|
589
615
|
## §membership File membership and project roots
|
|
590
616
|
|
|
617
|
+
A file is a member of a workspace when Git tracks it or a members definition includes it and none excludes it; only members can be READ, found by FIND, or changed by EDIT, and every member path resolves under the project root.
|
|
618
|
+
|
|
591
619
|
The project-file path has two explicit reconciliation gates. Internal entries do
|
|
592
620
|
not participate in this disk loop.
|
|
593
621
|
|
|
@@ -642,8 +670,7 @@ and never re-fetch a match.
|
|
|
642
670
|
files by an exact creation record with recorded provenance, and never by `git add`.
|
|
643
671
|
Changing this clause, the register, or the composition is an operator ruling recorded
|
|
644
672
|
on the issue that lands it — never an implementation convenience, never a side effect
|
|
645
|
-
of making a file visible to solve the problem at hand.
|
|
646
|
-
"untracked-but-not-ignored" ambient admission is retired.
|
|
673
|
+
of making a file visible to solve the problem at hand.
|
|
647
674
|
- §membership-model-universe **The exception register — files in the model's universe.**
|
|
648
675
|
Admitted by exact creation records (`source: "create"`, origin `constraint`), never
|
|
649
676
|
staged: (1) a file an accepted EDIT creates; (2) a COPY/MOVE destination
|
|
@@ -656,7 +683,7 @@ and never re-fetch a match.
|
|
|
656
683
|
exclusions — an `AGENTS.md` the repository ignores or an exclusion matches
|
|
657
684
|
is not projected. (4) A definition the model proposes through the `members`
|
|
658
685
|
family ({§members-functionality}), admitted only under the operator's ceiling
|
|
659
|
-
`PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` (
|
|
686
|
+
`PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` ({§members-model-scope}), projected with source
|
|
660
687
|
`model`, and never admitted past the repository's ignore rules or an exclusion.
|
|
661
688
|
Nothing else.
|
|
662
689
|
- §membership-git-membership The workspace owns the Git repository containing
|
|
@@ -713,7 +740,7 @@ identity, and terminal disposition without exposing raw bytes or a base64 lane.
|
|
|
713
740
|
workspace.** `PLURNK_SERVICE_FILE_MATERIALIZE_MAX_BYTES` is a required positive
|
|
714
741
|
byte ceiling over one disk source before Core reads it into the canonical file
|
|
715
742
|
snapshot. Its valid range is `1..104857600`, bounded by the channel storage
|
|
716
|
-
contract
|
|
743
|
+
contract. An oversized path remains a real member
|
|
717
744
|
with an empty body channel carrying a durable 413 producer result; no diagnostic
|
|
718
745
|
sentinel impersonates file content. READ therefore names the path, observed bytes,
|
|
719
746
|
ceiling, and recovery through the ordinary result contract, while EDIT returns the
|
|
@@ -769,7 +796,7 @@ The version travels *with the proposal*, never re-read from the entry at accept:
|
|
|
769
796
|
|
|
770
797
|
The CAS is the **hard backstop**, at the moment of writing, on every accept path. It composes with the model-facing {§line-anchors}: an anchor rejects a target whose relevant neighborhood changed before dispatch, while the CAS refuses to write against a snapshot disk left after proposal. An unanchored edit deliberately claims no pre-dispatch stale-view guarantee.
|
|
771
798
|
|
|
772
|
-
§membership-git-flags **Permission flags.** Service-wide Git admission comes from {§operator-config-git-ceiling}. `PLURNK_SERVICE_GIT_AUTO=1`
|
|
799
|
+
§membership-git-flags **Permission flags.** Service-wide Git admission comes from {§operator-config-git-ceiling}. `PLURNK_SERVICE_GIT_AUTO=1` includes the repository containing `project_root`; `=0` disables automatic Git membership, leaving member definitions as the only membership source. `ALLOWED` gates `AUTO`.
|
|
773
800
|
|
|
774
801
|
**Rationale.** Workspace is the right scope unit and the containing Git repository is its ordinary development boundary. Membership curation is tiered: Git bounds it by tracking, the client supersedes by overlay, and the model curates its render by READ/KILL. Supporting several independent repositories as one world would require Plurnk-owned topology, synchronization, and model teaching that Git already solves cleanly by treating them as separate workspaces.
|
|
775
802
|
|
|
@@ -859,8 +886,8 @@ waited 7.5 h, #703) is a number rather than a gap.
|
|
|
859
886
|
§digest-storage **The digest states the file's health.** Beside the database path it
|
|
860
887
|
reports the file size, the free pages it holds, its `auto_vacuum` mode, and the six
|
|
861
888
|
largest tables and indexes by allocated bytes (`dbstat`), so growth is a number in
|
|
862
|
-
every digest (#764). The digest reads loops as stored, so
|
|
863
|
-
|
|
889
|
+
every digest (#764). The digest reads loops as stored, so it tolerates databases
|
|
890
|
+
missing later lifecycle columns.
|
|
864
891
|
|
|
865
892
|
§loop-execution-allowance **One task has one execution allowance.** The first
|
|
866
893
|
execution snapshots `PLURNK_SERVICE_LOOP_TIMEOUT` on the loop. Active segments
|
|
@@ -1073,6 +1100,15 @@ single operation captures the equivalent boundary before dispatch. This limits
|
|
|
1073
1100
|
only log-row selection: operation phasing and same-turn resource effects retain
|
|
1074
1101
|
their ordinary contracts.
|
|
1075
1102
|
|
|
1103
|
+
### §engine-notifications One bundle of daemon callbacks
|
|
1104
|
+
|
|
1105
|
+
The daemon's observation callbacks — stream, reasoning, outside-text and packet
|
|
1106
|
+
events, worker wake, inject and cancel, operation settlement, notices — are one
|
|
1107
|
+
declared bundle (`EngineNotifications`). The Engine receives them flat, carries
|
|
1108
|
+
them as one value, and every consumer reads the callbacks it uses from that
|
|
1109
|
+
value; adding one is a declaration and a use, never an edit to the constructors
|
|
1110
|
+
between. Scheme contexts still expose the specific callbacks a handler may call.
|
|
1111
|
+
|
|
1076
1112
|
### §engine-rails Engine rails
|
|
1077
1113
|
|
|
1078
1114
|
After each admitted turn, one inline verdict decides whether the loop continues.
|
|
@@ -1081,7 +1117,7 @@ These are the complete strike sources:
|
|
|
1081
1117
|
|
|
1082
1118
|
| Strike source | Exact trigger | Model-visible occurrence |
|
|
1083
1119
|
|---------------------|------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
|
|
1084
|
-
| Hard result | An admitted non-execution operation or bounded parse-error status is `>= 400`, except the soft set `404`, `409`, `416`, `425`, `501
|
|
1120
|
+
| Hard result | An admitted non-execution operation or bounded parse-error status is `>= 400`, except the soft set `404`, `409`, `416`, `425`, `501`, in a turn where no operation succeeded ({§strike-progress-immunity}). | The originating failure row. |
|
|
1085
1121
|
| Cycle | The executed operations and their observed results repeat under {§engine-cycle-evidence}. | None; cycle detection itself is private engine accounting. |
|
|
1086
1122
|
| Empty turn | An admitted turn with no authored response operation ({§empty-turn}). | The turn's `422` error row, and its reasoning read back ({§reasoning-empty-turn-read}). |
|
|
1087
1123
|
|
|
@@ -1114,8 +1150,8 @@ effects. Ordinary contract strikes and operator budgets remain independent.
|
|
|
1114
1150
|
call ({§bare-inference}) takes the same recovery as the loop's own inference: each re-issue
|
|
1115
1151
|
is its own model call on the ledger, and a spent window leaves the operation's result as the
|
|
1116
1152
|
provider's exact failure. When a model
|
|
1117
|
-
call fails with a
|
|
1118
|
-
the provider's own retries, the turn records the exact Problem as a `_plurnk` row,
|
|
1153
|
+
call fails with a kind the provider marks `retryable` ({§provider-retryable-truth}: network
|
|
1154
|
+
failure, rate limit, deadline, interrupted resource, dropped output) after the provider's own retries, the turn records the exact Problem as a `_plurnk` row,
|
|
1119
1155
|
notices the client (`engine:provider` / `provider_unavailable`), waits with
|
|
1120
1156
|
exponential backoff (`PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF`, doubling up to
|
|
1121
1157
|
`PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF_MAX`), and re-issues the same call against the exact frozen model
|
|
@@ -1134,18 +1170,28 @@ instead, because parking stops the execution clock and no wake would ever arrive
|
|
|
1134
1170
|
({§operator-config-loop-timeout}), or a non-recoverable provider Problem (refusal,
|
|
1135
1171
|
authorization, quota, an invalid response) settles a loop on a provider failure.
|
|
1136
1172
|
|
|
1137
|
-
**Contract Strikes
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1173
|
+
**Contract Strikes**: every turn with one or more contract violations earns a
|
|
1174
|
+
strike; a turn without any contract violations clears the strikes.
|
|
1175
|
+
|
|
1176
|
+
§strike-progress-immunity **A turn in which at least one operation succeeded is
|
|
1177
|
+
immune to the hard-result source** (operator ruling, 2026-09-26, #853): its hard
|
|
1178
|
+
failures keep their exact rows, but the turn counts as progress — it earns no strike
|
|
1179
|
+
and clears the streak. A successful operation is any admitted operation that acts on the
|
|
1180
|
+
task — executions included — whose result status is `< 400`. Operations that steer the
|
|
1181
|
+
loop never qualify: NOTE (it cannot fail; reasoning NOTEs are filed as NOTEs, and outside
|
|
1182
|
+
text is no operation at all, {§outside-text}), WAIT, a parameterless KILL and a targetless SEND; a turn
|
|
1183
|
+
of failures beside a WAIT still strikes. The cycle source is not exempted: a repeating
|
|
1184
|
+
turn's operations succeed by construction, and the backstop exists to catch exactly
|
|
1185
|
+
that ({§engine-cycle-evidence}).
|
|
1186
|
+
The streak counts consecutive violating turns; `PLURNK_SERVICE_MAX_STRIKES` is the
|
|
1187
|
+
threshold, crossed ON the strike that reaches it; the crossing turn terminates at **508
|
|
1142
1188
|
Loop Detected** when cycle-detected, otherwise **500**.
|
|
1143
1189
|
|
|
1144
1190
|
The contracts, and the violation of each that strikes:
|
|
1145
1191
|
|
|
1146
1192
|
| Contract | Violation that strikes |
|
|
1147
1193
|
|---|---|
|
|
1148
|
-
| operation contract | a hard operation failure (status ≥ 400) in an admitted turn — soft statuses below excluded |
|
|
1194
|
+
| operation contract | a hard operation failure (status ≥ 400) in an admitted turn where no operation succeeded ({§strike-progress-immunity}) — soft statuses below excluded |
|
|
1149
1195
|
| review contract | none: an eligible final response joins live obligations ({§completion-joins-live-work}) or continues to observe results ({§completion-defers-to-results}) |
|
|
1150
1196
|
| progress contract | a detected operation cycle (`MIN_CYCLES` × period), or an admitted turn with no operation ({§empty-turn}) |
|
|
1151
1197
|
| frame contract | emission attempts exhausted with no admissible turn |
|
|
@@ -1203,10 +1249,10 @@ Author-facing contract: [`@plurnk/plurnk-providers`](../plurnk-providers/SPEC.md
|
|
|
1203
1249
|
Three current entry points:
|
|
1204
1250
|
|
|
1205
1251
|
- §provider-surface-generate `provider.generate(args)` — once per logical model call. An emission attempt supplies the complete packet messages, worker/turn coordinates, generation envelope, optional local grammar, first-party metadata, and `callKind: "emission"`. A BARE inference supplies only one user message containing its resolved prompt plus non-prompt call identity and accounting metadata, including `callKind: "bare"` ({§bare-inference} {§provider-call-kind}). Both receive a durable physical-request observer; provider-owned retry and failover may issue several ordered requests beneath either call. A successful `ProviderResponse` reaches its call-specific consumer; a `ProviderError.attempt` remains failed response evidence under {§provider-interrupted-attempt}. Core persists normalized response evidence separately from physical accounting.
|
|
1206
|
-
- §provider-surface-capacity `provider.assessRequestCapacity(messages, maxOutputTokens?, signal?)` — provider
|
|
1207
|
-
- §provider-surface-prompt-measurement `provider.countPromptTokens(messages, signal)` — the cancellable complete-request measurement primitive used by provider capacity assessment, with
|
|
1252
|
+
- §provider-surface-capacity `provider.assessRequestCapacity(messages, maxOutputTokens?, signal?)` — the provider's verdict under {§provider-capacity-admission}; core acts on it under {§tokenomics-context-envelope-admission}. `generate` performs this assessment for its exact request and preserves the evidence on success and capacity failure.
|
|
1253
|
+
- §provider-surface-prompt-measurement `provider.countPromptTokens(messages, signal)` — the cancellable complete-request measurement primitive used by provider capacity assessment, with the provenance of {§provider-prompt-measurement}. Core never substitutes this physical fact for its curation ruler.
|
|
1208
1254
|
|
|
1209
|
-
§provider-surface-identity Provider capacity and identity are immutable for one instance
|
|
1255
|
+
§provider-surface-identity Provider capacity and identity are immutable for one instance ({§provider-interface}); core invents no stand-in for a `null` fact. `inputCapacity` feeds {§tokenomics}; a call's response grant may expand under {§provider-flexed-allowance}; `model` identifies persisted turn/provider evidence; local GBNF admission also consumes `constrainsOutput` ({§grammar-configuration-admission}).
|
|
1210
1256
|
|
|
1211
1257
|
§inference-ledger **Logical inference is provider-neutral and physical requests have one ledger.** Every `inference_calls` identity belongs to a workspace and a model/inference turn, records its ordered kind and request model, and has a forward-only lifecycle. Its `model_calls` specialization owns normalized failure and capacity evidence; the response body is `model_call_responses`, present or retired under {§retention-policy}; one observation view records evidence, body and close together, and a settled call refuses a second observation. Only an `emission` has `turn_attempts` admission evidence; `bare` calls retain independent results. Every physical request is an ordered `provider_requests` child opened before I/O and settled once. Calls contribute to turn, loop, worker, and workspace accounting; only an emission supplies the latest context gauge.
|
|
1212
1258
|
|
|
@@ -1214,7 +1260,7 @@ Three current entry points:
|
|
|
1214
1260
|
|
|
1215
1261
|
### Engine → provider guarantees
|
|
1216
1262
|
|
|
1217
|
-
- `messages` is a complete prompt (the section list, pre-assembled into the
|
|
1263
|
+
- `messages` is a complete prompt (the section list, pre-assembled into the wire envelope, {§packet-wire-envelope}). Provider does not reorder.
|
|
1218
1264
|
- §provider-guarantees-signal-wired `signal` is wired to the worker's AbortController.
|
|
1219
1265
|
- §provider-guarantees-serial-attempts Emission attempts for one engine turn are serial. They reuse the exact messages, coordinates, generation limits, and strike state; two attempts for that turn never overlap.
|
|
1220
1266
|
- BARE calls admitted by one turn launch as one parallel batch; each call retains independent observer and failure state, and the engine awaits the complete batch before committing results in authored order ({§bare-inference}).
|
|
@@ -1230,9 +1276,13 @@ The parser owns its boundaries; core admits determinate work and exposes its fai
|
|
|
1230
1276
|
| Parsed response | Admission |
|
|
1231
1277
|
|---|---|
|
|
1232
1278
|
| Bounded program, including malformed operations | Admit valid operations and record parser failures; with no authored operation, apply {§empty-turn}. |
|
|
1233
|
-
| Outside response text |
|
|
1279
|
+
| Outside response text | Store it as the turn's `outside` source under {§outside-text}; never a row, never delivered, never completion. |
|
|
1234
1280
|
| Lost boundary after a closed operation | Admit the closed operations and record the boundary diagnostic under {§unparsed-tail-boundary}. |
|
|
1235
1281
|
| 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}). |
|
|
1283
|
+
| 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
|
+
|
|
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.
|
|
1236
1286
|
|
|
1237
1287
|
Warnings and closer recovery ({§closer-fallback}) do not reject. `finish=length`
|
|
1238
1288
|
discloses truncation and precludes completion; it is not independently a rejection.
|
|
@@ -1242,7 +1292,7 @@ optional, with no omission warning or invented operation ({§turn-shape}).
|
|
|
1242
1292
|
|
|
1243
1293
|
§safe-uri-target-groups After source and authored-command admission, Core tolerates one target group on READ or KILL only when splitting its raw target at top-level comma or whitespace separators produces at least two members and every member independently parses as an explicit `scheme://` URI. Request-metadata blocks are opaque to this split. Each member becomes one ordinary statement with an independent dispatch outcome and log row, in authored member order at that operation's position under {§op-execution-order}. Otherwise the target remains exactly singular, including local filenames containing spaces or commas. The stored `turnOps` and authored command count remain unexpanded, and no other operation admits target groups.
|
|
1244
1294
|
|
|
1245
|
-
Core retries a rejected emission against the exact same packet beneath the same engine turn, up to `PLURNK_SERVICE_EMISSION_ATTEMPTS`. Rejected bytes never dispatch or reach the engine strike rail. Before each `generate`, Core opens one durable logical `inference_calls` row with its `model_calls` specialization and emission-specific `turn_attempts` admission row. A call that ends without response evidence leaves that admission row unclassified (`accepted IS NULL`) and does not consume the emission-attempt ceiling. Beneath the logical call, every provider observer invocation opens one cardinal `provider_requests` occurrence immediately before physical I/O and settles it as response or error. Adapter retries and capacity failover append requests in issue order; a response-less failure therefore remains an accounted occurrence rather than disappearing. Normalized response evidence is durable before parser classification and does not duplicate the separately owned accounting. The accepted exchange alone extends `turns.packet` with response evidence; every physical request remains in turn and loop accounting, while the context gauge reads the latest settled emission request on the latest turn. Digest exposes rejected response evidence as
|
|
1295
|
+
Core retries a rejected emission against the exact same packet beneath the same engine turn, up to `PLURNK_SERVICE_EMISSION_ATTEMPTS`. Rejected bytes never dispatch or reach the engine strike rail. Before each `generate`, Core opens one durable logical `inference_calls` row with its `model_calls` specialization and emission-specific `turn_attempts` admission row. A call that ends without response evidence leaves that admission row unclassified (`accepted IS NULL`) and does not consume the emission-attempt ceiling. Beneath the logical call, every provider observer invocation opens one cardinal `provider_requests` occurrence immediately before physical I/O and settles it as response or error. Adapter retries and capacity failover append requests in issue order; a response-less failure therefore remains an accounted occurrence rather than disappearing. Normalized response evidence is durable before parser classification and does not duplicate the separately owned accounting. The accepted exchange alone extends `turns.packet` with response evidence; every physical request remains in turn and loop accounting, while the context gauge reads the latest settled emission request on the latest turn. Digest exposes rejected response evidence as `<stem>.attemptNNN.rejected.*` and every physical request in its machine-readable ledger.
|
|
1246
1296
|
|
|
1247
1297
|
When the loop continues after exhaustion under {§invalid-emission-attempts}, the next ordinary turn's packet projects the latest rejected response visibly from a durably body-suppressed emission-attempt item under {§rejected-emission-entry} and carries one transient `invalid_emission` Notice: `Response rejected before dispatch; no operations were performed.` followed by `Parser: <the latest attempt's first diagnostic>` with its `content-offset` position — the model sees why, at which line, against its own projected text. The Notice states only observed admission facts; it does not classify the response as unrecoverable, infer why generation ended, or prescribe intent beyond the parser-owned diagnostic. Attempt count and rail state never become model-facing. The recovery turn has its own honestly stored packet and its configured private same-packet attempts. The packet-local projection never changes the row's curation state, so no later packet repeats that malformed body unless the model explicitly READs its exact address. Admission clears the recovery projection; another exhaustion replaces it with the latest rejected response if the loop continues.
|
|
1248
1298
|
|
|
@@ -1335,7 +1385,7 @@ whose text is read once per daemon and handed to the provider verbatim
|
|
|
1335
1385
|
name with no path separator is refused with an error that says so, and an
|
|
1336
1386
|
unreadable file fails the constrained generation loudly; neither ever silently
|
|
1337
1387
|
becomes unconstrained. Nothing generates, validates, or grades a grammar, and
|
|
1338
|
-
no
|
|
1388
|
+
no effort is implied by one. The turn records transport as evidence:
|
|
1339
1389
|
`railsAttached: "client"` when the provider reports it sent the grammar, or
|
|
1340
1390
|
`"withheld"` when it reports it did not ({§provider-grammar-evidence}); there
|
|
1341
1391
|
is no verdict key and no notice about conformance, because the parser's
|
|
@@ -1344,7 +1394,7 @@ adds no grammar state at all.
|
|
|
1344
1394
|
|
|
1345
1395
|
```dotenv
|
|
1346
1396
|
PLURNK_MODEL_gemma=openai/macher.gguf
|
|
1347
|
-
|
|
1397
|
+
PLURNK_MODEL_flash=openrouter/deepseek/deepseek-v4.1-flash
|
|
1348
1398
|
PLURNK_MODEL=gemma
|
|
1349
1399
|
```
|
|
1350
1400
|
|
|
@@ -1419,6 +1469,8 @@ historical execution. No address grants ownership or access restrictions.
|
|
|
1419
1469
|
|
|
1420
1470
|
§fs-visibility-grantors **File visibility has two represented grantors; Plurnk never invents a private third one.** A file member is admitted by the active Git substrate or by an ordinary `include` row of the overlay. An `include` is either a projected `members` definition ({§members-projection}) or the exact, inspectable record of an accepted creation ({§fs-create-record}); both resolve to `constraint` membership. AGENTS.md remains auto-pulled as POLICY ({§policy-sections}), deliberately not a file member. A physically existing path that neither Git nor an `include` admits does not exist for the model and cannot be overwritten.
|
|
1421
1471
|
|
|
1472
|
+
### File scheme: creation and misses
|
|
1473
|
+
|
|
1422
1474
|
§fs-write-surface **The write surface — one admission and incorporation path.** Existing writes remain membership-gated. An absent path additionally crosses the effective creation scope and the complete constraint/Git policy before a proposal is issued. EDIT, COPY destinations, and MOVE destinations use this same path regardless of whether the producer is a model, client, plugin, or `_plurnk`. A COPY or MOVE destination scope on an absent channel resolves against its empty pre-mutation value under {§empty-mutation-scope}; a valid scope creates the channel with the selected source as its complete value. A coordinate outside that empty value is 416. Binary scopes remain numeric byte positions or ranges under {§binary-parity}.
|
|
1423
1475
|
|
|
1424
1476
|
| Case | Required admission | Accepted result |
|
|
@@ -1474,9 +1526,9 @@ Every fact names the canonical key, never the host root or an echo of the
|
|
|
1474
1526
|
model's spelling. These classes let a caller distinguish a wrong address, an
|
|
1475
1527
|
invalid range, read-only authority, and occupied hidden state without guessing.
|
|
1476
1528
|
|
|
1477
|
-
§membership-read-refusal **A file miss speaks of membership, never of the disk.** A file is read only as a member, so every `file` miss — READ, FIND of an exact path, KILL, a COPY or MOVE source — is 404 `entry-not-found`, `No member of this workspace is at '<key>'
|
|
1529
|
+
§membership-read-refusal **A file miss speaks of membership, never of the disk.** A file is read only as a member, so every `file` miss — READ, FIND of an exact path, KILL, a COPY or MOVE source — is 404 `entry-not-found`, `No member of this workspace is at '<key>'.` Recovery treats path correction with FIND, creation with EDIT, and admission with `members (add)` as alternatives; it must not presume a missing READ requires creation or admission. Admission takes a `{"glob": "<path>"}` body. The sentence is about the address and is true whether or not a file is there: it neither claims absence nor hints at presence. Beyond the root the engine does not look at the disk at all, so two reads of `../` paths differ only in the name they echo. Inside the root occupancy is not secret ({§fs-write-nonmember}), so an exact-path READ of a path that exists on disk but is not a member says so instead — 404 `entry-not-member`, `'<key>' exists on disk but is not a member of this workspace.`, with admission-only recovery. Occupancy may surface there; content never does ({§membership}).
|
|
1478
1530
|
|
|
1479
|
-
§
|
|
1531
|
+
§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.
|
|
1480
1532
|
|
|
1481
1533
|
### §scheme-manifest Manifest
|
|
1482
1534
|
|
|
@@ -1484,10 +1536,9 @@ invalid range, read-only authority, and occupied hidden state without guessing.
|
|
|
1484
1536
|
|
|
1485
1537
|
### §crud CRUD primitives
|
|
1486
1538
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
own a more specific operation. A stored-entry publication atomically upserts
|
|
1539
|
+
Core implements the `ctx.entries` capability of {§scheme-ctx-entries} and drives
|
|
1540
|
+
it for COPY/MOVE/KILL orchestration when a scheme does not own a more specific
|
|
1541
|
+
operation. A stored-entry publication atomically upserts
|
|
1491
1542
|
one workspace identity, metadata, and its complete channel set. Concurrent
|
|
1492
1543
|
publications expose one complete result, never a mix of channels; a failed
|
|
1493
1544
|
publication leaves the prior entry unchanged. Omitted attributes preserve the
|
|
@@ -1535,7 +1586,7 @@ Registration precedes loop affinity:
|
|
|
1535
1586
|
- §anchor-offset **An anchor offset is tolerated, never taught (#749).** A line mark may carry an offset from its anchor (`@abcde+1`, `@abcde-2`), and a bare `+N` after an anchor counts from that anchor (`<@abcde,+1>`). The anchor resolves as usual and the offset is added; a result before line 1 is an invalid mark, and past the end is the ordinary range refusal. Continuity and current-anchor preconditions check the anchor's own line. No teaching text, scope table or receipt mentions offsets; `plurnk.md` keeps its two anchor forms. A bare `+N` with no anchor before it is refused as before.
|
|
1536
1587
|
- §edit-batch **One compound operation may require atomic splices.** The scheme's `editBatch` primitive validates all supplied numeric edits against one snapshot and commits one revision or none. Core supplies one statement for an authored EDIT; same-resource MOVE can supply multiple splices as one operation. This primitive does not group separate authored operations. Its replacement, insertion, conflict, and receipt rules remain owned by the shared Slicer.
|
|
1537
1588
|
- §edit-batch-receipt **A refusal describes its own unapplied work.** An anchor collision lists every distinct unresolved anchor in that EDIT, including both range endpoints, in `unresolvedAnchors` (`anchor`, `kind: missing | ambiguous`, and matching `lines` when ambiguous). Missing is not proof of earlier validity or subsequent change. It carries `editCount: 1`, `applied: 0`, and recovery directing a READ for current coordinates; it makes no claim about other operations. A refused compound splice batch lists all conflicting pairs in `conflicts`, non-conflicting regions in `cleanRegions`, its first pair in `conflictingRegions`, and its own `editCount` and `applied: 0`.
|
|
1538
|
-
- §edit-batch-merges **Normalizations require evidence and a receipt.** An EDIT body carrying only this resource's published
|
|
1589
|
+
- §edit-batch-merges **Normalizations require evidence and a receipt.** An EDIT body carrying only this resource's published `L<@xxxxx>` prefixes (the number right-aligned) is stripped when those prefixes verify against current anchors or this worker's preserved READ receipts (`rendered-prefix-stripped`); otherwise it remains literal content (`rendered-prefix-unverified`). Within a single atomic splice batch, the Slicer can deduplicate identical regions/bodies, concatenate same-boundary insertions, assign a shared endpoint to the sole body reproducing that line, or relocate an inner change when its original content occurs exactly once in the outer body. An already-applied inner body can be dropped. Unevidenced overlap remains a collision. These batch resolutions never reinterpret separate authored EDITs. Applied normalizations carry their exact merge facts and a notice; receipts describe only the applied effects.
|
|
1539
1590
|
|
|
1540
1591
|
### Cross-scheme orchestration
|
|
1541
1592
|
|
|
@@ -1703,12 +1754,11 @@ empty setting excludes nothing, and the first match is the observable reason.
|
|
|
1703
1754
|
|
|
1704
1755
|
§search-size-bound Every search subject, whatever its scheme, is also bounded
|
|
1705
1756
|
by size when the operator sets one: a body longer than
|
|
1706
|
-
`PLURNK_SERVICE_SEARCH_MAX_BYTES` (
|
|
1757
|
+
`PLURNK_SERVICE_SEARCH_MAX_BYTES` (empty = unbounded) is `excluded` with the reason `larger than N bytes`, before
|
|
1707
1758
|
its body is read. It is neither parsed for symbols nor full-text indexed; READ,
|
|
1708
1759
|
FIND by path, and membership are unaffected. The reason joins the derivation
|
|
1709
1760
|
identity, so changing the bound re-derives the affected bodies and retention
|
|
1710
|
-
collects what they leave
|
|
1711
|
-
tokenizer vocabularies (up to 31 MB each) as full text.
|
|
1761
|
+
collects what they leave (#729).
|
|
1712
1762
|
|
|
1713
1763
|
A match produces the `excluded` derivation disposition and suppresses graph
|
|
1714
1764
|
and FTS while leaving the stored channel and direct READ unchanged. The
|
|
@@ -1896,9 +1946,11 @@ anchors from the complete canonical selected channel before applying the
|
|
|
1896
1946
|
authored text slice; its durable result retains the canonical derivation
|
|
1897
1947
|
identity and anchors aligned with returned lines. Packet rendering right-aligns
|
|
1898
1948
|
`L` to the decimal width of the complete canonical selected channel's final
|
|
1899
|
-
addressable line and emits
|
|
1900
|
-
|
|
1901
|
-
|
|
1949
|
+
addressable line and emits `L<@xxxxx><content>` with `L` right-aligned to that
|
|
1950
|
+
width, the scope literal as the delimiter, the content beginning after `>`. The anchor
|
|
1951
|
+
stands against its own text and never opens the row after the previous line's text —
|
|
1952
|
+
the placement that reads correctly at long context on every model measured (#893); a
|
|
1953
|
+
source line therefore retains the same prefix across projections of one revision.
|
|
1902
1954
|
An explicit default-channel fragment and its fragmentless spelling share that
|
|
1903
1955
|
identity; a selected non-default channel retains its canonical `#channel`.
|
|
1904
1956
|
|
|
@@ -1914,8 +1966,7 @@ range. COPY/MOVE mutation owners retain
|
|
|
1914
1966
|
the resolved endpoint neighborhoods as compare-and-swap preconditions. There is
|
|
1915
1967
|
no revision sidecar or fuzzy relocation. A range authenticates both endpoint
|
|
1916
1968
|
neighborhoods, so every line of a range up to `2C + 2` lines is covered; a
|
|
1917
|
-
longer range retains an unauthenticated interior gap.
|
|
1918
|
-
covers ranges through six lines.
|
|
1969
|
+
longer range retains an unauthenticated interior gap.
|
|
1919
1970
|
|
|
1920
1971
|
### §edit EDIT
|
|
1921
1972
|
|
|
@@ -2012,8 +2063,15 @@ READ is the one fan-out core performs ({§read-fan-out}).
|
|
|
2012
2063
|
result carries `matched`, the count of selected lines inside the scope. Zero
|
|
2013
2064
|
matches is an empty read (204, `matched: 0`), never a failure. A full-text
|
|
2014
2065
|
(`~`) or graph (`&`) pattern selects resources, not lines: 400
|
|
2015
|
-
`pattern-dialect-unsupported
|
|
2066
|
+
`pattern-dialect-unsupported` ({§pattern-dialect-find-only}); a matcher its mimetype cannot run answers the
|
|
2016
2067
|
matcher's own 415/400 ({§matcher-dispatch}).
|
|
2068
|
+
- §pattern-dialect-find-only **A `~` or `&` matcher outside FIND is refused as FIND's alone.** Every
|
|
2069
|
+
400 `pattern-dialect-unsupported` — READ, EDIT, KILL, COPY, MOVE, SEND — names the model's
|
|
2070
|
+
matcher and says only FIND takes it, and its recovery gives both working forms with the
|
|
2071
|
+
model's own target: the FIND carrying that matcher, and the same operation with a text
|
|
2072
|
+
pattern built from the symbol or words it named (regex metacharacters escaped, words joined
|
|
2073
|
+
by `|`): `` Locate it with `FIND (django/urls/resolvers.py) &RoutePattern`, or select lines
|
|
2074
|
+
with a text pattern: `READ (django/urls/resolvers.py) /RoutePattern/`. ``
|
|
2017
2075
|
- §read-fan-out **A READ over a glob reads every matching path.** `READ (pets_*.md)`
|
|
2018
2076
|
and `READ (pets_*.md) /dogs/i` keep their glob ({§read-find-normalization} in the
|
|
2019
2077
|
contracts SPEC) and dispatch fans them out: the ordinary FIND over the same
|
|
@@ -2033,9 +2091,7 @@ READ is the one fan-out core performs ({§read-fan-out}).
|
|
|
2033
2091
|
FIND failure is that failure on the authored glob. The FIND's resource page bounds
|
|
2034
2092
|
the fan-out: when more paths matched than were read, one `read_fanout_bounded`
|
|
2035
2093
|
notice names both counts. A full-text (`~`) or graph (`&`) matcher selects
|
|
2036
|
-
resources, not lines, so that READ dispatches as the FIND survey.
|
|
2037
|
-
2026-09-13: "give it what it asked for" — a model that asked to read the pantry
|
|
2038
|
-
looped five turns on the catalog it was handed instead.
|
|
2094
|
+
resources, not lines, so that READ dispatches as the FIND survey.
|
|
2039
2095
|
- §read-bytes A binary channel, and the `#bytes` view of
|
|
2040
2096
|
any resource whose scheme supplies bytes, reads as the source bytes one hexadecimal
|
|
2041
2097
|
octet per line: coordinate = line = byte, so `<a,b>` selects bytes, the markerless
|
|
@@ -2136,8 +2192,8 @@ turn admitted under {§empty-turn}, one runtime turn of the same loop
|
|
|
2136
2192
|
reasoning source; its receipt renders in the next packet like any other log row.
|
|
2137
2193
|
`PLURNK_REASONING_EMPTY_TURN_LINES` (alias-scoped, default in `.env.defaults`) selects the scope on the same
|
|
2138
2194
|
scale as `PLURNK_REASONING_VIEW_LINES`. No read follows a turn without reasoning, and none follows
|
|
2139
|
-
a turn whose emission or reasoning carries a foreign tool-call grammar
|
|
2140
|
-
the strike and its error row are unchanged.
|
|
2195
|
+
a turn whose emission or reasoning carries a foreign tool-call grammar or leaked template token
|
|
2196
|
+
(`KnownToxins` names them); the strike and its error row are unchanged.
|
|
2141
2197
|
|
|
2142
2198
|
### §log-kill-scope KILL on the log: whole items and scoped bodies
|
|
2143
2199
|
|
|
@@ -2145,6 +2201,8 @@ AST: `{ op: "KILL", target, matcher: MatcherBody | null, lineMarker: TextLineMar
|
|
|
2145
2201
|
|
|
2146
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}).
|
|
2147
2203
|
|
|
2204
|
+
§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
|
+
|
|
2148
2206
|
A READ carrying active native media is atomic: any KILL scope is ignored and the entire observation is retired, including its native context contribution ({§packet-attachment-parts}). For a model turn, native activity is the attachment selection in its actual input packet; without a model packet, a native observation is atomic by default. Text-only observations in the same selection retain ordinary scoped behavior. Neither form deletes source data or forensic evidence.
|
|
2149
2207
|
|
|
2150
2208
|
§log-readable-projection Log content has two independent projections:
|
|
@@ -2253,7 +2311,10 @@ Authored `metadata` retains its opaque ordered block strings under {§scheme-met
|
|
|
2253
2311
|
This is retained evidence, not a separate visibility or delivery lifecycle. Source mutation/deletion
|
|
2254
2312
|
cannot change a retained observation. Explicit READ of its still-active log source can acquire the same media again.
|
|
2255
2313
|
Every compatible-model packet includes one file part per retained, admitted READ observation, after the
|
|
2256
|
-
packet text, in observation order
|
|
2314
|
+
packet text, in observation order, each preceded by a text part that names the observation's log
|
|
2315
|
+
coordinate, source path and projection facts and states that the bytes are that READ's own, retained
|
|
2316
|
+
until its row is KILLed: an uncaptioned native part on the user turn reads as a fresh arrival (#899).
|
|
2317
|
+
Model-response settlement never consumes an observation. KILL follows
|
|
2257
2318
|
{§log-kill-scope}; forks inherit the snapshot and ordinary projection state independently. Output withholding
|
|
2258
2319
|
suppresses the complete native part under {§context-output-admission}. Unsupported routes receive only the
|
|
2259
2320
|
text projection and no native charge; switching back to a compatible route exposes still-retained media.
|
|
@@ -2316,10 +2377,10 @@ single line past the end, a reversed range, empty content, a command's log row
|
|
|
2316
2377
|
|
|
2317
2378
|
| Surface | Contract |
|
|
2318
2379
|
|---|---|
|
|
2319
|
-
| 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 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. |
|
|
2380
|
+
| 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. |
|
|
2320
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. |
|
|
2321
2382
|
| 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. |
|
|
2322
|
-
| Retention | One ops source and one
|
|
2383
|
+
| 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. |
|
|
2323
2384
|
| 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. |
|
|
2324
2385
|
| Index | Source text uses the existing derivation, FTS and graph machinery; only its derivation attachment is replaceable. |
|
|
2325
2386
|
| 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. |
|
|
@@ -2405,7 +2466,7 @@ per-operation projection on `rx`; the aggregate remains inside dispatch.
|
|
|
2405
2466
|
| `effect.source`, `result` | `effect` as `<source> -> <result>` | Resolved scopes mapping the source snapshot into the landed body; the admitted marker stays in durable `requested` and `tx`. |
|
|
2406
2467
|
| `effect.removed`, `inserted` | `change` | Removed and inserted counts in the receipt unit. |
|
|
2407
2468
|
| `effect.removedText` | `removed` | {§edit-receipt-removed-text}: a pure deletion's removed text, its first `PLURNK_SERVICE_EDIT_RECEIPT_REMOVED_LINES` lines; absent when the edit inserted anything. |
|
|
2408
|
-
| `effect.context` | Canonical row body | Numbered physical lines at each landed boundary, bounded symmetrically by `PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES`.
|
|
2469
|
+
| `effect.context` | Canonical row body | Numbered physical lines at each landed boundary, bounded symmetrically by `PLURNK_SERVICE_EDIT_RECEIPT_CONTEXT_LINES`. A pattern batch's `last` context follows the first's when it differs, with no blank line between: every line of a row body carries its coordinate. |
|
|
2409
2470
|
| `disposition`, `requested` | `disposition`, `requested` | A reviewer-replaced batch preserves the authored marker while stating that its attributed effect was superseded. |
|
|
2410
2471
|
| `replacement` | `replacement`, `change`, canonical proposal-owner body | The one whole-resource effect actually applied by the reviewer replacement; never duplicated across authored rows. |
|
|
2411
2472
|
|
|
@@ -2550,9 +2611,10 @@ Log history preserved — `log_entries` stores path tuple as text, not FK to `en
|
|
|
2550
2611
|
|
|
2551
2612
|
- §log-uniform-query **Log speaks the universal query contract** — ```` ```FIND (log://…) ```` works like every scheme's FIND. Candidates are worker rows scoped by the coordinate hierarchy ({§log-coordinate-hierarchy}) and projected exactly as READ shows them. Content dialects use `Matcher.matchCandidates`; `~` full-text and `&graph` use the same persistent derivation artifacts and candidate rankers as entries. Broad results are one-channel catalog groups whose `[0].path` is `log:///loop/turn/seq/OP`; exact matcher results are flat locations ({§find-result-projection}). Log remains the core event ledger rather than duplicating rows into `entries`; its core-private storage adapter supplies one complete channel representation to the same READ projector. That adapter is not a plugin seam and grants no protocol scheme an alternate READ path.
|
|
2552
2613
|
- §find-source-agnostic **The content matcher is source-agnostic** — `Matcher.matchCandidates(body, candidates, mimetypes)` applies a content matcher (regex/jsonpath/xpath/glob) to candidates from ANY source, keyed by the caller's own identity (a pathname for entries, a `loop/turn/seq` coordinate for log). The matcher never cares what table the content came from, so FIND works uniformly across schemes by construction: `EntryFind` and `Log.find` run the one shared primitive rather than re-implementing it per scheme. Log stays its own event stream, but its rows are candidates the shared matcher covers like any entry's content.
|
|
2614
|
+
- §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.
|
|
2553
2615
|
- §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.
|
|
2554
2616
|
|
|
2555
|
-
- §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
|
|
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.
|
|
2556
2618
|
|
|
2557
2619
|
Resource-authority globs select authorities independently of the path scope.
|
|
2558
2620
|
Matching resources retain their full addresses through pattern matching,
|
|
@@ -2567,6 +2629,7 @@ Log history preserved — `log_entries` stores path tuple as text, not FK to `en
|
|
|
2567
2629
|
matches the selected channel's content or derivation; path globs select
|
|
2568
2630
|
resources through `(target)` ({§path-glob}).
|
|
2569
2631
|
- §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
|
+
- §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`. ``
|
|
2570
2633
|
- §find-scoped-isolation Workspace + scheme scoped — no cross-workspace/cross-scheme leakage.
|
|
2571
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 }`:
|
|
2572
2635
|
|
|
@@ -2654,7 +2717,7 @@ same durable liveness.
|
|
|
2654
2717
|
| Fresh operation/parser failure, without an authored WAIT | Continue before any automatic parking. |
|
|
2655
2718
|
| Neither an authored WAIT nor an eligible completion request ({§kill-conclusion}) | Continue, regardless of earlier replies or live work. |
|
|
2656
2719
|
| 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. |
|
|
2657
|
-
| 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
|
|
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. |
|
|
2658
2721
|
| Unanswered messages | Continue. |
|
|
2659
2722
|
| Unobserved operation results, failures, child results or stream conclusions | Continue; the next packet presents them. |
|
|
2660
2723
|
| Eligible completion request with no outstanding messages, live work or unobserved results | Conclude successfully. |
|
|
@@ -2709,8 +2772,8 @@ accounting and model-visible failure evidence remain separately owned by
|
|
|
2709
2772
|
contains exactly one KILL without a target, scope, matcher or metadata, no hard
|
|
2710
2773
|
parse error or lost boundary, and was not cut at the provider's output allowance.
|
|
2711
2774
|
The operation limit must admit the entire program.
|
|
2712
|
-
SEND, NOTE
|
|
2713
|
-
|
|
2775
|
+
SEND, NOTE and log-targeted KILL may accompany it, as may outside text ({§outside-text});
|
|
2776
|
+
every other operation requires continuation. This tolerance is unadvertised:
|
|
2714
2777
|
model teaching requests KILL alone. Reasoning-side NOTEs remain ordinary notes.
|
|
2715
2778
|
An aside is allowed. After the program settles, {§wait-obligation-matrix} admits the
|
|
2716
2779
|
completion or returns a non-striking continuation/parking receipt explaining the
|
|
@@ -2722,23 +2785,23 @@ accounting and model-visible failure evidence remain separately owned by
|
|
|
2722
2785
|
completion. New arrivals still guard the terminal transition atomically
|
|
2723
2786
|
({§completion-defers-to-messages}); an arrival concurrent with an accepted reply
|
|
2724
2787
|
remains unanswered and keeps the loop running. No implicit successful exit exists.
|
|
2725
|
-
- §
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
the
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2788
|
+
- §outside-text **Text outside the operations is the turn's `outside` source: stored, weighed, never a row.**
|
|
2789
|
+
The spans {§response-text} supplies are stored verbatim as one immutable `outside` turn source
|
|
2790
|
+
per admitted emission, in source order joined by a blank line ({§turn-source-resources}); no
|
|
2791
|
+
operation is minted for them, a repetitive or length-cut response stays one source, and nothing
|
|
2792
|
+
filters what is stored. The model hears only the weight: the next packet's Notices section
|
|
2793
|
+
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.
|
|
2795
|
+
{§empty-turn} still strikes a turn that holds only text, with unchanged reasoning recovery
|
|
2796
|
+
({§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
|
|
2798
|
+
not response text ({§unfenced-operation}) and so never reaches the source; `KnownToxins` guards
|
|
2799
|
+
only the read-back ({§reasoning-empty-turn-read}). Clients receive the text once through
|
|
2800
|
+
`outside/event` ({§notifications-outside-event}, {§agui-outside-text}); FORK snapshots the
|
|
2801
|
+
source with the turn's others; the digest names its weight. It has no address: there is no
|
|
2802
|
+
`outside://` scheme, and exact emissions remain at `ops://`. This is evidence, not an alternate
|
|
2803
|
+
authoring format: the `plurnk.md` requirement to use only valid Plurnk OPs remains, and
|
|
2804
|
+
{§response-text} alone owns which bytes are operations, quotations or outside text.
|
|
2742
2805
|
- §loop-answer **A loop's address is what it said.** READ `ops://<worker>/<loop>` resolves to
|
|
2743
2806
|
the latest reply the loop gave to the message that started it: the body of a SEND
|
|
2744
2807
|
or accepted final KILL that answered that message. A running loop without one is 425; a loop that
|
|
@@ -2748,10 +2811,9 @@ accounting and model-visible failure evidence remain separately owned by
|
|
|
2748
2811
|
remains that turn's emission. A concluded child's `loop_termination` row to its parent
|
|
2749
2812
|
READs this same loop resource. Witness: `test/intg/loop-answer.test.ts`.
|
|
2750
2813
|
- §empty-turn **No authored response operation is a recoverable turn, never completion.**
|
|
2751
|
-
Count parsed response operations before
|
|
2752
|
-
|
|
2753
|
-
sources and count one progress-contract strike, whether or not the turn carried text
|
|
2754
|
-
({§response-text-note}). The strike sends no notice of its own: the turn records one `_plurnk`
|
|
2814
|
+
Count parsed response operations before reasoning NOTEs join them; neither they nor outside
|
|
2815
|
+
text ({§outside-text}) enter the count. When none exist and no boundary was lost, retain the turn
|
|
2816
|
+
and its raw sources and count one progress-contract strike, whether or not the turn carried text. The strike sends no notice of its own: the turn records one `_plurnk`
|
|
2755
2817
|
error row, `422` `The turn performed no operation.`, which rides the next packet's errors like
|
|
2756
2818
|
any failure ({§operation-result-uniform-error-channel}), and its reasoning is read back to the
|
|
2757
2819
|
model under {§reasoning-empty-turn-read}; the threshold terminal still says why ({§engine-rails}).
|
|
@@ -2777,8 +2839,8 @@ accounting and model-visible failure evidence remain separately owned by
|
|
|
2777
2839
|
empty one. Attachment is owned by the one terminal seam.
|
|
2778
2840
|
- §metadata-ignored **Options a scheme does not take are dropped, not refused.** A READ, FIND,
|
|
2779
2841
|
EDIT or KILL carrying `[metadata]` for a scheme whose manifest takes none runs without it,
|
|
2780
|
-
and the packet carries one `metadata_ignored` notice naming the scheme (
|
|
2781
|
-
|
|
2842
|
+
and the packet carries one `metadata_ignored` notice naming the scheme (a warning,
|
|
2843
|
+
never a refusal). The `pattern` option never reaches this
|
|
2782
2844
|
path; it is lifted into the matcher at parse time ({§matcher-option}). SEND recipients,
|
|
2783
2845
|
executions, WORK and FORK own their input and receive it whole ({§send-resource-attachments},
|
|
2784
2846
|
{§env-option}); a key they do not take is their own 400.
|
|
@@ -2852,15 +2914,15 @@ anything spawns — a file is the script; a directory is refused `400 target-not
|
|
|
2852
2914
|
pointing at `[{"cwd": "…"}]`; an absent path is refused `400 target-not-found`, giving the
|
|
2853
2915
|
applicable accepted form without inferring what the model meant. When the target is a
|
|
2854
2916
|
registered tool of another executor, recovery gives that tool's exact bracketed
|
|
2855
|
-
invocation;
|
|
2917
|
+
invocation; when it names another available executor (`sh (python3)` over a Python body, #895),
|
|
2918
|
+
recovery names that executor's fence — `` `python3` is its own executor; use that name on the
|
|
2919
|
+
opening fence and put the program in the body. ``; otherwise it points at an existing script or a bare shell-command body. A non-file resource
|
|
2856
2920
|
target that cannot be read keeps the owning READ's failure identity (#163) and states
|
|
2857
2921
|
the slot contract in its recovery — the resource is the program and the body its stdin;
|
|
2858
2922
|
a command belongs beneath a targetless heading — without guessing which was meant (#425). The started receipt always
|
|
2859
2923
|
names the working directory only when it is not the project root, and then in the
|
|
2860
2924
|
model's own project-relative form ({§fs-namespace}: the root is the model's `/`, so it
|
|
2861
|
-
is never rendered, and no receipt or Problem carries a host-absolute path —
|
|
2862
|
-
batch of 2026-08-29 showed the absolute `cwd` copied back into the target slot as
|
|
2863
|
-
`(cwd: /host/path)`). The `(path)` is a program — a script for an interpreter, a tool name for a tool
|
|
2925
|
+
is never rendered, and no receipt or Problem carries a host-absolute path). The `(path)` is a program — a script for an interpreter, a tool name for a tool
|
|
2864
2926
|
family — and neither a command nor a working directory is ever a target. The default
|
|
2865
2927
|
shell is written as its own fence, ```` ```sh ````; no runtime-less form exists.
|
|
2866
2928
|
|
|
@@ -2873,6 +2935,10 @@ streams are eight hex digits) is the writer's name for the run: with a body, the
|
|
|
2873
2935
|
body runs as if targetless; without one, the source read refuses as before. A
|
|
2874
2936
|
real stream id is always the program source.
|
|
2875
2937
|
|
|
2938
|
+
§exec-target-documentation **Generated reference is never a program.** A resource target under
|
|
2939
|
+
`worker:///_plurnk/` — the executor and scheme documentation the harness generates — is refused
|
|
2940
|
+
at admission, 400 `target-is-documentation`, before any source is realized or run: `` `worker:///_plurnk/plurnk/sh.md` is reference documentation the harness generated, not a program; sh cannot run it. `` With a body the recovery is `Drop the target and keep the command: the opening fence line is sh alone, with the command lines beneath it.`; without one it points to READ for the documentation and to the program's own path or a targetless heading to run something. Admitting it realized the markdown as a host temporary file that a sandboxed runtime could not open (`cannot open /tmp/plurnk-exec-….md`), and ran markdown where it could.
|
|
2941
|
+
|
|
2876
2942
|
§exec-tool-fall-through **A tool run as a shell command is named at the failure
|
|
2877
2943
|
site.** A bare shell command whose program is the name of a tool published by
|
|
2878
2944
|
another enabled runtime (`brave_web_search {…}` under the default shell) exits
|
|
@@ -2915,14 +2981,29 @@ its filename, extension, sibling imports, and source-relative assets remain
|
|
|
2915
2981
|
intact. This neither bypasses admission nor changes the executor's working
|
|
2916
2982
|
directory. A disappeared native source fails; it never runs a stale projection.
|
|
2917
2983
|
Other resources and derived channels supply standalone source, not a filesystem:
|
|
2918
|
-
Core creates one
|
|
2919
|
-
process- and database-coordinate-independent identity. No
|
|
2920
|
-
and no relative-resource filesystem is emulated. The
|
|
2984
|
+
Core creates one file under the scratch directory ({§exec-scratch-directory}), preserving the
|
|
2985
|
+
source extension, with an exclusive, process- and database-coordinate-independent identity. No
|
|
2986
|
+
sibling tree is copied and no relative-resource filesystem is emulated. The file lives through
|
|
2921
2987
|
the executor run and core removes it after the subscription's terminal result
|
|
2922
2988
|
has settled. A removal failure is reported to daemon diagnostics with its
|
|
2923
2989
|
complete cause; it cannot rewrite the execution result, stream state, or
|
|
2924
2990
|
completion wake.
|
|
2925
2991
|
|
|
2992
|
+
§exec-scratch-directory **A realized source is readable by its executor for the execution's
|
|
2993
|
+
lifetime.** The standalone file is written under the one scratch directory
|
|
2994
|
+
`PLURNK_SERVICE_EXEC_SCRATCH` names: empty, it is `$XDG_RUNTIME_DIR/plurnk` when
|
|
2995
|
+
`XDG_RUNTIME_DIR` is set and the platform temporary directory otherwise; an explicit value is
|
|
2996
|
+
an absolute directory (`~` expands), and a relative one fails at boot by name. The directory
|
|
2997
|
+
is created on first use with mode `0700`; each file is created exclusively with mode `0600`
|
|
2998
|
+
under a unique name. An executor that runs in another filesystem namespace — a container that
|
|
2999
|
+
mounts only the repository — is given a directory both sides can see by pointing the knob at
|
|
3000
|
+
it. Admission ensures the directory: one the daemon cannot create or write refuses the
|
|
3001
|
+
execution, 400 `scratch-unavailable`, whose detail names the directory, the knob and the
|
|
3002
|
+
failure code, and whose recovery has the operator point the knob at a writable absolute
|
|
3003
|
+
directory the executor can also read and, meanwhile, has the writer target a program file the
|
|
3004
|
+
executor can reach or run the command beneath a targetless heading. An execution never fails
|
|
3005
|
+
mid-run for this reason.
|
|
3006
|
+
|
|
2926
3007
|
Loop-flag authority follows the selected runtime's declaration:
|
|
2927
3008
|
|
|
2928
3009
|
| Target realization | Schemes that must be active |
|
|
@@ -2967,7 +3048,8 @@ Per-tool programs such as `go`, `cargo`, `make`, and `npm` do not earn executor
|
|
|
2967
3048
|
|
|
2968
3049
|
§exec-lifetime **How long a spawn may live is the fence's metadata, one field.**
|
|
2969
3050
|
`[{"lifetime": …}]` takes a duration (`30s`, `30m`, `2h`), or one of three words;
|
|
2970
|
-
absent is `loop`.
|
|
3051
|
+
absent is `loop`. The key is one of the service's reserved metadata keys, withheld
|
|
3052
|
+
from every owner by the framework ({§service-metadata-keys}). An execution takes no scope: a numeric coordinate on an
|
|
2971
3053
|
executor target is refused `scope-unsupported` (400), naming the field.
|
|
2972
3054
|
|
|
2973
3055
|
| `lifetime` | The spawn |
|
|
@@ -3050,7 +3132,7 @@ two states and no others:
|
|
|
3050
3132
|
|
|
3051
3133
|
| state | what the model receives |
|
|
3052
3134
|
|---|---|
|
|
3053
|
-
| active | nothing in the Log. The `## Delegation` stream pointer names the stream with each channel's size and its growth since the last packet ({§child-orientation}); the model READs any range it wants, and every READ of a stream channel carries `terminal: false` while it runs and `terminal: true` once it has concluded, so an empty page is never mistaken for a finished command that printed nothing
|
|
3135
|
+
| active | nothing in the Log. The `## Delegation` stream pointer names the stream with each channel's size and its growth since the last packet ({§child-orientation}); the model READs any range it wants, and every READ of a stream channel carries `terminal: false` while it runs and `terminal: true` once it has concluded, so an empty page is never mistaken for a finished command that printed nothing. |
|
|
3054
3136
|
| terminal | ONE `origin=_plurnk` READ at the execution's channel address, born visible, that is exactly a markerless READ of the channel — its bounded first page ({§read-selection-projection}, the whole channel when it fits, the channel's own mimetype), the `range` or `region`, terminal status and Problem, `terminal: true`, and any producer-supplied integer `exitCode`. The packet writes the read resource as its operand, exactly as an explicit READ does ({§log-address-metadata}). |
|
|
3055
3137
|
|
|
3056
3138
|
§stream-observation-result **One liveness fact.** The durable READ result owns
|
|
@@ -3060,11 +3142,13 @@ observations alike, independently of mimetype: `active` gives false, `closed` or
|
|
|
3060
3142
|
projection preserves that Boolean, any included
|
|
3061
3143
|
integer `exitCode`, and a producer's `page` receipt ({§executor-page-receipt}), even for an empty body. An automatic observation's atomic
|
|
3062
3144
|
publication transition consumes the same result flag; private log attributes
|
|
3063
|
-
retain only the publication offset, not a second liveness value.
|
|
3145
|
+
retain only the publication offset, not a second liveness value. A closed channel
|
|
3146
|
+
publishes only once subscription settlement has installed its terminal result;
|
|
3147
|
+
until then the stream is still in flight and publishes on the turn after it settles.
|
|
3064
3148
|
|
|
3065
3149
|
§exec-concurrency **Bounded admission per workspace (#389).** At most
|
|
3066
|
-
`PLURNK_SERVICE_EXEC_CONCURRENCY` executions run at once in one workspace (
|
|
3067
|
-
|
|
3150
|
+
`PLURNK_SERVICE_EXEC_CONCURRENCY` executions run at once in one workspace (`-1`
|
|
3151
|
+
unbounded); the scope is the workspace, so neither delegation nor later turns
|
|
3068
3152
|
bypass it and no other workspace can starve it. Every admitted execution still creates its
|
|
3069
3153
|
entry, channels, and open subscription before its receipt returns, so queued work is
|
|
3070
3154
|
cancellable, restart-reconcilable, completion-gated, and observed through the ordinary
|
|
@@ -3098,9 +3182,7 @@ channel that holds content, and an empty sibling channel is a fact on that row
|
|
|
3098
3182
|
(`channels: {"#stderr": 0}`), never a row of its own; only a stream that printed
|
|
3099
3183
|
nothing on any channel lands one bodyless conclusion row, on its default
|
|
3100
3184
|
channel, whose terminal fact, causal execution link, and available exit code make
|
|
3101
|
-
completion explicit without invented narration
|
|
3102
|
-
per-channel empty row was "a useless packet bomb" — 131 of 298 conclusion rows
|
|
3103
|
-
in the candidate4 run). A skipped channel's publication is still marked
|
|
3185
|
+
completion explicit without invented narration. A skipped channel's publication is still marked
|
|
3104
3186
|
terminal, so the stream's termination is delivered and never left pending. KILL may curate
|
|
3105
3187
|
that log row without rewinding the cursor or publishing the terminal result
|
|
3106
3188
|
again; the exact terminal result and channel content remain READable at the
|
|
@@ -3181,8 +3263,8 @@ body prefixes.
|
|
|
3181
3263
|
key WORK or FORK does not take is refused the same way. The durable row redacts the block
|
|
3182
3264
|
wholesale ({§log-sensitive-request-evidence}); the spawn's record names each such value's
|
|
3183
3265
|
provenance as the modifier's.
|
|
3184
|
-
- §exec-hold-until-concluded **The turn-hold exception** — for runtimes in `PLURNK_SERVICE_EXEC_HOLD` (a decision-table env
|
|
3185
|
-
- §exec-entry-sink **The entry() sink** implements {§executor-entry-sink} over ordinary scheme-owned entries. Core owns allocation, materialization, and persistence; executors receive only the returned resource address.
|
|
3266
|
+
- §exec-hold-until-concluded **The turn-hold exception** — for runtimes in `PLURNK_SERVICE_EXEC_HOLD` (a decision-table env), an in-flight stream **pauses the cycle**: the next packet does not assemble until the stream concludes, so the model never burns a turn asking "are we there yet" about a result the engine controls end-to-end. This exception is limited to seconds-bounded runtimes whose final result the engine controls end-to-end. Bounded by `PLURNK_SERVICE_EXEC_HOLD_MS` and **fail-open**: at the cap the standard cycle resumes untouched (waits, wakes, polls). Zero grammar or teaching surface — the model emits an executor fence, optionally followed by WAIT; the wake-shaped world simply arrives one packet sooner. It extends selected runtimes beyond the ordinary {§worker-optimistic-settlement} cap before the next packet assembles. A bare entry holds ALL of a runtime's spawns; a `<runtime>:<effect>` suffix (`github:read`) holds only that effect-class — an MCP server is one runtime whose tools split (a `read` `get_issue` is instant; a `host` `run_migration` is a slow mutation), so an operator opts the known-fast read-class in without parking on the mutation. Conservative stays default: an arbitrary third-party server's latency never parks the engine unless a suffix opts a class in.
|
|
3267
|
+
- §exec-entry-sink **The entry() sink** implements {§executor-entry-sink} over ordinary scheme-owned entries. Core owns allocation, materialization, and persistence; executors receive only the returned resource address. Web acquisition and materialization are the `https` handler's {§web-materialization-contract}, reached through the scheme registry; core names no leaf package.
|
|
3186
3268
|
|
|
3187
3269
|
| Input / effect | Consumer behavior |
|
|
3188
3270
|
| --- | --- |
|
|
@@ -3204,7 +3286,7 @@ body prefixes.
|
|
|
3204
3286
|
|
|
3205
3287
|
- **Client disposition** ({§methods-proposal-resolve}) — a client interface delivers accept, reject, or cancel; AG-UI uses standard resume entries ({§agui-proposal-resolve}).
|
|
3206
3288
|
- **Loop disposition** ({§proposal-disposition}) — core applies the exact automatic accept/reject before observational subscribers run; automatic policy is not an event listener or client fallback.
|
|
3207
|
-
- §proposal-timeout-cancels **Timeout is OPT-IN;
|
|
3289
|
+
- §proposal-timeout-cancels **Timeout is OPT-IN; an empty deadline WAITS** - an empty `PLURNK_SERVICE_PROPOSAL_TIMEOUT_MS` means a pending proposal - a file edit awaiting review - waits indefinitely for its human: absence is not an answer, so the service does not synthesize a cancellation. A finite positive millisecond value establishes the bound; then elapsing synthesizes `cancel` (outcome `timeout`), server-side, needing no client. Every other explicit value fails at the proposal lifecycle owner and terminalizes an already-written proposal rather than silently choosing an indefinite wait. Indefinite is with respect to the clock alone: the wait ends with its loop. A loop whose signal aborts — its own timeout, `loop.cancel`, worker `KILL` — settles every proposal it is holding through {§proposal-cancel-aborts}, carrying the abort's reason as the outcome, because a cancelled loop is not a loop awaiting a decision.
|
|
3208
3290
|
|
|
3209
3291
|
**The decision drives a one-way state transition** on `log_entries.state` (resolution is idempotent — `WHERE state='proposed'`, so a second resolution 404s):
|
|
3210
3292
|
|
|
@@ -3214,7 +3296,9 @@ body prefixes.
|
|
|
3214
3296
|
| §proposal-reject-fails reject | `failed` | 400 | `rejected` | none — the action did not occur. |
|
|
3215
3297
|
| §proposal-cancel-aborts cancel | `cancelled` | 499 | `loop_aborted` | none — the loop is abandoning. |
|
|
3216
3298
|
|
|
3217
|
-
§proposal-outcome-terse-error A caller-supplied `outcome` overrides the default. On an **accept** it rides the result as the forensic `outcome` field; a **non-accept** is a Problem that carries the same `outcome` field (`write_failed` / `rejected` / `timeout` — one word) and names it in its detail, because "the action didn't occur" without the mechanical why leaves the model acting on a phantom success (the fan-out dead-park: an ENOENT apply rendered as a mute 400).
|
|
3299
|
+
§proposal-outcome-terse-error A caller-supplied `outcome` overrides the default. On an **accept** it rides the result as the forensic `outcome` field; a **non-accept** is a Problem that carries the same `outcome` field (`write_failed` / `rejected` / `timeout` — one word) and names it in its detail, because "the action didn't occur" without the mechanical why leaves the model acting on a phantom success (the fan-out dead-park: an ENOENT apply rendered as a mute 400).
|
|
3300
|
+
|
|
3301
|
+
§proposal-harness-settlement **A settlement the harness itself decided names its condition and its recovery.** Nobody attending, no answer before a deadline, a loop or daemon ending while the proposal waited: each states that condition and an exit in its detail and `recovery`, because a reviewer's outcome is a reviewer's word but these have no author present to explain them; the one-word token stays as the forensic `outcome` either way ({§proposal-outcome-terse-error}).
|
|
3218
3302
|
|
|
3219
3303
|
§proposal-proposed-hidden **A proposed row is invisible until it resolves.** A `state='proposed'` / 202 row is withheld from both packet materialization and `log/entry`; it surfaces exactly once after resolution, carrying its terminal status — models and clients see outcomes, never pending proposals.
|
|
3220
3304
|
|
|
@@ -3425,19 +3509,19 @@ SQLite (`node:sqlite`) with WAL mode and STRICT tables. Hand-written DDL; CI-ali
|
|
|
3425
3509
|
|
|
3426
3510
|
No generator. SQLite-optimal: STRICT (3.37+), `INTEGER PRIMARY KEY` aliasing, explicit `NOT NULL`, indexed query paths, deliberate FK `ON DELETE`/`ON UPDATE`, `WITHOUT ROWID` where access pattern warrants, generated columns, FTS5.
|
|
3427
3511
|
|
|
3428
|
-
| Concern |
|
|
3512
|
+
| Concern | Rule |
|
|
3429
3513
|
|---|---|
|
|
3430
|
-
| §db-schema-baseline Baseline | `migrations/`
|
|
3431
|
-
|
|
|
3432
|
-
|
|
|
3433
|
-
|
|
|
3514
|
+
| §db-schema-baseline Baseline | Versions 1–8 of `migrations/` are the released baseline, as domain chapters — `001_workspaces`, `002_workers`, `003_loops`, `004_inference`, `005_entries`, `006_log`, `007_subscriptions`, `008_interactions` — each one `MIGRATE` block whose version is the file's numeric prefix. They create the shape 1.21.1 shipped: tables, indexes, views, the constraint triggers that are a table's invariants (guards that only `RAISE`), and a view's `INSTEAD OF` write path. No migration holds an `INIT` block or a trigger that writes a row. |
|
|
3515
|
+
| §db-migrations Evolution | A released version is frozen: only its comments may change. Every shape change is one new file at the next version (`009_effort` renames the #877 columns), applied by sqlrite above the database's `PRAGMA user_version`, ascending, each in its own transaction with its version bump. A fresh database takes the same path as an existing one. Each migration carries upgrade coverage: `test/intg/schema-baseline.test.ts` pins the released shape's fingerprint and migrates a released database, asserting its rows survive. A process trigger's change needs no migration: its `INIT` block re-declares it on the next open ({§db-process-triggers}). A table anything references cannot be rebuilt in a migration: foreign keys stay enforced inside the migration transaction, so the drop cascades through its children (`011_settled.sql`); such a table evolves by adding columns or redeclaring its guard triggers. Only an unreferenced table is rebuilt (`010_outside_text.sql`). |
|
|
3516
|
+
| §validation-topology Where an invariant is enforced | The SQL core owns each invariant: a chapter's CHECK constraints and guard triggers are its one statement, and a rule two tables share is the same expression over each column (`entry_channel_producer_result_contract` and `subscriptions_result_contract_update` hold the settled-result rule as one text, `011_settled`). Contracts (JSON Schema) are enforced at the gates: a scheme's result entering core (`Results` in plurnk-schemes) and the wire leaving to clients (plurnk-agui's `Validator` calls). Everything between trusts core and the gates and carries no defensive re-validation: a result read back from a row is parsed, never re-asserted. Witness: `test/intg/validation-topology.test.ts` applies one corpus of settled results to the gate, to chapter 5 and to chapter 7 and asserts the three agree on every row. |
|
|
3517
|
+
| Open failure | A missing table or column after migration means the file's shape disagrees with its version: a database from a newer release, or one from an unreleased development build. The daemon refuses to open it and names both remedies. |
|
|
3434
3518
|
| §db-process-triggers Processes beside their owners | A trigger that writes rows — a cascade, a capture, an ambient event, a publication cursor, a landed curation — is a process, not shape. It is declared as an `-- INIT: <trigger name>` block in the `.sql` file beside the statements that fire it (`ambient.sql` for the ambient feed, `LoopLifecycle.sql`, `Turn.sql`, `Engine.sql` for model calls, `_entry-crud.sql`, `Log.sql`, `ChannelWrite.sql`), as `DROP TRIGGER IF EXISTS` then `CREATE TRIGGER`, so the definition is current on every open of a database whose shape is current. `MIGRATE` always precedes `INIT` and `INIT` runs on the writer only, so a process may reference any table regardless of file order and never runs on the read pool. `test/intg/schema-composition.test.ts` fails on a baseline trigger that writes, an `INIT` trigger that only guards, a block not named after its trigger or not dropping first, and a live trigger set that differs from the declared set after a first and a second open. |
|
|
3435
3519
|
| §db-fk-indexes Foreign-key check paths | Every foreign-key column a delete, cascade, or parent replacement can check carries an index (partial where the column is nullable), and no registry statement's plan scans a growing table: `test/intg/schema-query-plans.test.ts` runs `EXPLAIN QUERY PLAN` over every `-- PREP` statement against the baseline and fails on a `SCAN` of a growing table, except statements that read a whole table by design (digest, startup recovery, whole-workspace listings, scheduled-loop claims). An index claim is a plan, never a grep of index names. |
|
|
3436
3520
|
| §db-index-owners Every index has an owner | An explicit index earns its place one of three ways: a registry statement's plan uses it, its leading column is a foreign key whose check it serves, or it enforces uniqueness. The same test fails on any other index, naming it: an index nobody reads is a write on every insert. Duplicates of a `UNIQUE` constraint's own index and sort-only indexes no plan selects were removed on this rule; a column no statement reads (`symbol_refs.col`, `ambient_events.created_at`) is not stored. |
|
|
3437
3521
|
| §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. |
|
|
3438
|
-
| §db-space-reclamation The daemon keeps its own file healthy | `PLURNK_SERVICE_AUTO_VACUUM` (`incremental
|
|
3439
|
-
| §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
|
|
3440
|
-
| §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` (
|
|
3522
|
+
| §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
|
+
| §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`. |
|
|
3441
3525
|
|
|
3442
3526
|
- DDL = storage truth; JSON Schemas = wire truth. They are allowed to differ where ergonomics demand.
|
|
3443
3527
|
- §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`.
|
|
@@ -3589,6 +3673,14 @@ and is ignored rather than resolved against the working directory.
|
|
|
3589
3673
|
| Reproducible cache | `$XDG_CACHE_HOME` (default `~/.cache`) | Reserved; no directory is created without an owned artifact. |
|
|
3590
3674
|
| Shared global Agent Skills | User home | `.agents/skills/<name>/SKILL.md` |
|
|
3591
3675
|
|
|
3676
|
+
§state-root **A private daemon has one root.** `PLURNK_SERVICE_STATE_ROOT` (absolute; a leading
|
|
3677
|
+
`~/` expands; a relative value fails hard by name) replaces the data, state, cache and runtime homes
|
|
3678
|
+
with `<root>/data`, `<root>/state`, `<root>/cache` and `<root>/runtime`, the database with them
|
|
3679
|
+
(`PLURNK_SERVICE_DB_PATH` still names the database exactly). Configuration stays where the cascade
|
|
3680
|
+
reads it and the shared Agent Skills root stays under the user's home: a state root separates what
|
|
3681
|
+
the daemon *writes*, not what the operator supplies, and is no execution sandbox. What a launcher
|
|
3682
|
+
keeps or removes under a root after the daemon stops is that launcher's retention decision.
|
|
3683
|
+
|
|
3592
3684
|
The service creates only a directory required by the current command. A newly
|
|
3593
3685
|
created configuration or data directory uses mode `0700`; a newly seeded
|
|
3594
3686
|
secret-bearing `.env` uses `0600`. Existing user-owned permissions are not
|
|
@@ -3648,14 +3740,15 @@ Each knob's value lives on its panel and nowhere else (`plurnk-service config de
|
|
|
3648
3740
|
| Var | Purpose |
|
|
3649
3741
|
|---|---|
|
|
3650
3742
|
| `PLURNK_SERVICE_DB_PATH` | SQLite file path; an explicit non-empty value overrides the derived default. |
|
|
3743
|
+
| `PLURNK_SERVICE_SHARE_FOLDER` | Parent of the shares written when no folder is named ({§share-folder}); empty is `$XDG_STATE_HOME/plurnk/shares`. |
|
|
3651
3744
|
| §operator-config-shared-keys `PLURNK_HOST`, `PLURNK_PORT` | The listener's bind address and TCP port — THE client surface, the AG-UI+ listener the plurnk-agui module binds at boot; production is single-listener. **A key the daemon and its clients both read has a shared owner**: `@plurnk/plurnk-contracts` declares these two and the optional `PLURNK_AGUI_URL` on its own panel, the one package every side depends on. The daemon folds it like any installed member's, a client folds it beneath its own, and so neither holds the other's default. The service's `--host` and `--port` flags are generated from that panel. |
|
|
3652
3745
|
| §operator-config-git-ceiling `PLURNK_SERVICE_GIT_ALLOWED` | Hard service ceiling: only `1` admits Git membership and status; every other value denies them. |
|
|
3653
3746
|
| §operator-config-file-create-scope `PLURNK_SERVICE_FILE_CREATE_SCOPE` | Hard file-creation ceiling: `none < root < namespace`. `none` denies new filesystem files, `root` admits only paths inside `project_root`, and `namespace` also admits canonical outside-root paths. Existing-member writes are unaffected. |
|
|
3654
3747
|
| `PLURNK_SERVICE_FILE_MATERIALIZE_MAX_BYTES` | Byte ceiling in `1..104857600` for one workspace-file snapshot ({§membership-materialization-limit}). |
|
|
3655
|
-
| `PLURNK_SERVICE_MAX_TURNS` | Operator
|
|
3656
|
-
| `PLURNK_SERVICE_MAX_COMMANDS` | Per-emission action ceiling; `-1` = no cap
|
|
3748
|
+
| `PLURNK_SERVICE_MAX_TURNS` | Operator model-call **ceiling** — `-1` = no cap; a positive value clamps `runLoop({maxTurns})`. The durable worker-tree budget includes descendant calls, BARE, and park/resume under {§turn-cap-counts-the-tree}; non-model chronology consumes none. |
|
|
3749
|
+
| `PLURNK_SERVICE_MAX_COMMANDS` | Per-emission action ceiling; `-1` = no cap — every generated op dispatches. A positive value caps dispatched actions: overflow ops drop with one durable `max-commands-exceeded` error row on the next packet. The final disposition always dispatch. Tightened per workspace via `settings.maxCommands` (min wins). |
|
|
3657
3750
|
| §operator-config-loop-timeout `PLURNK_SERVICE_LOOP_TIMEOUT` | Positive ms of cumulative active execution per loop ({§loop-execution-allowance}); excludes parked/queued time. Snapshotted on first execution, retained across wakes. Exhaustion aborts in-flight work and terminates `504 loop_timeout`, including a stuck provider call. |
|
|
3658
|
-
| `PLURNK_SERVICE_PROVIDER_RECOVERY` | ms
|
|
3751
|
+
| `PLURNK_SERVICE_PROVIDER_RECOVERY` | ms of recovery after the first recoverable provider failure; `0` disables reissue. Expiry parks attended loops, concludes unattended loops, or returns BARE's failure under {§provider-recovery}. |
|
|
3659
3752
|
| `PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF` | First recovery delay (ms); doubles per failure up to `PLURNK_SERVICE_PROVIDER_RECOVERY_BACKOFF_MAX` ({§provider-recovery}). |
|
|
3660
3753
|
| `PLURNK_SERVICE_MAX_STRIKES` | Consecutive turn-contract strike threshold ({§engine-rails}). |
|
|
3661
3754
|
| `PLURNK_SERVICE_EMISSION_ATTEMPTS` | Completed provider responses allowed beneath one engine turn before frame admission is exhausted. Bounded interior operation errors are admitted without spending this budget. Exhaustion contributes one frame-contract strike under {§invalid-emission-attempts}. |
|
|
@@ -3671,6 +3764,7 @@ Each knob's value lives on its panel and nowhere else (`plurnk-service config de
|
|
|
3671
3764
|
| `PLURNK_SERVICE_FILES_ITEMS` | Turn-0 catalog preview. Folder-capable schemes render a one-level `*` map with `dir/**` rollups; kernel docs remain recursive and explicitly complete. `-1` = markerless first pages; positive `N` explicitly caps only file-map rows; `0` / unset = off ({§actor-boundary-catalog-preview}). |
|
|
3672
3765
|
| `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` | Ceiling for a model's `members` definitions in the lattice `none < root < namespace`; `none` refuses every model definition ({§members-model-scope}). |
|
|
3673
3766
|
| `PLURNK_SERVICE_EXEC_CONCURRENCY` | Executions admitted at once per workspace; the rest queue FIFO with `202 queued` receipts; `-1` unbounded ({§exec-concurrency}). |
|
|
3767
|
+
| `PLURNK_SERVICE_EXEC_SCRATCH` | Directory a standalone execution source is written to for its run; empty derives `$XDG_RUNTIME_DIR/plurnk`, else the platform temporary directory ({§exec-scratch-directory}). |
|
|
3674
3768
|
| `PLURNK_SERVICE_PROPOSAL_TIMEOUT_MS` | Finite positive milliseconds before cancellation with outcome `timeout`; empty waits, and every other explicit value fails ({§proposal-timeout-cancels}). |
|
|
3675
3769
|
| §operator-config-worker-warm `PLURNK_SERVICE_WORKSPACE_WARM_MS` | Milliseconds a lease-free workspace Functionality snapshot remains warm; `0` cools without grace and `-1` disables time-based cooling ({§module-workspace-residency}). |
|
|
3676
3770
|
| `PLURNK_SERVICE_WORKSPACE_WARM_MAX` | Maximum lease-free workspace Functionality snapshots retained process-wide; `0` retains none and `-1` disables the idle-LRU bound ({§module-workspace-residency}). |
|
|
@@ -3679,55 +3773,22 @@ Every core knob listed is enforced at its owning read site; `.env.defaults` is t
|
|
|
3679
3773
|
|
|
3680
3774
|
**Two override semantics — ceiling vs default.** Which kind a var is determines what "override" means across the cascade:
|
|
3681
3775
|
|
|
3682
|
-
- **Ceiling** (most-restrictive-wins) — an operator-set hard bound nothing downstream may exceed: not a lower-precedence file, not a per-workspace constraint, not a per-call seam argument. `PLURNK_SERVICE_GIT_ALLOWED` ({§operator-config-git-ceiling}), `PLURNK_SERVICE_FILE_CREATE_SCOPE` ({§operator-config-file-create-scope}), `PLURNK_SERVICE_MAX_COMMANDS`, `PLURNK_SERVICE_MAX_STRIKES`, and `PLURNK_SERVICE_MAX_TURNS` (`-1`
|
|
3776
|
+
- **Ceiling** (most-restrictive-wins) — an operator-set hard bound nothing downstream may exceed: not a lower-precedence file, not a per-workspace constraint, not a per-call seam argument. `PLURNK_SERVICE_GIT_ALLOWED` ({§operator-config-git-ceiling}), `PLURNK_SERVICE_FILE_CREATE_SCOPE` ({§operator-config-file-create-scope}), `PLURNK_SERVICE_MAX_COMMANDS`, `PLURNK_SERVICE_MAX_STRIKES`, and `PLURNK_SERVICE_MAX_TURNS` (`-1` = no cap; a positive value caps the per-call request). The sandbox/cost guarantee: the operator caps it; no client widens it.
|
|
3683
3777
|
- **Default** (explicit-wins) — a fallback the most-specific setter replaces freely: `PLURNK_MODEL` (a `runLoop({selector})` request overrides it) and the config-time vars (`HOST` / `PORT` / `DB_PATH`).
|
|
3684
3778
|
|
|
3685
|
-
§operator-config-shipped-defaults **The shipped `.env.defaults` is itself under
|
|
3686
|
-
test.** It has no active `PLURNK_MODEL`; no active local GBNF constraint; and
|
|
3687
|
-
the policy renders in exactly one packet section. Every other tier runs the
|
|
3688
|
-
test cascade, so shipped-default regressions are otherwise invisible by
|
|
3689
|
-
construction.
|
|
3690
|
-
|
|
3691
3779
|
§operator-config-flag-parity The companion **flag-parity** check binds code and
|
|
3692
3780
|
template both ways: every `PLURNK_SERVICE_*` the service reads has a
|
|
3693
3781
|
`.env.defaults` line — a floor, a `--flag`, and a legend entry — and every
|
|
3694
3782
|
declared `PLURNK_SERVICE_*` is read. A half-landed rename therefore fails a test
|
|
3695
3783
|
instead of a user's boot, and a dead knob cannot ship.
|
|
3696
3784
|
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
| Owner | Configuration |
|
|
3700
|
-
|---|---|
|
|
3701
|
-
| `.env.test` | Universal real-model gate posture, with no model selection, alias declarations, routes, secrets, model tuning, or cost/sandbox ceilings. |
|
|
3702
|
-
| Live/demo scripts | The repository policy path and runner topology. |
|
|
3703
|
-
| Benchlets | Their snapshotted policy, workspace restrictions, and task-specific exceptions; direct env wins over the profile. |
|
|
3704
|
-
| Operator env/shell | Model alias declarations and explicit selection overrides, provider capability such as GBNF, endpoints, credentials, tuning, and deliberate ceiling overrides. |
|
|
3705
|
-
| `test/setup.ts` | Mock-only alias, envelope, resource, storage, and isolation fixtures; unit/integration never consume the real-model profile. |
|
|
3706
|
-
|
|
3707
|
-
The profile clears `PLURNK_SERVICE_POLICY`, disabling the implicit XDG
|
|
3708
|
-
`AGENTS.md` policy under {§policy-sections}. Mock tests do the same in their
|
|
3709
|
-
bootstrap. Live/demo and benchmarks may select a harness-owned policy explicitly;
|
|
3710
|
-
none implicitly inherit the daily-driving policy. This does not disable project
|
|
3711
|
-
`AGENTS.md` guidance or modify the operator's file.
|
|
3712
|
-
|
|
3713
|
-
The profile does not repeat `NODE_OPTIONS`: runner selection belongs to the invoking command, and a process-global Node option would leak into the daemon and its children. Hard ceilings such as max turns, max commands, and Git denial remain operator-owned; harnesses bound paid experiments through their per-call contract and never widen a configured ceiling here.
|
|
3785
|
+
Feature-flag bools use `process.env.X === "1"` exactly — never `=== "true"`.
|
|
3714
3786
|
|
|
3715
|
-
§operator-config-
|
|
3716
|
-
not another configuration source.** The live/demo `:zeropin` scripts load the
|
|
3717
|
-
ordinary environment cascade, then the test floor removes operator model tuning
|
|
3718
|
-
before assembled package defaults fill unset values:
|
|
3787
|
+
External plugins declare their own env vars in their own `.env.defaults`, assembled at boot ({§operator-config-env-defaults}).
|
|
3719
3788
|
|
|
3720
|
-
|
|
3721
|
-
|-----------------------------------------------------------|--------------------|
|
|
3722
|
-
| Any `PLURNK_PROVIDERS_CONTEXT_WINDOW` | Remove |
|
|
3723
|
-
| Alias-specific output and reasoning budgets | Remove |
|
|
3724
|
-
| Model selection, routes, and credentials | Retain |
|
|
3725
|
-
| Bare shipped generation-envelope defaults | Retain |
|
|
3726
|
-
| Unrelated environment | Retain |
|
|
3789
|
+
§operator-config-cli-flags **Admin CLI flags derive only from the service package's `.env.defaults`.** Every `PLURNK_*` declared there becomes `--<kebab-cased-name>` (prefix stripped, lowercased, underscores → dashes). A comment immediately above the declaration becomes its `-h` description. Installed plugin defaults join the environment floor and catalog but do not implicitly expand the service executable's flag surface.
|
|
3727
3790
|
|
|
3728
|
-
|
|
3729
|
-
is red because provider capacity did not derive for
|
|
3730
|
-
a fresh-user configuration.
|
|
3791
|
+
### Loop limits
|
|
3731
3792
|
|
|
3732
3793
|
§turn-cap-counts-the-tree **The turn ceiling is the worker tree's budget of model
|
|
3733
3794
|
calls.** The owner is the current loop of the topmost ancestor-or-self worker that has
|
|
@@ -3740,7 +3801,9 @@ terminal ({§loop-terminals}) when the ceiling is met; a BARE beyond the budget
|
|
|
3740
3801
|
429 `max-turns` before any provider call, so one turn cannot spend past it with a batch. A
|
|
3741
3802
|
child loop inherits the value and binds the same count.
|
|
3742
3803
|
|
|
3743
|
-
§operator-config-max-turns-ceiling Enforcement is per-use-site — no central most-restrictive pass; each ceiling is checked where it bites. `PLURNK_SERVICE_MAX_TURNS`
|
|
3804
|
+
§operator-config-max-turns-ceiling Enforcement is per-use-site — no central most-restrictive pass; each ceiling is checked where it bites. `PLURNK_SERVICE_MAX_TURNS` at `-1` is no cap; when an operator sets a positive value, the per-call request is `min()`-capped against it. Other termination rules remain independent ({§loop-terminals}).
|
|
3805
|
+
|
|
3806
|
+
### Workspace settings
|
|
3744
3807
|
|
|
3745
3808
|
§operator-config-workspace-settings **Client open-context (per workspace).**
|
|
3746
3809
|
`workspace.create({ settings })` accepts only the following fields, normalizes
|
|
@@ -3775,18 +3838,55 @@ leak into another.
|
|
|
3775
3838
|
floor — the tightest — admitting a plan and disposition with zero actions.
|
|
3776
3839
|
- §operator-config-workspace-git `settings.git` (`false`) **denies** git for the workspace (`PLURNK_SERVICE_GIT_ALLOWED` AND workspace) — the client opts its workspace out of git membership and working-tree status; it can never re-enable git past the operator's service-wide lockout.
|
|
3777
3840
|
- §operator-config-workspace-file-create-scope `settings.fileCreateScope` narrows `PLURNK_SERVICE_FILE_CREATE_SCOPE` by the ordered lattice `none < root < namespace`; a workspace may disable creation or confine a namespace-enabled service to its root, but never widen the operator's ceiling. Unknown service values fail configuration validation and unknown workspace values fail `workspace.create`.
|
|
3778
|
-
-
|
|
3841
|
+
- `settings.membersModelScope` narrows `PLURNK_SERVICE_MEMBERS_MODEL_SCOPE` ({§members-model-scope}).
|
|
3779
3842
|
- §operator-config-workspace-capabilities `settings.capabilities` is one
|
|
3780
3843
|
workspace-stable `CapabilityPolicy` layer in {§capability-admission}. It may
|
|
3781
3844
|
narrow any registered operation, scheme, runtime, tool, access class, or
|
|
3782
3845
|
trait through the canonical `only`/`deny` selectors; it cannot register a
|
|
3783
3846
|
capability or restore one removed by the service layer.
|
|
3784
3847
|
|
|
3785
|
-
|
|
3848
|
+
### Gate profiles
|
|
3786
3849
|
|
|
3787
|
-
|
|
3850
|
+
§operator-config-shipped-defaults **The shipped `.env.defaults` is itself under
|
|
3851
|
+
test.** It has no active `PLURNK_MODEL`; no active local GBNF constraint; and
|
|
3852
|
+
the policy renders in exactly one packet section. Every other tier runs the
|
|
3853
|
+
test cascade, so shipped-default regressions are otherwise invisible by
|
|
3854
|
+
construction.
|
|
3788
3855
|
|
|
3789
|
-
§operator-config-
|
|
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:
|
|
3857
|
+
|
|
3858
|
+
| Owner | Configuration |
|
|
3859
|
+
|---|---|
|
|
3860
|
+
| `.env.test` | Universal real-model gate posture, with no model selection, alias declarations, routes, secrets, model tuning, or cost/sandbox ceilings. |
|
|
3861
|
+
| Live/demo scripts | The repository policy path and runner topology. |
|
|
3862
|
+
| Benchlets | Their snapshotted policy, workspace restrictions, and task-specific exceptions; direct env wins over the profile. |
|
|
3863
|
+
| Operator env/shell | Model alias declarations and explicit selection overrides, provider capability such as GBNF, endpoints, credentials, tuning, and deliberate ceiling overrides. |
|
|
3864
|
+
| `test/setup.ts` | Mock-only alias, envelope, resource, storage, and isolation fixtures; unit/integration never consume the real-model profile. |
|
|
3865
|
+
|
|
3866
|
+
The profile clears `PLURNK_SERVICE_POLICY`, disabling the implicit XDG
|
|
3867
|
+
`AGENTS.md` policy under {§policy-sections}. Mock tests do the same in their
|
|
3868
|
+
bootstrap. Live/demo and benchmarks may select a harness-owned policy explicitly;
|
|
3869
|
+
none implicitly inherit the daily-driving policy. This does not disable project
|
|
3870
|
+
`AGENTS.md` guidance or modify the operator's file.
|
|
3871
|
+
|
|
3872
|
+
The profile does not repeat `NODE_OPTIONS`: runner selection belongs to the invoking command, and a process-global Node option would leak into the daemon and its children. Hard ceilings such as max turns, max commands, and Git denial remain operator-owned; harnesses bound paid experiments through their per-call contract and never widen a configured ceiling here.
|
|
3873
|
+
|
|
3874
|
+
§operator-config-zero-pin-gate **Zero-pin is a counterfactual real-model gate,
|
|
3875
|
+
not another configuration source.** The live/demo `:zeropin` scripts load the
|
|
3876
|
+
ordinary environment cascade, then the test floor removes operator model tuning
|
|
3877
|
+
before assembled package defaults fill unset values:
|
|
3878
|
+
|
|
3879
|
+
| Configuration family | Zero-pin treatment |
|
|
3880
|
+
|-----------------------------------------------------------|--------------------|
|
|
3881
|
+
| Any `PLURNK_PROVIDERS_CONTEXT_WINDOW` | Remove |
|
|
3882
|
+
| Alias-specific output and reasoning budgets | Remove |
|
|
3883
|
+
| Model selection, routes, and credentials | Retain |
|
|
3884
|
+
| Bare shipped generation-envelope defaults | Retain |
|
|
3885
|
+
| Unrelated environment | Retain |
|
|
3886
|
+
|
|
3887
|
+
The floor reports every removed key. A gate that succeeds only with those pins
|
|
3888
|
+
is red because provider capacity did not derive for
|
|
3889
|
+
a fresh-user configuration.
|
|
3790
3890
|
|
|
3791
3891
|
---
|
|
3792
3892
|
|
|
@@ -3946,8 +4046,8 @@ definition for the submitting client or worker.
|
|
|
3946
4046
|
capability-aware operations, scoped module actions, and retained provider work
|
|
3947
4047
|
lease the workspace's Functionality. Boot, workspace or worker creation,
|
|
3948
4048
|
attachment, listing, naming, idle clients, and parked state alone do not.
|
|
3949
|
-
After the last lease releases, `PLURNK_SERVICE_WORKSPACE_WARM_MS`
|
|
3950
|
-
`
|
|
4049
|
+
After the last lease releases, `PLURNK_SERVICE_WORKSPACE_WARM_MS` and
|
|
4050
|
+
`PLURNK_SERVICE_WORKSPACE_WARM_MAX` bound idle
|
|
3951
4051
|
residency. `0` disables the respective grace or allowance; `-1` disables that
|
|
3952
4052
|
bound. Concurrent demand coalesces; cooling never closes a leased connection.
|
|
3953
4053
|
|
|
@@ -3963,8 +4063,8 @@ documents and its state. Only a family that holds processes prepares
|
|
|
3963
4063
|
`runtimes` (MCP servers today); the field is absent for every other family, so
|
|
3964
4064
|
warming and cooling bound workspaces, never families, and the two-stage rollback
|
|
3965
4065
|
guards the manager registration of every family alongside the one family's
|
|
3966
|
-
processes. There is no per-family residency policy
|
|
3967
|
-
|
|
4066
|
+
processes. There is no per-family residency policy: residency is
|
|
4067
|
+
MCP-specific.
|
|
3968
4068
|
|
|
3969
4069
|
The version-1 baseline table `workspace_module_state` stores one JSON value
|
|
3970
4070
|
per `(workspace_id, namespace_owner)`. It is configuration, not an executable
|
|
@@ -4003,7 +4103,12 @@ actions from model operations where the family contract requires it
|
|
|
4003
4103
|
outcomes, and a snapshot with `commit`/`abort`. Successful publication commits;
|
|
4004
4104
|
failure aborts; cooling tears down. Protocol continuations remain ordinary
|
|
4005
4105
|
module actions. Optional `forget` releases an installed or provisioned
|
|
4006
|
-
definition before removal; failure rejects removal ({§skills-remove}).
|
|
4106
|
+
definition before removal; failure rejects removal ({§skills-remove}). The
|
|
4107
|
+
seam's shapes — the identity a verb acts under, its options, definition
|
|
4108
|
+
sources, outcomes, preparation, the prepared result and the family handle —
|
|
4109
|
+
are declared once in `plurnk-contracts` and imported by core and every
|
|
4110
|
+
module; core adds only its own face of the seam, the runtime registration a
|
|
4111
|
+
resident family prepares and the scheme facet it may expose.
|
|
4007
4112
|
|
|
4008
4113
|
An adapter may expose a `scheme` facet beneath its family's runtime namespace
|
|
4009
4114
|
({§runtime-resource-binding}). A facet claims a path subtree and is the scheme's
|
|
@@ -4179,11 +4284,12 @@ Core's behavior behind them.
|
|
|
4179
4284
|
| §methods-conversation-worker Workspace lifecycle | `createConversationWorker({ workspaceId, name? })` | Creates a distinct model-origin root worker with empty history: a fresh conversation over the same world, not a fork or the stable default. |
|
|
4180
4285
|
| Workspace lifecycle | `forkWorker({ workspaceId, workerId, name? })` | Creates a child worker that branches the source worker's history while sharing workspace state. |
|
|
4181
4286
|
| §methods-workspace-rename Workspace metadata | `renameWorkspace(workspaceId, name)` | Changes only the world's unique mutable name; workers, log, and membership remain intact. |
|
|
4182
|
-
| §methods-workspace-prompts Workspace metadata | `listPrompts(workspaceId, limit?)` | Returns nonempty loop-seed prompts
|
|
4287
|
+
| §methods-workspace-prompts Workspace metadata | `listPrompts(workspaceId, limit?, workerId?)` | Returns nonempty loop-seed prompts a client addressed to the workspace's model workers, newest-first; `workerId` narrows to one worker. Authorship is the seed message's address ({§message-arrival}): a worker-issued seed (WORK, FORK, SEND to a worker) has none and is never history, whichever worker it seeded; a client prompt at a forked conversation worker is. An omitted limit is `PLURNK_SERVICE_PROMPTS_PAGE`. |
|
|
4183
4288
|
| Workspace metadata | `listWorkspaces()`, `workspaceDerivationStatus(...)` | Reads current workspace identity and derivation progress. |
|
|
4184
4289
|
| §methods-worker-read Worker topology | `readWorker({ workspaceId, identity })` | Ownership-bounds an exact id-or-name lookup and returns one durable Worker projection or `null` under {§application-worker-observation}. Supplying both identities or neither is invalid. |
|
|
4185
4290
|
| §methods-worker-list Worker topology | `listWorkers(workspaceId, query?)` | Returns the workspace's durable Worker projections under {§application-worker-observation}. The origin filter is exact; an explicitly present `parentWorkerId` filters roots (`null`) or one immediate parent (id), while omission returns every lineage position. Each projection carries `kind` (`conversation`, `fork` for a child with a fork boundary, `work` for any other child) and `lifecycle`, the representative work loop's status through {§loop-lifecycle-vocabulary} (`idle` with no work loop), so a directory row shows the same lifecycle glyph the bound worker's own status gauge shows; clients infer neither (#523). |
|
|
4186
4291
|
| §methods-worker-loops Loop lifecycle | `listWorkerLoops({ workspaceId, workerId })` | Ownership-checks the Worker and returns its Loops in sequence order under {§application-loop-observation}, including the validated exact terminal result when one exists. It performs no scheduling or event replay. |
|
|
4292
|
+
| §methods-worker-descendants Descendant spend | `descendantAccounting({ workspaceId, workerId, loopId })` | Ownership-checks the Worker and returns the {§provider-accounting} projection of every settled request on a loop that a descendant of the Worker (`parent_worker_id`, to any depth) ran after `loopId` — this delegation's spend, the same tree the turn cap counts ({§turn-cap-counts-the-tree}). The Worker's own loop is never included: its accounting stays {§notifications-loop-terminated}'s. `loopId: null` is the empty projection, explicit zero. Derived from the request ledger on every read ({§tokenomics-provider-usage}); no rollup, no second store. |
|
|
4187
4293
|
| Extension actions | `listModuleActions()`, `invokeModuleAction(name, params, context)` | Lists setup-registered `{ name, scope, inputSchema, outputSchema }` descriptors in sorted order. Invocation requires a context matching the registered scope; missing names, forged scope, and missing workspace identity fail before the owner runs. Handler values remain opaque to core. |
|
|
4188
4294
|
|
|
4189
4295
|
§methods-loop-run-fold-consistency **A folded prompt cannot silently reconfigure
|
|
@@ -4299,8 +4405,8 @@ registration; there is no separate per-tool availability system.
|
|
|
4299
4405
|
|
|
4300
4406
|
§model-catalog **Model discovery is a bounded local projection, not provider
|
|
4301
4407
|
activity.** Core composes the release-pinned Models.dev snapshot with
|
|
4302
|
-
provider-owned `{§model-catalog-readiness}` and {§provider-
|
|
4303
|
-
Each entry includes the exact route's admitted `
|
|
4408
|
+
provider-owned `{§model-catalog-readiness}` and {§provider-effort}.
|
|
4409
|
+
Each entry includes the exact route's admitted `efforts`; worker-level
|
|
4304
4410
|
model/spawn intersections and alias tuning are not catalog facts. The default query includes only
|
|
4305
4411
|
providers configured enough to attempt; `availability: "all"` includes every
|
|
4306
4412
|
catalog model with structured missing-configuration causes. Provider and text
|
|
@@ -4322,8 +4428,8 @@ cascade. A WORK/FORK child copies the spawning loop's effective spawn model
|
|
|
4322
4428
|
no live link and begins with no override, so a later parent change affects
|
|
4323
4429
|
only that worker's future loops and descendants. Client operation actors and
|
|
4324
4430
|
Plurnk-owned bookkeeping workers run no model loops and own no model
|
|
4325
|
-
selection; the model, spawn-override, and
|
|
4326
|
-
`409 model-worker-required` before any policy row is initialized or written. An explicit model, spawn-override, or
|
|
4431
|
+
selection; the model, spawn-override, and effort controls refuse them with
|
|
4432
|
+
`409 model-worker-required` before any policy row is initialized or written. An explicit model, spawn-override, or effort change while
|
|
4327
4433
|
the worker holds any queued, running, or parked loop is a precise
|
|
4328
4434
|
`409 worker-loop-active` ({§worker-lifecycle-live}), independent of a process-local
|
|
4329
4435
|
drain. The policy write checks liveness atomically, including selections carried
|
|
@@ -4333,11 +4439,11 @@ First-time initialization of an unset worker model remains legal and never
|
|
|
4333
4439
|
rewrites an existing loop's generation snapshot.
|
|
4334
4440
|
|
|
4335
4441
|
A client-created branch copies the source worker's durable model, spawn
|
|
4336
|
-
override, and
|
|
4442
|
+
override, and effort by value alongside its history. It retains no
|
|
4337
4443
|
live policy link to the source worker.
|
|
4338
4444
|
|
|
4339
|
-
§worker-
|
|
4340
|
-
worker model has exactly one member of the shared `{§
|
|
4445
|
+
§worker-effort **Reasoning is a durable worker policy.** Each selected
|
|
4446
|
+
worker model has exactly one member of the shared `{§effort-wire}`;
|
|
4341
4447
|
a modelless worker has none. A declared alias's scoped environment value—or the
|
|
4342
4448
|
global provider value for an exact route—seeds the policy only when the worker
|
|
4343
4449
|
first receives its model. Model identity and reasoning
|
|
@@ -4346,17 +4452,17 @@ token ceilings remain separate concerns. An explicit policy change validates
|
|
|
4346
4452
|
the exact policy against both the worker model and its optional spawn model and
|
|
4347
4453
|
is refused while the worker owns a live or parked loop. Effort is identity-grade:
|
|
4348
4454
|
every client-visible model route carries the worker's durable policy as
|
|
4349
|
-
`
|
|
4455
|
+
`effort`, omitted only when the cataloged model has no reasoning
|
|
4350
4456
|
dimension. Client inspection
|
|
4351
4457
|
returns the supported-policy intersection of those two routes. Inspection or
|
|
4352
4458
|
mutation materializes the daemon-default model and policy onto an uninitialized
|
|
4353
4459
|
model worker before answering; a deliberately modelless daemon remains unset.
|
|
4354
4460
|
|
|
4355
|
-
§worker-
|
|
4356
|
-
`
|
|
4357
|
-
or provider configuration, `explicit` only after `worker.
|
|
4358
|
-
returns `source`, and a projected `ModelRoute` carries `
|
|
4359
|
-
`
|
|
4461
|
+
§worker-effort-source **A default never masquerades as a choice.** The worker row records
|
|
4462
|
+
`effort_source` beside `effort`: `default` when the value was seeded from the alias
|
|
4463
|
+
or provider configuration, `explicit` only after `worker.effort.set`. `worker.effort.get`
|
|
4464
|
+
returns `source`, and a projected `ModelRoute` carries `effortSource` exactly when it carries
|
|
4465
|
+
`effort`, so a client can render `deepdumb[low]` differently from a seeded `low` without
|
|
4360
4466
|
inferring anything. Selecting a new model keeps an explicit policy (validated against the new
|
|
4361
4467
|
model) and re-derives a default one from the new alias, so a seeded value never outlives the alias
|
|
4362
4468
|
that supplied it; the source itself is not part of the mid-loop generation-change check, because
|
|
@@ -4377,7 +4483,7 @@ addressed worker before the loop snapshots it; an omitted selector is not a
|
|
|
4377
4483
|
selection and continues the worker's durable model
|
|
4378
4484
|
({§worker-model-selection}). The fully resolved provider identity and reasoning
|
|
4379
4485
|
policy are persisted on the loop and remain immutable through turns, parks,
|
|
4380
|
-
wakes, and restart ({§worker-
|
|
4486
|
+
wakes, and restart ({§worker-effort}).
|
|
4381
4487
|
Injecting into an existing loop with a conflicting explicit selection fails
|
|
4382
4488
|
before work is accepted. Provider instances are cached; no resume path
|
|
4383
4489
|
substitutes a boot default for missing or malformed durable selection.
|
|
@@ -4436,6 +4542,7 @@ adding a loop to it. LOOK text anchors resolve through the same
|
|
|
4436
4542
|
| §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. |
|
|
4437
4543
|
| §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. |
|
|
4438
4544
|
| §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. |
|
|
4545
|
+
| §notifications-outside-event `outside/event` | `{ workerId, loopId, turnId, coordinate, text, tokens }` | An admitted emission carried text outside every operation ({§outside-text}): once per admitted emission, the exact stored text, its packet weight, and the turn's `<worker>-<loop>-<turn>` coordinate. It is transient presentation evidence; the turn's `outside` source remains the durable authority. |
|
|
4439
4546
|
| §notifications-reasoning-event `reasoning/event` | `{ workerId, loopId, turnId, modelCallId, requestSequence, phase, delta? }` | A main emission call exposes readable reasoning. Each physical request that emits reasoning owns a distinct positive `requestSequence` and balanced start/content/end stream; opening a retry closes the preceding stream before any retry delta. Only content carries a nonempty exact delta. It is transient presentation evidence, never a log row, Notice, packet field, or BARE/child channel. The settled provider response remains the durable authority. |
|
|
4440
4547
|
|
|
4441
4548
|
§notifications-stream-event-failure-isolation The plugin-facing
|
|
@@ -4487,6 +4594,26 @@ flowchart LR
|
|
|
4487
4594
|
measure --> rail[Engine budget admission and dispatch]
|
|
4488
4595
|
```
|
|
4489
4596
|
|
|
4597
|
+
### §packet-wire-envelope The wire envelope
|
|
4598
|
+
|
|
4599
|
+
The packet reaches the provider under the roles the model was tuned on, its bytes unchanged:
|
|
4600
|
+
|
|
4601
|
+
| Message | Role | Content |
|
|
4602
|
+
|:--|:--|:--|
|
|
4603
|
+
| 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).
|
|
4616
|
+
|
|
4490
4617
|
### §packet-cache-monotone Default order and cache locality
|
|
4491
4618
|
|
|
4492
4619
|
Conditional absence never reorders the surviving default sections.
|
|
@@ -4497,7 +4624,7 @@ Conditional absence never reorders the surviving default sections.
|
|
|
4497
4624
|
| 2 | system | `system-policy` | Operator policy; empty content is omitted on the wire. |
|
|
4498
4625
|
| 3 | system | `inject` | Present only when operator notes are configured. |
|
|
4499
4626
|
| 4 | user | `log` | Append-mostly model-visible history; the first user section, so the cached prefix ends inside it. |
|
|
4500
|
-
| 5 | user | `worker` | `Worker`: `{"path": "worker://alice", "parent": <address or null>, "loop": L, "turn": T}`, the actor
|
|
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}). |
|
|
4501
4628
|
| 6 | user | `delegation` | `Delegation`: per-turn `{workers, streams}` pointers; always present, each list `[]` when empty ({§packet-empty-sections}). |
|
|
4502
4629
|
| 7 | user | `errors` | Per-turn failure pointers; empty content is omitted. |
|
|
4503
4630
|
| 8 | user | `notices` | Per-turn observations; empty content is omitted. |
|
|
@@ -4538,14 +4665,14 @@ time of measurement.
|
|
|
4538
4665
|
| Fact | Owner and unit | Time | Contract |
|
|
4539
4666
|
|:-----|:---------------|:-----|:---------|
|
|
4540
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. |
|
|
4541
|
-
| §tokenomics-context-envelope-admission Provider input capacity | Provider model limits and configured
|
|
4542
|
-
| Provider generation envelope | Provider
|
|
4668
|
+
| §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
|
+
| 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}. |
|
|
4543
4670
|
| 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. |
|
|
4544
4671
|
|
|
4545
|
-
- §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 (a trailing newline terminates the last line; empty content has none), so a catalog lists extent without reading bodies.
|
|
4672
|
+
- §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.
|
|
4546
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.
|
|
4547
4674
|
- §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}).
|
|
4548
|
-
- §tokenomics-window-partition **One capacity derivation; no service-side token budget.** The provider owns model limits
|
|
4675
|
+
- §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.
|
|
4549
4676
|
- §tokenomics-prompt-projection-share **Prompt projection is stable packet policy.**
|
|
4550
4677
|
`PLURNK_SERVICE_PROMPT_PROJECTION` is a required alias-scoped percentage in
|
|
4551
4678
|
`(0, 100)`. It allocates that share of the cold-start curation allowance
|
|
@@ -4706,8 +4833,7 @@ Stream progress remains owned by {§exec-stream}.
|
|
|
4706
4833
|
|
|
4707
4834
|
## §packet Packet shape
|
|
4708
4835
|
|
|
4709
|
-
§packet-markdown **The packet's Markdown projection
|
|
4710
|
-
projection package retired (#626).** Core renders the transformed section list
|
|
4836
|
+
§packet-markdown **The packet's Markdown projection (#626).** Core renders the transformed section list
|
|
4711
4837
|
into one system string and one user string. Within each slot, list order is
|
|
4712
4838
|
preserved. A nonempty section with a header renders as an H2 immediately followed
|
|
4713
4839
|
by its JSON object/array content; non-JSON content has one blank line after the
|
|
@@ -4769,8 +4895,7 @@ directly with `json_extract`. A packet transformed by a plugin, or any non-log s
|
|
|
4769
4895
|
item. Items no composition references are transient data: `retention_collect_packet_items`
|
|
4770
4896
|
collects them under the retention policy ({§retention-policy}), which is how a deleted worker's or
|
|
4771
4897
|
workspace's packets release their space while shared items survive. A fork copies the composition
|
|
4772
|
-
and shares the items ({§worker-fork-trigger}).
|
|
4773
|
-
`turn_packets` view and is recreated, never read, under {§db-schema-baseline}.
|
|
4898
|
+
and shares the items ({§worker-fork-trigger}).
|
|
4774
4899
|
|
|
4775
4900
|
| Field | Presence | Contract |
|
|
4776
4901
|
| ----------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -4793,23 +4918,30 @@ turn receives a note instead of a fabricated response.
|
|
|
4793
4918
|
§digest-turn-artifact-identity **Digest packet artifacts project durable turns.**
|
|
4794
4919
|
After selectors are applied, digest retains every turn with exact program source, a
|
|
4795
4920
|
valid stored provider request, or malformed stored packet evidence; orders those
|
|
4796
|
-
turns by durable chronology; and names
|
|
4921
|
+
turns by durable chronology; and names each by its log coordinate ({§share-packet-names}). The
|
|
4797
4922
|
producer does not affect projection.
|
|
4798
4923
|
|
|
4924
|
+
§share-packet-names **Packet artifacts carry the coordinate the log uses.** A turn's files are named
|
|
4925
|
+
`<worker>-<loop>-<turn>`, the worker's name and the loop and turn sequences that `log:///<loop>/<turn>/…`
|
|
4926
|
+
addresses: the model's first turn in its first loop is `<worker>-1-2`, because the initialization
|
|
4927
|
+
survey is turn 1 and writes no packet. A digest spanning several workspaces nests each workspace's
|
|
4928
|
+
files in a folder named for it. `digest.json` records each turn's stem as `artifact`, so no
|
|
4929
|
+
consumer reconstructs a name. A name that cannot be a file name, or two turns sharing one, fails.
|
|
4930
|
+
|
|
4799
4931
|
| Artifact | Present when | Authority |
|
|
4800
4932
|
|----------|--------------|-----------|
|
|
4801
|
-
|
|
|
4802
|
-
|
|
|
4933
|
+
| `<stem>.assistant.md` | The turn has an `ops` source | Exact `turn_sources.content`, independent of log rows |
|
|
4934
|
+
| `<stem>.system.md`, `<stem>.user.md` | The turn stored a provider request | Stored text sections projected through `PacketWire`; native parts are not Markdown |
|
|
4803
4935
|
| `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. |
|
|
4804
|
-
|
|
|
4805
|
-
|
|
|
4806
|
-
|
|
|
4807
|
-
|
|
|
4936
|
+
| `<stem>.assistantRaw.json` | The request has an admitted provider response | Stored opaque provider response |
|
|
4937
|
+
| `<stem>.response.md`, attempt artifacts | The request received no admitted response | Stored request and attempt state |
|
|
4938
|
+
| `<stem>.packet.raw.txt` | The stored packet fails typed validation | Exact stored packet text |
|
|
4939
|
+
| `<stem>.packet.invalid.json` | The stored packet fails typed validation | Turn identity and complete validation error chain |
|
|
4808
4940
|
|
|
4809
4941
|
A source-backed turn without provider participation therefore produces only
|
|
4810
4942
|
`assistant.md`; a request-only turn produces no fabricated assistant. A
|
|
4811
4943
|
source-less programmatic turn with no provider request has no forensic payload
|
|
4812
|
-
to project and
|
|
4944
|
+
to project and writes no files.
|
|
4813
4945
|
|
|
4814
4946
|
The external tokenless draft and transformation boundary is owned by
|
|
4815
4947
|
{§scheme-packet-transform}. Core alone extends each validated draft with its
|
|
@@ -4905,11 +5037,9 @@ producers notify the same settlement path after durable execution; reply wake-up
|
|
|
4905
5037
|
shows in Open Messages and in an arrival row's `resource`. A client's own identity for the same
|
|
4906
5038
|
message — an AG-UI message UUID, an A2A address — stays the durable `path` that correlation,
|
|
4907
5039
|
delivery and reply accounting use, and remains addressable in its own scheme; the short form is an
|
|
4908
|
-
additional alias. Answering either reaches the same message.
|
|
4909
|
-
packet showed a 77-character `agui://anonymous/threads/…/messages/<uuid>` twice per open message,
|
|
4910
|
-
while the docs taught the short form.
|
|
5040
|
+
additional alias. Answering either reaches the same message.
|
|
4911
5041
|
|
|
4912
|
-
§message-causal-source **Message authorship and delivery are distinct facts.** The harness publishes every arrival row; the row's `source` carries the canonical address of the causal actor. Native WORK, FORK, and directed worker SEND derive `worker://<sender>` from the authenticated sender worker ID. A trusted exterior adapter supplies its own canonical actor address through {§methods-loop-run}: the AG-UI bridge names the client's message under `agui://` ({§agui-run-source}), the inbound A2A adapter under `a2a://`. An absent source is the operator. Attribution persists with the message through the inbox, parking, orphan recovery, restart, and later log projection; model syntax cannot author it. The wire renders the row's `source` in place of its `origin`, which is constant for every arrival, except where the source is the transport that minted this very message, which says nothing the address does not ({§message-short-identity}). The operator's message is then the one arrival with no sender to show, so it renders `"origin": "user"
|
|
5042
|
+
§message-causal-source **Message authorship and delivery are distinct facts.** The harness publishes every arrival row; the row's `source` carries the canonical address of the causal actor. Native WORK, FORK, and directed worker SEND derive `worker://<sender>` from the authenticated sender worker ID. A trusted exterior adapter supplies its own canonical actor address through {§methods-loop-run}: the AG-UI bridge names the client's message under `agui://` ({§agui-run-source}), the inbound A2A adapter under `a2a://`. An absent source is the operator. Attribution persists with the message through the inbox, parking, orphan recovery, restart, and later log projection; model syntax cannot author it. The wire renders the row's `source` in place of its `origin`, which is constant for every arrival, except where the source is the transport that minted this very message, which says nothing the address does not ({§message-short-identity}). The operator's message is then the one arrival with no sender to show, so it renders `"origin": "user"`; a bare arrival would read as the model's own SEND and hide the request it answers. The Open Messages pointer carries the same attribution ({§message-arrival}).
|
|
4913
5043
|
|
|
4914
5044
|
§message-projection **Message storage is unbounded by model context; automatic materialization is not.** Core persists every accepted message completely before packet assembly. The selected provider's derived `inputCapacity` and the alias-resolved percentage from `PLURNK_SERVICE_PROMPT_PROJECTION` derive one aggregate curation-weight allowance for the visible bodies of arrivals other than a peer worker's — every `source` that is not a `worker://` address, the loop's own assignment included. Complete bodies render when their aggregate weight fits. Otherwise all such visible rows share the allowance: full bodies consume only their required share, unused shares are redistributed, and partial bodies render the largest leading complete-line region that fits their share or an exact character-bound prefix when the first physical line alone is larger. The sum of their rendered body weights never exceeds the allowance. Every partial body carries `preview` under {§packet-extent-metadata}. The row remains complete and READable by coordinate; its `log:///` body additionally obeys deliberate curation under {§log-readable-projection}. A peer worker's message takes the ordinary bounds. When provider input capacity is unknown the percentage is underivable, so arrival rows retain the ordinary bounded projection rather than inventing capacity. This policy never rejects, summarizes, or discards a message because it exceeds a context window.
|
|
4915
5045
|
|
|
@@ -4992,24 +5122,39 @@ retain distinct contracts and lifetimes.
|
|
|
4992
5122
|
|
|
4993
5123
|
§notice-event-notify **Client surface.** Engine Notices broadcast live via the `notice/event` notification — `{ workerId, loopId, notice: { source, kind, level, message?, position?, …kind-specific } }` per the grammar's `Notice` schema — the moment they land. A loop Notice names its owning Worker; workspace derivation progress alone carries `workerId=null, loopId=0`. AG-UI projects the same observation as the custom `plurnk.notice` event. Failures do not broadcast on this surface: they are log rows, and the client reads them through `log.read` / the `log/entry` notification, the durable log.
|
|
4994
5124
|
|
|
5125
|
+
§loop-status-notice **The drain beats the loop's lifecycle on the notice channel.** When the drain claims a loop to run it broadcasts `notice/event` `{ workerId, loopId, notice: { source: "engine:lifecycle", kind: "loop_status", level: "info", status: 102 } }`, and when it leaves a loop parked ({§loop-wake-identity}) the same with `status: 202`; a wake that the drain claims again is another `102`. The beat is transient: broadcast to the workspace like any notice ({§notice-event-notify}), never a log row, never in a packet. Terminals stay `loop/terminated`'s; a client that reads the beat has the running/parked edges a parked delegation otherwise never publishes.
|
|
5126
|
+
|
|
5127
|
+
§share **A share is the database's record, ready to send.** `plurnk-service share [<file.db>] [<folder>]`, and `npm run share` from a checkout, take a consistent copy of the database (`VACUUM INTO`; a live database is never read in place), and write its digest into `<folder>`, an ordinary folder the user archives or attaches however they like. Without a database the service's own is shared. Nothing is overwritten: a folder that exists and is not empty is refused, and a caller reusing a place removes it first. The share is the user's bug report and our dogfood, benchmark and forensics artifact alike.
|
|
5128
|
+
|
|
5129
|
+
§share-snapshot **A database is copied by SQLite, never by the filesystem.** `Share.snapshot(dbPath, copy)`, exported as `@plurnk/plurnk-service/share` with `Share.write`, is the one consistent copy: a byte copy of a WAL-mode database drops every committed page still in its `-wal` file. A harness that keeps the database beside its digest takes it through `snapshot`; an existing `copy` is refused.
|
|
5130
|
+
|
|
5131
|
+
§share-scope **A share is unredacted.** `--workspace=<id>` limits a share to one workspace; without it the whole database is shared. Nothing is filtered, redacted or scanned: a share holds what the models saw and wrote in scope, including prompts, file contents read, command output and reasoning, and the command says so. `--requiem` adds the forensic interview ({§digest-requiem}), which calls a model; `plurnk-service requiem <file.db> <folder>` adds it later to a digest already written.
|
|
5132
|
+
|
|
5133
|
+
§share-folder **Shares land in one place.** With no folder named, a share is a stamped child, `share-<UTC stamp>`, of `PLURNK_SERVICE_SHARE_FOLDER` (a leading `~/` expands, as for every explicit Plurnk path) or of `$XDG_STATE_HOME/plurnk/shares`.
|
|
5134
|
+
|
|
4995
5135
|
§digest-programmatic-surface **The digest is an importable forensic surface.**
|
|
4996
5136
|
|
|
4997
5137
|
| Surface | Contract |
|
|
4998
5138
|
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
4999
5139
|
| Import `@plurnk/plurnk-service/digest` | Ships `Digest` and its package-owned SqlRite statements; importing performs no I/O or process action. The CLI wrapper alone invokes it. |
|
|
5000
5140
|
| `run({ dbPath })` | Reads the required database and writes a complete digest to `./test/digest` relative to the caller's working directory. |
|
|
5001
|
-
| `digestDir` | Selects a nonempty output
|
|
5141
|
+
| `digestDir` | Selects a nonempty output path. `run` refuses a folder that exists and is not empty, and `requiem` refuses an existing `requiem.json` or `requiem.md`, before database or provider I/O; neither deletes ({§share}). Concurrent callers use distinct folders. |
|
|
5002
5142
|
| Reader lifetime | `run` reads heavy evidence on demand while rendering, then closes its reader on success or failure. `requiem` closes its reader before awaiting witness inference. |
|
|
5143
|
+
| §candidate-pinned-runtime Candidate runtime | At launch, after its optional build, the candidate copies every workspace's package projection (`files`) into `<state>/runtime`, links third-party dependencies, and resolves `@plurnk/*` to those copies. Its daemon and its digest export both run from that copy, never the shared checkout, so neither source edits nor a concurrent candidate's or developer's rebuild after launch changes the code a run finishes on. The copy is removed once the digest is written. |
|
|
5003
5144
|
| Export completion | Packet and response bodies are read and serialized one record at a time, without discarding evidence. `digest.json` is promoted from a partial file only after every artifact is written; its absence identifies an incomplete export. |
|
|
5004
5145
|
| `workerId` | Narrows workers and every dependent loop, turn, turn-attached logical inference, specialization, physical request, and log row to that one worker. |
|
|
5005
5146
|
| `workspaceId` | Narrows workers plus every logical inference and dependent evidence owned by one workspace, when both selectors are present they intersect. |
|
|
5006
5147
|
|
|
5007
5148
|
§digest-cost-kind **Cost basis named.** A rendered Cost line carries the basis of its dollar figure: `(charged)` only when every settled request's cost is provider-charged; `(estimated — catalog rates)` when any settled request's cost is an estimate, because a mixed sum is no more trustworthy than its weakest term. A dollar figure without its basis reads as billed truth, and an estimate must never impersonate a charge.
|
|
5008
5149
|
|
|
5009
|
-
§output-allowance-notice **The output allowance is not disclosed; a ceiling cut names its cause.** The packet's budget section carries the curation state and no response allowance: a model does not plan in tokens and no harness tells it its output ceiling, so the number
|
|
5150
|
+
§output-allowance-notice **The output allowance is not disclosed; a ceiling cut names its cause.** The packet's budget section carries the curation state and no response allowance: a model does not plan in tokens and no harness tells it its output ceiling, so the number is a fact without a use (#826). Overflow tolerance (#482) is likewise never advertised; a cut's notice names the true per-call grant from the response's own capacity record. When a provider finish is `length`, the engine emits an `output_truncated` notice (source `engine:capacity`) naming the allowance — the fact alone, never advice on what to do about it — on every path — railed or not — and the rails verdict never blames the model's grammar for a cut the engine's own ceiling made. The same precedence governs a cut so deep no operation parses: the rejection notice names the truncation as the cause, not the parser's symptom, overriding {§invalid-emission-attempts}'s parser diagnostic for `length` finishes.
|
|
5010
5151
|
|
|
5011
5152
|
§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.
|
|
5012
5153
|
|
|
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.
|
|
5155
|
+
|
|
5156
|
+
§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
|
+
|
|
5013
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.
|
|
5014
5159
|
|
|
5015
5160
|
Unrecognized actionless log rows are retained and labelled as such, not
|
|
@@ -5147,8 +5292,7 @@ or an unknown enabled alias fails the daemon at boot.
|
|
|
5147
5292
|
namespace`, narrowed by `settings.membersModelScope` (most restrictive wins). `none`
|
|
5148
5293
|
refuses every model definition — inclusion or exclusion — as `403
|
|
5149
5294
|
members/functionality/model-scope`, naming `git add` and the operator's `/members add` as
|
|
5150
|
-
the paths that remain; `root` admits patterns inside the root; `namespace
|
|
5151
|
-
default, admits `../` too.
|
|
5295
|
+
the paths that remain; `root` admits patterns inside the root; `namespace` admits `../` too.
|
|
5152
5296
|
`auto` loops self-approve proposals, so the ceiling — not the proposal — is the guard
|
|
5153
5297
|
({§membership-baseline}). The coordinator hands `admit` the caller (`action` | `operation`)
|
|
5154
5298
|
so the family bounds the model without a second grammar.
|
|
@@ -5188,7 +5332,7 @@ source it rides the definition. The workspace's durable state owns enablement
|
|
|
5188
5332
|
model-facing trace.
|
|
5189
5333
|
|
|
5190
5334
|
*Discovery is inert.* `discover {query}` searches the ecosystem registry
|
|
5191
|
-
(`PLURNK_SERVICE_SKILLS_REGISTRY_URL
|
|
5335
|
+
(`PLURNK_SERVICE_SKILLS_REGISTRY_URL`; empty disables it
|
|
5192
5336
|
with 501 `registry-not-configured`) and returns one candidate per hit with
|
|
5193
5337
|
`registry` provenance and the exact `owner/repo` source. `discover {source}`
|
|
5194
5338
|
lists the skills one standard package reference contains with `source`
|
|
@@ -5204,8 +5348,8 @@ names, rather than the coordinator's generic default.
|
|
|
5204
5348
|
*Preparation.* For each enabled alias the adapter selects the host-provided
|
|
5205
5349
|
tree for `service` scope or locates the directory at the filesystem scope;
|
|
5206
5350
|
a workspace definition whose directory is absent is installed
|
|
5207
|
-
through the standard CLI (`PLURNK_SERVICE_SKILLS_CLI`,
|
|
5208
|
-
|
|
5351
|
+
through the standard CLI (`PLURNK_SERVICE_SKILLS_CLI`, invoked as
|
|
5352
|
+
`<cli> add <source> --agent universal --skill <name> --yes [--global]`, run with
|
|
5209
5353
|
`HOME` set to the service's user home so the installer's `~` is the global
|
|
5210
5354
|
root) and the installed `SKILL.md` — never the installer's output — is the
|
|
5211
5355
|
evidence.
|
|
@@ -5297,11 +5441,11 @@ section because they are language extensions rather than executable tools.
|
|
|
5297
5441
|
|
|
5298
5442
|
### §inject system.inject — the operator injection
|
|
5299
5443
|
|
|
5300
|
-
§packet-inject When `PLURNK_SERVICE_PACKET_INJECT` names a readable markdown file, its content renders as an `## Operator Notes` section in the system slot (definition → policy → inject). Read per-turn so the operator's edits take effect live; a set-but-unreadable path fails the turn hard
|
|
5444
|
+
§packet-inject When `PLURNK_SERVICE_PACKET_INJECT` names a readable markdown file, its content renders as an `## Operator Notes` section in the system slot (definition → policy → inject). Read per-turn so the operator's edits take effect live; a set-but-unreadable path fails the turn hard as {§policy-sections} rules. `~/` expands to home. It's the operator-side complement to the plugin section hook — a pressure valve so reshaping the packet edits operator content, never the core. Unset → no section.
|
|
5301
5445
|
|
|
5302
5446
|
### §policy system.policy — the client's policy injection
|
|
5303
5447
|
|
|
5304
|
-
§policy-sections One section rides the system slot **after the definition**: the contents of `PLURNK_SERVICE_POLICY` (
|
|
5448
|
+
§policy-sections One section rides the system slot **after the definition**: the contents of `PLURNK_SERVICE_POLICY` (unset resolves to the policy member in {§host-path-layout}), with no engine-generated heading. The policy document owns its Markdown structure. Policy is the client's authoritative rules promoted into the privileged zone — NOT a log entry; the model cannot READ or KILL it. A default-absent path is silent (the section is omitted); an explicit override (env set) that fails to read fails the turn hard — a deliberate setting with a broken path is a misconfig, surfaced not hidden. Read per-turn so edits take effect live. The PROJECT `AGENTS.md` is local guidance, not policy: it rides turn 0 as the foisted `worker:///_plurnk/AGENTS.md` entry ({§turn0-agents-stunt}); references and skills use native discovery ({§skills-functionality}).
|
|
5305
5449
|
|
|
5306
5450
|
On first run, and only when `$XDG_CONFIG_HOME/plurnk` itself is absent, the service seeds
|
|
5307
5451
|
`AGENTS.md` from `@plurnk/plurnk-meta/POLICY.md` ({§teaching-corpus}).
|
|
@@ -5461,6 +5605,17 @@ READ/EDIT/COPY/MOVE scopes use the same physical text:
|
|
|
5461
5605
|
One/two-coordinate line shorthand is newline-aware so deleting a line does not
|
|
5462
5606
|
leave an empty line. A terminal position after a final newline is an exact
|
|
5463
5607
|
insertion anchor, not an additional whole line. `<1,-1>` selects all content.
|
|
5608
|
+
|
|
5609
|
+
§zero-width-column-one-insert **A zero-width region at column 1 inserts whole lines.** The
|
|
5610
|
+
schemes region algebra ({§slicer-text-algebra}) inserts every body verbatim; the missing
|
|
5611
|
+
newline is a fence artifact, so core repairs it where a fenced EDIT body becomes inserted
|
|
5612
|
+
content, through the one schemes helper `wholeLineBody`, at the mutation and again in the
|
|
5613
|
+
receipt and anchor-continuity recomputation so every site sees one body. At `<L,1,L,1>`,
|
|
5614
|
+
an anchored `<@hash,1,@hash,1>`, or `L` = final line + 1 when the content ends with a
|
|
5615
|
+
newline, a non-empty body that does not end in a newline is inserted with the content's
|
|
5616
|
+
line separator appended, so `X` at `<2,1,2,1>` into `a\nb` yields `a\nX\nb`. An empty
|
|
5617
|
+
body inserts nothing. A zero-width region at any other column stays a byte-exact insert with
|
|
5618
|
+
nothing appended. COPY and MOVE transfer source bytes, not a fenced body, and are untouched.
|
|
5464
5619
|
The runtime also tolerates an authored three-coordinate
|
|
5465
5620
|
`<startLine,startColumn,endLine>` scope, immediately lowers it to the complete
|
|
5466
5621
|
four-coordinate region ending after the final code point of `endLine`, and
|
|
@@ -5572,7 +5727,7 @@ Carried from the contract walk; durable.
|
|
|
5572
5727
|
|
|
5573
5728
|
A KILL with a text-coordinate scope aimed at an entry-bearing scheme deletes exactly that span: core prepares and dispatches it as an EDIT with an empty body over the same marker, so anchors resolve, proposals gate it, and the merge facts and receipt are the EDIT path's — while the log row records the model's KILL. Its packet metadata and canonical log body use {§edit-result-receipt-projection}. ```` ```EDIT (path) <scope> ```` with an empty body remains the same act spelled the other way; the teaching names KILL.
|
|
5574
5729
|
|
|
5575
|
-
§kill-pattern **A pattern on an entry KILL deletes each matching line.** ```` ```KILL (path) [{"pattern": "beta"}] ```` takes the same EDIT path as a scoped KILL, expanded under {§edit-pattern} in whole lines: the resource is read once, the matcher runs line by line, and every line a match touches becomes one empty-body line splice in one atomic batch guarded by those lines' anchors. A numeric scope bounds the lines the pattern may touch. Zero matches change nothing (204, `matched: 0`); a whole-entry KILL never widens from a pattern that selected nothing. The receipt is the EDIT path's, compacted the same way: `matched` lines, the first deletion's `receipt` with its `removedText` ({§edit-receipt-removed-text}), and `last` for the final one. Node-selecting patterns (`//`, `$`) select whole lines here, as they name nodes with line extents; resource-selecting ones (`~`, `&`) are refused (400 `pattern-dialect-unsupported
|
|
5730
|
+
§kill-pattern **A pattern on an entry KILL deletes each matching line.** ```` ```KILL (path) [{"pattern": "beta"}] ```` takes the same EDIT path as a scoped KILL, expanded under {§edit-pattern} in whole lines: the resource is read once, the matcher runs line by line, and every line a match touches becomes one empty-body line splice in one atomic batch guarded by those lines' anchors. A numeric scope bounds the lines the pattern may touch. Zero matches change nothing (204, `matched: 0`); a whole-entry KILL never widens from a pattern that selected nothing. The receipt is the EDIT path's, compacted the same way: `matched` lines, the first deletion's `receipt` with its `removedText` ({§edit-receipt-removed-text}), and `last` for the final one. Node-selecting patterns (`//`, `$`) select whole lines here, as they name nodes with line extents; resource-selecting ones (`~`, `&`) are refused (400 `pattern-dialect-unsupported`, {§pattern-dialect-find-only}). The log stays the exception: a pattern on `log:///` selects rows ({§log-curation-set-selection}), and a stream scheme's KILL is process control, so a pattern there is 400 `kill-pattern-unsupported`.
|
|
5576
5731
|
|
|
5577
5732
|
---
|
|
5578
5733
|
|
|
@@ -5617,3 +5772,154 @@ marker file or a sweep; an unstamped invocation is not a special case with its o
|
|
|
5617
5772
|
simply an unstamped run with its own directory. A stamped run that passes is reclaimed when it
|
|
5618
5773
|
exits; a failed suite's evidence is never touched and stays exactly where the run reported it. A cross-package test may reuse Core's migration fixture only by passing a path inside the
|
|
5619
5774
|
caller's own run directory.
|
|
5775
|
+
|
|
5776
|
+
§fs-world-state **The world-state harness — coverage that closes the class.** Op-outcome tests check what an op returned; the harness checks the resulting world. `WorldState.check(db)` asserts, pure-db and read-only: identity uniqueness in practice (no tuple holds two rows), the canonical fixpoint on every file-class key, channel orphan-freedom, the closed admission set (every file row's origin is Git or constraint), and sig-coherence. Generated-pick incorporation and lifecycle require filesystem/Git evidence and are covered by the composed creation matrix rather than a false pure-database proxy. The harness runs as a lifecycle-test epilogue and at every soak turn boundary, where the delta half applies: an idle turn grows the entries table by ZERO. A violation names its law and its row.
|
|
5777
|
+
|
|
5778
|
+
## Problem codes and pinned wording
|
|
5779
|
+
|
|
5780
|
+
Every Problem code core mints is named here under its family ({§problem-error-carrier} carries it); the root lint (`scripts/problem-codes.mjs`) refuses a code no owning SPEC names.
|
|
5781
|
+
|
|
5782
|
+
§problems-dispatch **Dispatch Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
|
|
5783
|
+
|
|
5784
|
+
| code | status | contract |
|
|
5785
|
+
|---|---:|---|
|
|
5786
|
+
| `target-required` | 400 | *OP* requires a target path. Recovery: Write the target in parentheses on the opening fence line: `OP (path)`. |
|
|
5787
|
+
| `scheme-not-found` | 501 | Scheme '*name*' is not registered. |
|
|
5788
|
+
| `scheme-metadata-unsupported` | 400 | *OP* on '*scheme*' does not accept the [metadata] modifier. |
|
|
5789
|
+
| `operation-not-implemented` | 501 | Scheme '*name*' does not implement *OP* (or exec). |
|
|
5790
|
+
| `entry-read-not-implemented` | 501 | The '*scheme*' scheme does not provide entry reads. |
|
|
5791
|
+
| `entry-write-not-implemented` | 501 | The '*scheme*' scheme does not provide entry writes. |
|
|
5792
|
+
| `entry-delete-not-implemented` | 501 | The '*scheme*' scheme does not provide entry deletion. |
|
|
5793
|
+
| `channel-delete-not-implemented` | 501 | The '*scheme*' scheme does not provide channel deletion. |
|
|
5794
|
+
| `scheme-handler-threw` | 500 | The '*scheme*' scheme did not produce a result for *OP*. |
|
|
5795
|
+
| `exec-source-not-data` | 501 | Scheme '*name*' is not a data source for an execution. |
|
|
5796
|
+
| `writer-forbidden` | 403 | Writer '*origin*' cannot modify scheme '*name*'. |
|
|
5797
|
+
| `capability-denied` | 403 | Capability '*route*' is denied by *scope* policy. |
|
|
5798
|
+
| `spawn-prompt-empty` | 422 | *OP* has no prompt text: the resource is empty and there is no body. |
|
|
5799
|
+
| `message-not-found` | 404 | No accepted message exists at *address*. |
|
|
5800
|
+
| `edit-collision` | 409 | EDIT collided with the current resource state ({§edit-collision}). Recovery: *n* of *m* edits applied. READ the target for current coordinates. |
|
|
5801
|
+
| `edit-target-required` | 400 | A line-anchored EDIT requires a target resource. Recovery: Provide the target that rendered the line anchor. |
|
|
5802
|
+
| `kill-target-required` | 400 | KILL requires a target path. |
|
|
5803
|
+
| `kill-target-scheme-required` | 400 | KILL target requires a scheme. |
|
|
5804
|
+
| `worker-not-found` | 404 | Worker '*name*' does not exist in this workspace. |
|
|
5805
|
+
| `entry-operation-unsupported` | 400 | KILL requires an entry-bearing target; '*scheme*' does not provide one. |
|
|
5806
|
+
| `resource-scheme-required` | 400 | Resource selection requires an address. |
|
|
5807
|
+
| `channel-required` | 400 | The '*scheme*' scheme has no default channel. Recovery: Address a named channel with a URI fragment. |
|
|
5808
|
+
| `binary-source-unsupported` | 415 | Channel #*name* is binary and its scheme keeps no bytes to transfer. |
|
|
5809
|
+
| `metadata-unsupported` | 400 | *OP* takes only the env option; '*key*' is not one. |
|
|
5810
|
+
| `worker-name-conflict` | 409 | Worker '*name*' already exists in this workspace. Recovery: To give '*name*' more work, write `SEND (worker://_name_)` with the task as the body; to start another worker, choose a name no worker holds. |
|
|
5811
|
+
| `no-operation` | 422 | The turn performed no operation ({§empty-turn}). |
|
|
5812
|
+
| `send-target-not-a-recipient` | 400 | The addressed scheme is not a SEND recipient. Recovery: A targetless SEND answers the open messages; a directed SEND requires a recipient that implements SEND. |
|
|
5813
|
+
|
|
5814
|
+
§problems-content **Content and transfer Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
|
|
5815
|
+
|
|
5816
|
+
| code | status | contract |
|
|
5817
|
+
|---|---:|---|
|
|
5818
|
+
| `handler-crashed` | 415 | The *mimetype* content handler failed on *key*: *cause*. |
|
|
5819
|
+
| `line-anchor-unsupported` | 400 | The byte view of *target* publishes no anchors. Recovery: Use byte coordinates: `<first,last>`. |
|
|
5820
|
+
| `line-anchor-invalid` | 400 | A line anchor in the marker is malformed or names no current line ({§line-anchors}). |
|
|
5821
|
+
| `channel-not-found` | 404 | The addressed channel does not exist at *target*. Recovery: Use one of the available channels: #*a*, #*b*. |
|
|
5822
|
+
| `binary-read-unsupported` | 415 | The representation at *target* is binary and cannot be rendered. |
|
|
5823
|
+
| `pattern-unapplicable` | 422 | The pattern could not be applied to *target*. |
|
|
5824
|
+
| `move-region-overlap` | 409 | MOVE cannot insert a whole channel into itself and then remove that channel. |
|
|
5825
|
+
| `mimetype-mismatch` | 415 | COPY or MOVE cannot write '*source-mimetype*' into a '*destination-mimetype*' channel. |
|
|
5826
|
+
| `binary-region-unsupported` | 415 | Channel #*name* is binary and cannot receive a textual region. |
|
|
5827
|
+
| `copy-destination-exists` | 409 | COPY or MOVE destination *address* already contains different content. |
|
|
5828
|
+
| `proposal-apply-missing` | 500 | The source scheme accepted its MOVE proposal without applying the source mutation. |
|
|
5829
|
+
| `line-anchor-collision` | 409 | READ coordinates collided with current content at *target*. |
|
|
5830
|
+
|
|
5831
|
+
§problems-file **File scheme Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
|
|
5832
|
+
|
|
5833
|
+
| code | status | contract |
|
|
5834
|
+
|---|---:|---|
|
|
5835
|
+
| `edit-empty` | 400 | EDIT requires at least one statement. Recovery: Provide an EDIT statement. |
|
|
5836
|
+
| `edit-batch-mismatch` | 400 | The EDIT batch spans multiple resources. Recovery: Submit a separate EDIT batch for each resource. |
|
|
5837
|
+
| `line-marker-required` | 400 | EDIT of an existing file requires a line marker ({§edit-marker-required-on-existing}). Recovery: Name the lines to replace with `<@hash>` or `<@start,@end>` from a READ of the file; `<L,1,L,1>` inserts before line L, and `<1,-1>` replaces the whole file. |
|
|
5838
|
+
| `creation-batch-conflict` | 409 | Multiple EDIT operations attempted to create the same file. Recovery: Create the file with one EDIT before applying additional edits. |
|
|
5839
|
+
| `member-read-only` | 403 | The mounted member '*path*' is read-only. |
|
|
5840
|
+
| `project-root-required` | 400 | The workspace has no project root, so it cannot write files. |
|
|
5841
|
+
| `path-names-no-file` | 403 | The spelling '*path*' does not name a file: it is empty, or it names a directory. |
|
|
5842
|
+
| `path-occupied-by-nonmember` | 403 | A non-member file already occupies '*path*'. Recovery: Choose an unoccupied member path. |
|
|
5843
|
+
| `path-outside-workspace` | 403 | A symlink on '*path*' resolves outside the namespace. |
|
|
5844
|
+
| `binary-write-unsupported` | 415 | A text EDIT cannot author binary '*mimetype*'; COPY or MOVE the bytes instead. |
|
|
5845
|
+
| `file-create-excluded` | 403 | A members exclusion (`!_glob_`) covers '*path*'. Recovery: Remove or disable the excluding members definition, or choose another path. |
|
|
5846
|
+
| `file-create-gitignored` | 403 | Active Git policy ignores '*path*', and no members definition includes it. Recovery: Choose a Git-admitted path or add a members definition that includes it. |
|
|
5847
|
+
| `file-materialization-limit` | 413 | The file exceeds the materialization byte limit and is not read into the workspace. |
|
|
5848
|
+
| `entry-not-found` | 404 | No member of this workspace is at '*path*'. Recovery: Check the path with FIND. EDIT creates files; `members (add)` admits existing files with a `{"glob": "<path>"}` body. |
|
|
5849
|
+
|
|
5850
|
+
§problems-exec **Execution Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
|
|
5851
|
+
|
|
5852
|
+
| code | status | contract |
|
|
5853
|
+
|---|---:|---|
|
|
5854
|
+
| `invalid-input-target` | 400 | SEND input addresses an execution, without a channel or scope. |
|
|
5855
|
+
| `input-unavailable` | 409 | This execution's input receiver is no longer enabled. |
|
|
5856
|
+
| `stream-not-found` | 404 | No execution exists at the requested address. |
|
|
5857
|
+
| `input-closed` | 410 | Execution input is closed. |
|
|
5858
|
+
|
|
5859
|
+
§problems-entries **Entry scheme Problems (log, worker, entries).** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
|
|
5860
|
+
|
|
5861
|
+
| code | status | contract |
|
|
5862
|
+
|---|---:|---|
|
|
5863
|
+
| `read-target-required` | 400 | READ requires a log coordinate. Recovery: Provide one exact log coordinate. |
|
|
5864
|
+
| `coordinate-malformed` | 400 | The log coordinate '*path*' is malformed. Recovery: Use one exact loop/turn/sequence coordinate. |
|
|
5865
|
+
| `worker-target-required` | 400 | EDIT requires a worker:// target. Recovery: Provide the worker target. |
|
|
5866
|
+
| `binary-edit-unsupported` | 415 | The #*channel* channel is binary and cannot be edited. |
|
|
5867
|
+
| `message-not-implemented` | 501 | SEND does not deliver messages to *scheme* entries. Recovery: To reply, SEND to an Open Message address or omit the target. `SEND (worker://<name>)` sends a new message. |
|
|
5868
|
+
| `worker-entity-not-editable` | 400 | A worker entity is not an editable entry. Recovery: EDIT requires an entry path, such as worker:///example.md. |
|
|
5869
|
+
| `message-empty` | 400 | SEND has no message text or attachments. |
|
|
5870
|
+
| `scope-unsupported` | 400 | A worker SEND takes no scope. |
|
|
5871
|
+
|
|
5872
|
+
§problems-functionality **Server and Functionality Problems.** Every code this family mints, its status, and the sentence that is its contract (placeholders in *italics* are filled at emission; a fixed recovery follows its detail).
|
|
5873
|
+
|
|
5874
|
+
| code | status | contract |
|
|
5875
|
+
|---|---:|---|
|
|
5876
|
+
| `service-starting` | 503 | The PLURNK service owns this listener but has not admitted its client interface yet. |
|
|
5877
|
+
| `configuration-unsupported` | 400 | Environment discovery reads this installation's declared configuration; client configuration contributes nothing. |
|
|
5878
|
+
| `name-reserved` | 400 | '*alias*' is plurnk's own: PLURNK_* configuration and provider credential names never reach a subprocess. |
|
|
5879
|
+
| `value-invalid` | 400 | '*alias*' needs a string value. |
|
|
5880
|
+
| `env-invalid` | 400 | `env` must be an object of string values; '*name*' is not a name a shell can export. |
|
|
5881
|
+
| `query-required` | 400 | discover takes a path or a glob. Recovery: Supply `{ "query": "<path or glob>" }`. |
|
|
5882
|
+
| `headless` | 409 | The workspace has no project root, so there are no file members. Recovery: Open the workspace on a project root. |
|
|
5883
|
+
| `definition-invalid` | 400 | A members definition is { glob }: a gitignore-style pattern, `!glob` to exclude; a skill definition names an installable skill. |
|
|
5884
|
+
| `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*. |
|
|
5889
|
+
| `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*. |
|
|
5893
|
+
| `workspace-not-found` | 404 | Workspace *id* does not exist. |
|
|
5894
|
+
| `state-not-json` | 400 | Worker module state is not JSON-serializable. |
|
|
5895
|
+
| `workspace-busy` | 409 | Workspace *id* is running an operation or another capability change. Recovery: Settle the current operation and retry the capability change. |
|
|
5896
|
+
| `not-configured` | 503 | No provider is configured for this worker. |
|
|
5897
|
+
| `model-worker-required` | 404 | No model worker exists for prompt injection (or to fork). |
|
|
5898
|
+
| `name-conflict` | 409 | The worker name is taken. Recovery: Choose another worker name. |
|
|
5899
|
+
| `offset-channel-required` | 400 | Recovery: Select the channel to read from the offset. |
|
|
5900
|
+
| `target-invalid` | 400 | Recovery: Use a scheme://path target. |
|
|
5901
|
+
| `proposal-not-pending` | 409 | Recovery: Refresh pending proposals before resolving one. |
|
|
5902
|
+
| `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
|
+
| `scope-cancelled` | 499 | The worker scope was cancelled: *reason*. |
|
|
5904
|
+
| `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. |
|
|
5908
|
+
| `skill-invalid` | 422 | Agent Skill '*alias*' is not a valid standard skill: *cause*. |
|
|
5909
|
+
|
|
5910
|
+
§pinned-wording-core **Pinned wording.** Verbatim sentences tests pin: each is contract, and a change here is a change of contract.
|
|
5911
|
+
|
|
5912
|
+
| sentence | arises when |
|
|
5913
|
+
|---|---|
|
|
5914
|
+
| Nothing is in flight. Continuing. | a WAIT (or a premature terminal) with no live work ({§wait-obligation-matrix}) |
|
|
5915
|
+
| Completion deferred. Conclude with KILL alone. | a concluding KILL that carries other operations ({§kill-conclusion}) |
|
|
5916
|
+
| Context Token Budget Overflow: logTokensTotal exceeds logTokensMax; retained context cannot be admitted. | output admission over the retained-context ceiling |
|
|
5917
|
+
| This run is unattended: nobody is present to answer. | a capability that needs a present operator in an unattended loop ({§loop-attendance}) |
|
|
5918
|
+
| Worker name '*name*' must match `[A-Za-z0-9][A-Za-z0-9_-]{0,62}`. Recovery: Use 1–63 ASCII letters, digits, '_' or '-', starting with a letter or digit. | an invalid worker name |
|
|
5919
|
+
| Provide the client identifier. / Provide an absolute project path. / Use a positive integer limit. / prompt is not a non-empty string. | client input validation on the daemon's methods |
|
|
5920
|
+
| The stream was cancelled by KILL. | a stream terminal after KILL |
|
|
5921
|
+
| '*program*' exited with code *n*. | an execution's non-zero exit |
|
|
5922
|
+
| '*path*' is a directory, not a file; READ reads one file. Recovery: List its files with `FIND (_path_/)`, then READ one by its path. | READ of a directory |
|
|
5923
|
+
| The execution at ops://*worker*/*loop* has not concluded. | a bare READ of a running worker's result (425) |
|
|
5924
|
+
| The child provider failed. | a child's provider failure read back by its parent |
|
|
5925
|
+
| '*path*' exists on disk but is not a member of this workspace. Recovery: Admit it with `members (add)` and a `{"glob": "<path>"}` body. | a non-member on disk at the addressed path |
|