@plurnk/plurnk-service 1.0.6 → 1.0.8
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 +21 -21
- package/SPEC.md +22 -16
- package/dist/core/Dispatcher.d.ts +9 -1
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +3 -2
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +10 -6
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/PacketBuilder.d.ts +1 -1
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +46 -66
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/PluginLoader.d.ts +2 -0
- package/dist/core/PluginLoader.d.ts.map +1 -1
- package/dist/core/PluginLoader.js +27 -1
- package/dist/core/PluginLoader.js.map +1 -1
- package/dist/core/SchemeRegistry.js +1 -1
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/packet-wire.js +6 -6
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/zero-pin.d.ts +3 -0
- package/dist/core/zero-pin.d.ts.map +1 -0
- package/dist/core/zero-pin.js +16 -0
- package/dist/core/zero-pin.js.map +1 -0
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +4 -0
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/digest.sql +1 -1
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +8 -5
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +60 -9
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/exec-poll-backoff.d.ts +2 -0
- package/dist/server/exec-poll-backoff.d.ts.map +1 -0
- package/dist/server/exec-poll-backoff.js +6 -0
- package/dist/server/exec-poll-backoff.js.map +1 -0
- package/dist/service.js +2 -2
- package/dist/service.js.map +1 -1
- package/package.json +17 -15
package/.env.defaults
CHANGED
|
@@ -55,6 +55,8 @@ PLURNK_SERVICE_MAX_COMMANDS=-1
|
|
|
55
55
|
PLURNK_SERVICE_PROPOSAL_TIMEOUT_MS=
|
|
56
56
|
# Turn-hold runtimes: while a listed runtime's stream is in flight the cycle pauses (no next
|
|
57
57
|
# turn) until it concludes. Bounded by _HOLD_MS, fail-open at the cap. Empty = no holds.
|
|
58
|
+
# A bare name holds ALL of that runtime's spawns; `<runtime>:<effect>` (e.g. github:read) holds
|
|
59
|
+
# only that effect-class — an MCP server's read tools opt in while its host mutations background (#485).
|
|
58
60
|
PLURNK_SERVICE_EXEC_HOLD=search,images,videos,news,map,music,it,science,social,downloadable
|
|
59
61
|
PLURNK_SERVICE_EXEC_HOLD_MS=300000
|
|
60
62
|
# Post-EXEC breath: after a turn fires a non-inline EXEC whose spawn is still in
|
|
@@ -63,6 +65,12 @@ PLURNK_SERVICE_EXEC_HOLD_MS=300000
|
|
|
63
65
|
# beat, NOT a wait-for-completion (slow execs proceed + surface via the wake path).
|
|
64
66
|
# 0 = off (the model sees fast-exec output a turn late, as today).
|
|
65
67
|
PLURNK_SERVICE_EXEC_WAIT_MS=1000
|
|
68
|
+
# Exec poll backoff (#521, §exec-poll): a parked exec with NO explicit `<,P>` cadence is no longer
|
|
69
|
+
# park-blind-forever — the loop wakes on an exponential backoff so the model regains a turn to read
|
|
70
|
+
# partial output and re-park a slow long-runner or KILL a stuck one. Wake N fires at BASE·2^min(N,TURNS-1)
|
|
71
|
+
# seconds (60,120,240,…,7680), then holds at the cap. An explicit `<,P>` wins; `<,0>` opts out (blind).
|
|
72
|
+
PLURNK_SERVICE_EXEC_POLL_SEC=60
|
|
73
|
+
PLURNK_SERVICE_EXEC_POLL_TURNS=8
|
|
66
74
|
# Teardown reap grace: when a loop/run tears down a background exec, the spawn gets a polite
|
|
67
75
|
# signal first (SIGHUP, or the model's KILL[code]); a stream that IGNORES it is hard-killed
|
|
68
76
|
# (SIGKILL, to the whole process group) this many ms later — so the reap can't wedge on a
|
|
@@ -144,15 +152,13 @@ PLURNK_SERVICE_WORKSPACE_WORKERS_MAX_ACTIVE=-1
|
|
|
144
152
|
# envelope (max_tokens on every call — no decode is unbounded); SAFETY covers chat-template
|
|
145
153
|
# overhead. The prompt ceiling is DERIVED, never set directly.
|
|
146
154
|
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
#
|
|
153
|
-
|
|
154
|
-
PLURNK_SERVICE_REASONING=16384
|
|
155
|
-
PLURNK_SERVICE_COMPLETION=49152
|
|
155
|
+
# #507 (owner-ruled): the generation envelope is PROVIDER-owned — window + reserves live in the
|
|
156
|
+
# provider tier (`PLURNK_PROVIDERS_CONTEXT_WINDOW` / `PLURNK_PROVIDERS_REASONING_RESERVE` /
|
|
157
|
+
# `PLURNK_PROVIDERS_COMPLETION_RESERVE`, ingested from the backend or pinned there; percent or
|
|
158
|
+
# absolute forms — see plurnk-providers/.env.defaults). Core keeps ONE knob: SAFETY, the ruler's
|
|
159
|
+
# own packing margin (a service fact, not a model fact). Resolves per alias: _<alias> wins over bare.
|
|
160
|
+
# The retired PLURNK_SERVICE_{CONTEXT_WINDOW,REASONING,COMPLETION} names FAIL HARD at boot naming
|
|
161
|
+
# their successors — a stale .env never silently loses its envelope.
|
|
156
162
|
PLURNK_SERVICE_SAFETY=1024
|
|
157
163
|
#
|
|
158
164
|
# PLURNK_SERVICE_BUDGET_MERMAID — the Budget section as visual mermaid (#440, §budget-mermaid). Default
|
|
@@ -162,19 +168,13 @@ PLURNK_SERVICE_SAFETY=1024
|
|
|
162
168
|
# A/B against the tabular baseline (#440's before/after measurement).
|
|
163
169
|
PLURNK_SERVICE_BUDGET_MERMAID=on
|
|
164
170
|
#
|
|
165
|
-
# LOCAL aliases (llama-server)
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
# their alias and their model's real n_ctx (example alias name 'myalias'; a local model
|
|
169
|
-
# run under the bare cloud-generous defaults fails HARD at build with a remedy naming these knobs):
|
|
170
|
-
# PLURNK_SERVICE_CONTEXT_WINDOW_myalias=78848
|
|
171
|
-
# PLURNK_SERVICE_REASONING_myalias=4096
|
|
172
|
-
# PLURNK_SERVICE_COMPLETION_myalias=8192
|
|
173
|
-
# PLURNK_SERVICE_SAFETY_myalias=1024
|
|
171
|
+
# LOCAL aliases (llama-server): the window is PROBED and the reserves DERIVE from it in the
|
|
172
|
+
# provider tier (percent defaults) — zero-config. Absolute pins, when wanted, are provider-tier:
|
|
173
|
+
# PLURNK_PROVIDERS_{REASONING,COMPLETION}_RESERVE_<alias> (see plurnk-providers/.env.defaults).
|
|
174
174
|
# COUPLING (F7): per-request numeric reasoning budgets are IGNORED by llama-server — a local
|
|
175
|
-
# thinking model's serving box MUST clamp via --reasoning-budget =
|
|
176
|
-
# or the reserve is fiction. The service warns at boot.
|
|
177
|
-
#
|
|
175
|
+
# thinking model's serving box MUST clamp via --reasoning-budget = the alias's resolved reasoning
|
|
176
|
+
# reserve, or the reserve is fiction. The service warns at boot. Pinned absolutes exceeding the
|
|
177
|
+
# detected window fail HARD at build time, naming the provider-tier knobs.
|
|
178
178
|
|
|
179
179
|
# PLURNK_SERVICE_VERSION_POLL_TTL — how long (ms) discover caches its npm-registry version poll
|
|
180
180
|
# (service + client `latest`) before a background refresh. Best-effort; the poll never
|
package/SPEC.md
CHANGED
|
@@ -222,7 +222,7 @@ Three entry points:
|
|
|
222
222
|
- `provider.countTokens(text)` — synchronous, called at write-time (§tokenomics) and render-time. Non-negative integer. {§provider-surface-counttokens}
|
|
223
223
|
- `provider.costFor(usage)` — once per completed turn; pico-USD. Engine writes to `turns.usage_cost_pico`; triggers cascade to `workers.cost_pico` / `workspaces.cost_pico`. {§provider-surface-costfor}
|
|
224
224
|
|
|
225
|
-
Plus immutable identity: `provider.
|
|
225
|
+
Plus immutable identity: `provider.contextWindow` (token total, or `null` → "no budget info"), read by the budget {§provider-surface-identity}; and `provider.model` — the instance identity the deferred model-switch recompute compares (§tokenomics), exposed but not yet consumed here.
|
|
226
226
|
|
|
227
227
|
**Metadata passthrough (provider → client).** `generate` may return an open `meta: Record<string, unknown>` bag — e.g. a hosted provider's running `balancePico`. The service stores it **unenforced** per turn (`turns.meta`, `json_valid` only — no schema) and forwards the latest turn's blob to the client on the loop usage payload (`loop.run` result / `loop/terminated`, §methods). **The service never reads a field within it.** The canonical-field contract — which fields exist and their shapes — is the *provider framework's* (it normalizes raw vendor data into the agreed set) and the *client's* (it renders that set); a provider and client can ship a feature with **zero service change** as long as the blob flows. Absent → `{}` (the client renders nothing; never fabricated). The mirror direction (client → provider, the self-identified `client` id) rides `generate({client})` (§attribution). {§meta-passthrough}
|
|
228
228
|
|
|
@@ -268,7 +268,7 @@ First path segment = provider plugin; rest = provider's own model id.
|
|
|
268
268
|
|
|
269
269
|
### §mock-provider Mock provider (sibling fixture)
|
|
270
270
|
|
|
271
|
-
`Mock` (exported from `@plurnk/plurnk-providers`) — intg fixture + reference implementation. `{
|
|
271
|
+
`Mock` (exported from `@plurnk/plurnk-providers`) — intg fixture + reference implementation. `{ contextWindow, responses }` constructor; `generate` shifts from the queue. `MockResponse.assistant.ops?: PlurnkStatement[]` is a pre-parsed escape hatch the engine consumes directly when present; production providers don't expose this — and being a daughter export, this contract has no service-side `§`-ref. {§mock-provider-mock-fixture}
|
|
272
272
|
|
|
273
273
|
---
|
|
274
274
|
|
|
@@ -379,7 +379,7 @@ Engine → scheme guarantees:
|
|
|
379
379
|
|
|
380
380
|
Author-facing contract: [plurnk-mimetypes](https://github.com/plurnk/plurnk-mimetypes). Below: firing semantics + consumption surface.
|
|
381
381
|
|
|
382
|
-
**Firing semantics.** Render-time consumers. Engine invokes during packet assembly; handlers read current channel content (possibly mid-stream), produce structural view, result lands in the manifest catalog. Schemes do NOT call mimetype handlers at write — writes are verbatim. Two content lifecycles sit outside that rule and are not exceptions to it: (1) **write-time indexing** — the FTS keyword half derives at write (handler-free, plain string→FTS) so a cold workspace's first query narrows; the vector half and every structural VIEW (symbols/@graph, tokens, lines, extent) derive at RENDER, re-computed each packet so a model/tokenizer change can never leave a stored snapshot lying. (2) **fetch-time materialization** — the web-fetch tool (the exec sink) projects a fetched `text/html` page through the handler's readable projection ONCE and stores THAT as the decisive `body` (`text/markdown` — what READ serves, FIND matches, and every
|
|
382
|
+
**Firing semantics.** Render-time consumers. Engine invokes during packet assembly; handlers read current channel content (possibly mid-stream), produce structural view, result lands in the manifest catalog. Schemes do NOT call mimetype handlers at write — writes are verbatim. Two content lifecycles sit outside that rule and are not exceptions to it: (1) **write-time indexing** — the FTS keyword half derives at write (handler-free, plain string→FTS) so a cold workspace's first query narrows; the vector half and every structural VIEW (symbols/@graph, tokens, lines, extent) derive at RENDER, re-computed each packet so a model/tokenizer change can never leave a stored snapshot lying. (2) **fetch-time materialization** — the web-fetch tool (the exec sink) projects a fetched `text/html` page through the handler's readable projection ONCE and stores THAT as the decisive `body` (`text/markdown` — what READ serves, FIND matches, and every weight reports), archiving the raw page under `html` (xpath + fidelity). This sets the body's identity, not a re-derived view; an AUTHORED/workspace html file is written verbatim (its attributes are data). {§mimetype-schemes-do-not-invoke-handlers}
|
|
383
383
|
|
|
384
384
|
### §mimetype-manifest Manifest
|
|
385
385
|
|
|
@@ -555,7 +555,7 @@ OPEN/FOLD operate on the **log** (`log:///`) — the model's context-curation su
|
|
|
555
555
|
|
|
556
556
|
### §jsonplurnk The Log's wire format
|
|
557
557
|
|
|
558
|
-
The `## Log` section renders as a fenced `jsonplurnk` block — a JSON array of entry objects, otherwise-valid JSON with **exactly one** deviation: each `body` value, when present, is a raw HEREDOC (`<<:::TAG … :::TAG`, TAG = the entry's target/log URI), rendered verbatim (numbered for text, tree-navigable verbatim), never a JSON-escaped string. The carve-out is localized to `body`, so the strip-parser is trivial — after `"body":`, `<<:::TAG` opens and `:::TAG` at column 0 closes; replacing that block with an escaped string recovers strict JSON (the plurnkdown linter's transform). Each entry's fold state is a self-documenting `display` field (grammar-ratified #437 — `display:none` mirrors CSS, `folded`/`open` echo the FOLD/OPEN ops): `"display":"none"` (no body), `"folded"` (a body exists but is hidden, still weighable), `"open"` (body shown, heredoc present). The block's leading Note defines the carve-out AND the token semantics in-packet (#466 — a number the model can see states its meaning where it appears, never in a doc it can't): `tokens`
|
|
558
|
+
The `## Log` section renders as a fenced `jsonplurnk` block — a JSON array of entry objects, otherwise-valid JSON with **exactly one** deviation: each `body` value, when present, is a raw HEREDOC (`<<:::TAG … :::TAG`, TAG = the entry's target/log URI), rendered verbatim (numbered for text, tree-navigable verbatim), never a JSON-escaped string. The carve-out is localized to `body`, so the strip-parser is trivial — after `"body":`, `<<:::TAG` opens and `:::TAG` at column 0 closes; replacing that block with an escaped string recovers strict JSON (the plurnkdown linter's transform). Each entry's fold state is a self-documenting `display` field (grammar-ratified #437 — `display:none` mirrors CSS, `folded`/`open` echo the FOLD/OPEN ops): `"display":"none"` (no body), `"folded"` (a body exists but is hidden, still weighable), `"open"` (body shown, heredoc present). The block's leading Note defines the carve-out AND the token semantics in-packet (#466 — a number the model can see states its meaning where it appears, never in a doc it can't): `tokens` is the ruler-weight of the row's body in this packet — the room it takes (what OPEN adds, what FOLD saves); a FIND's `itemsTokenTotal` is the ruler-weight of the matched entries themselves (the room READing them takes) — curation weights, not dollars. The invariants bind regardless of shape (§packet): addressability (`path`/`target`/`#channel`/numbered bodies), weighability (per-item `tokens`), honesty (every 4xx/5xx row, bodyless-0-token items, the explicit fold state). This is the Log's realization of the packet-wide plurnkdown house style — the whole outbound packet is one coherent document, log data stays JSON housed in a fence. {§jsonplurnk}
|
|
559
559
|
|
|
560
560
|
The opening fence length is **dynamic**: one backtick longer than the longest backtick run in any body (floor 3). A body can carry arbitrary content — a READ of a doc whose own text opens a column-0 triple-backtick fence — which a fixed opener would let close the block early; a dynamic opener can never be closed by its own body content (CommonMark closes a fence only on a line of at least its own length), independent of the `N:\t` numbering that incidentally keeps text bodies off column 0. {§jsonplurnk-dynamic-fence}
|
|
561
561
|
|
|
@@ -639,7 +639,7 @@ Engine routes unconditionally to `exec` scheme (the `(target)` slot is a local p
|
|
|
639
639
|
|
|
640
640
|
**Runtime fall-through** {§exec-runtime-fallthrough} — an UNREGISTERED runtime tag falls through to the shell with the tag as the command word: `EXEC[go]:test ./...` runs as sh's `go test ./...` (#350, the execs architect's dispatch spec automating the owner's execs#21 ruling: per-tool runtimes — go, cargo, make, npm — never earn tags; sh is the general answer). The fall-through rides sh's OWN policy gate (a workspace that disabled sh gets the refusal, never a side door), its output entry lands under `sh://` (it ran on sh, no phantom scheme), and telemetry records `exec_runtime_fallthrough {requested}` — which tags models reach FOR is the data that would ever justify a real runtime, never guesswork. A typo'd tag becomes the shell's own clear 127. No new surface: anything expressible as `EXEC[foo]:bar` was expressible as `EXEC[sh]:foo bar`. The 501 remains for a fall-through-ineligible dispatch (sh itself unregistered or workspace-disabled).
|
|
641
641
|
|
|
642
|
-
**Timeout and poll — `<T,P>` on the `<L>` slot (grammar 0.74.20).** EXEC repurposes the line-marker slot as `<timeout, poll>` in **seconds** (consistent with the `seconds=` stream-age render). `T` (mark[0]) caps the spawn's lifetime: at `T>0` the service aborts it (a bounded reap — polite signal then SIGKILL after `PLURNK_SERVICE_EXEC_KILL_GRACE_MS`) and stamps the stream **504**, distinct from a deliberate kill (499) or a clean exit (200). `-1` / absent → unbounded (loop-life bounded), the background-stream behavior. **`0` → turn-scoped**: the stream is reaped at the worker's *next pre-turn* (via the registry abort, before the turn's own spawns), so it never survives into the subsequent turn; its terminal output surfaces born-OPEN like any close (§exec-stream). {§exec-timeout} `P` (mark[1]) is the **poll cadence**, stored on the subscription: while the loop is *blocked on a `SEND[202]` wait* for that stream (the poll rides the subscription), the daemon arms a per-worker timer for the tightest open poll cadence and resumes the blocked loop every P seconds (floored by `PLURNK_SERVICE_EXEC_WAIT_MS` so it can't tick faster than a turn settles) to inspect progress (the same 202→100 resume a stream conclusion uses, §worker-lifecycle). It does **nothing while the loop is active** — an active loop already gets the ambient folded stream deltas (§exec-stream), so the poll-wake matters only across a wait.
|
|
642
|
+
**Timeout and poll — `<T,P>` on the `<L>` slot (grammar 0.74.20).** EXEC repurposes the line-marker slot as `<timeout, poll>` in **seconds** (consistent with the `seconds=` stream-age render). `T` (mark[0]) caps the spawn's lifetime: at `T>0` the service aborts it (a bounded reap — polite signal then SIGKILL after `PLURNK_SERVICE_EXEC_KILL_GRACE_MS`) and stamps the stream **504**, distinct from a deliberate kill (499) or a clean exit (200). `-1` / absent → unbounded (loop-life bounded), the background-stream behavior. **`0` → turn-scoped**: the stream is reaped at the worker's *next pre-turn* (via the registry abort, before the turn's own spawns), so it never survives into the subsequent turn; its terminal output surfaces born-OPEN like any close (§exec-stream). {§exec-timeout} `P` (mark[1]) is the **poll cadence**, stored on the subscription: while the loop is *blocked on a `SEND[202]` wait* for that stream (the poll rides the subscription), the daemon arms a per-worker timer for the tightest open poll cadence and resumes the blocked loop every P seconds (floored by `PLURNK_SERVICE_EXEC_WAIT_MS` so it can't tick faster than a turn settles) to inspect progress (the same 202→100 resume a stream conclusion uses, §worker-lifecycle). It does **nothing while the loop is active** — an active loop already gets the ambient folded stream deltas (§exec-stream), so the poll-wake matters only across a wait. An absent `P` on ANY unbounded park no longer sleeps blind: the daemon wakes the loop on an **exponential backoff** (`PLURNK_SERVICE_EXEC_POLL_SEC` base, doubling for `PLURNK_SERVICE_EXEC_POLL_TURNS` steps — 60,120,…,7680s — then holding at the cap), so **no park is ever blind** (#521, owner-ruled). This is UNIVERSAL — a hung exec AND a child-join park (blocking collect) wake on the same ladder — so a lost wake edge (a dropped stream conclusion or child terminal) self-heals within one step: the model regains a turn to read partial output and re-park a slow long-runner or **KILL** a stuck one (no auto-kill — only the model tells a silent deadlock from a silent `cargo build`). The primary wake edge still resumes it immediately when it fires; the backoff is the safety net beneath it. Non-striking (a correctly-waiting parent never strikes out). Explicit `<,P>` (P>0) wins with its fixed cadence; `<,0>` opts out (blind, an exec a model wants unwatched). {§exec-poll}
|
|
643
643
|
|
|
644
644
|
**Effect-gating.** Each executor declares an `effect` (`pure` | `read` | `host`); the service maps it to policy (`EffectPolicy`). A `host` runtime (subprocess; file-backed sqlite) mutates the host → **propose** (lifecycle §proposal): the worker waits for a human gate, then spawns and writes stdout/stderr to channels of a `<runtime>:///<loop>/<turn>/<seq>` entry (the runtime tag is the URI scheme, §exec/#240; the coordinate matches the op's log-row coordinate, e.g. `sh:///1/1/2`), returning `102 Processing` immediately. Channel state transitions (`active` → `closed`/`errored`) drive what the model sees at subsequent turn boundaries (§channel-state). {§exec-host-proposes}
|
|
645
645
|
|
|
@@ -650,7 +650,7 @@ A `read` runtime (observes external state, e.g. search) or `pure` runtime (no ob
|
|
|
650
650
|
`SEND[499](exec:///<loop>/<turn>/<seq>)` cancels the in-flight subprocess via the subscription registry's stored `AbortController` — the coordinate addresses the spawn (`exec://` is the process-control face); the `<runtime>://` output entry delegates the same KILL to the one `Exec` handler that owns the abort state (§stream-control).
|
|
651
651
|
|
|
652
652
|
**Scoped environment.** An EXEC subprocess inherits the *project's* environment — its `.env`, the standard shell vars — so the model's commands run as the project expects; but never plurnk's own secrets: the provider API keys and `PLURNK_*` config are stripped before the spawn, so a model-run command can't `printenv` the engine's keys. The service owns the scoping policy (the denylist); the executor spawns with the env it is handed. {§exec-env-scoped}
|
|
653
|
-
- **The turn-hold exception** {§exec-hold-until-concluded} — for runtimes in `PLURNK_SERVICE_EXEC_HOLD` (a decision-table env, shipped listing the search family), an in-flight stream **pauses the cycle**: the next packet does not assemble until the stream concludes, so the model never burns a turn asking "are we there yet" about a result the engine controls end-to-end (one final JSON digest, seconds-bounded — the owner's ruling: this is the one special case where the stream is known well enough not to fall back on the standard cycle). Bounded by `PLURNK_SERVICE_EXEC_HOLD_MS` and **fail-open**: at the cap the standard cycle resumes untouched (waits, wakes, polls). Zero grammar or teaching surface — the model emits `EXEC + SEND[102]` as ever; the wake-shaped world simply arrives one packet sooner. Lives at the post-EXEC breath seam in `runLoop`, upstream of `PLURNK_SERVICE_EXEC_WAIT_MS`.
|
|
653
|
+
- **The turn-hold exception** {§exec-hold-until-concluded} — for runtimes in `PLURNK_SERVICE_EXEC_HOLD` (a decision-table env, shipped listing the search family), an in-flight stream **pauses the cycle**: the next packet does not assemble until the stream concludes, so the model never burns a turn asking "are we there yet" about a result the engine controls end-to-end (one final JSON digest, seconds-bounded — the owner's ruling: this is the one special case where the stream is known well enough not to fall back on the standard cycle). Bounded by `PLURNK_SERVICE_EXEC_HOLD_MS` and **fail-open**: at the cap the standard cycle resumes untouched (waits, wakes, polls). Zero grammar or teaching surface — the model emits `EXEC + SEND[102]` as ever; the wake-shaped world simply arrives one packet sooner. Lives at the post-EXEC breath seam in `runLoop`, upstream of `PLURNK_SERVICE_EXEC_WAIT_MS`. 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 (#485). Conservative stays default: an arbitrary third-party server's latency never parks the engine unless a suffix opts a class in.
|
|
654
654
|
- **The entry() sink** {§exec-entry-sink} — an executor may *request* entry materialization (execs SPEC §2.6: every sink is a consumer-implemented callback; the executor owns zero substrate). The service implements it in exec dispatch: `entry(path, content: string | null, {tags, mimetype?})` upserts the entry (writeEntry; tags **UNIONED** across writes — a re-seen URL keeps its history of query slugs), then narrates ONE `EDIT` row in the reserved `plurnk` worker's log — the fs-fiction pattern, `source` = the calling worker, `tokens` = the content's count, `attrs` carrying the tags — which the env-delta ambience (§env-delta) folds into every worker's next packet as a one-liner. **The executor owns no fetcher** (Web Search ruling #5): a `content: null` is a *declaration* — the service fetches the page once through schemes-http's guarded primitive (`WebFetcher`: SSRF-guarded fetch+render, dead-as-`null`) and materializes it, and a dead fetch (guard-refused / unreachable / non-2xx / non-textual / empty) **rejects the sink** so the executor prunes that row. A non-null `content` is the materialize-given-body path (the caller already holds the bytes and states their mimetype). **No page body ever rides a packet**; the announcement is the folded row's meta (path + tokens + tags), and the model READs/~queries what it chooses. Parallel `entry()` calls serialize on a per-spawn chain; a rejected call prunes that item without breaking the chain. The narration context (one plurnk-worker turn) is lazy per spawn, not per entry. Born of the Web Search Epic's one-load flow (#340/#455): the executor declares each candidate, the service fetches survivors once, tags them with the query slug, and the digest lists survivors only.
|
|
655
655
|
|
|
656
656
|
### §proposal The proposal lifecycle
|
|
@@ -760,6 +760,8 @@ When SQL becomes onerous for a specific case, retreat for that case and document
|
|
|
760
760
|
|
|
761
761
|
## §plugin-discovery Plugin Discovery
|
|
762
762
|
|
|
763
|
+
**The stepchild covenant is verified at the boundary.** {§plugin-built-against} A stepchild manifest declares `plurnk.builtAgainst` — the exact family-head version the artifact was built against, stamped at publish. The loader verifies it BEFORE import: skew refuses legibly naming both versions and the cure ("<pkg> built against 1.0.5; loaded 1.0.7 — republish pending."), never a mid-import SyntaxError on a removed export (#512's detonation class). An absent field is a legacy artifact: one warning, load proceeds — the field becomes required once the stepchild phase has stamped the fleet (#514).
|
|
764
|
+
|
|
763
765
|
Scoped-package scan with manifest field:
|
|
764
766
|
|
|
765
767
|
1. Each package declares its kind:
|
|
@@ -846,7 +848,7 @@ Model selection: separate alias cascade in `ProviderRegistry` (§provider-instan
|
|
|
846
848
|
| `PLURNK_SERVICE_MD_<ALIAS>` | (unset) | Operator reference doc: materializes `<path>` as `plurnk:///<ALIAS>.md`, auto-READ into every model worker's turn 0 (§actor-boundary). `~` expands to home. |
|
|
847
849
|
| `PLURNK_SERVICE_FILES_ITEMS` | `-1` | Turn-0 catalog preview, one `FIND(scheme:///**)` per scheme. Memory/scratch/docs always full; the first-`N` cap applies **only** to the `file` list. `-1` = all full; positive `N` = file list first-N (memory still full); `0` / unset = off (§actor-boundary-catalog-preview). |
|
|
848
850
|
| `PLURNK_SERVICE_PROPOSAL_TIMEOUT_MS` | (empty — waits indefinitely) | ms wait for a proposed entry (status=202) to be resolved before timing out. |
|
|
849
|
-
| `
|
|
851
|
+
| `PLURNK_PROVIDERS_REASONING` + `_BUDGET` | `adaptive` / (unset) | The activation/budget split (a numeric budget silently flipping template flags was secret flag-setting). `off | adaptive | on`; budget (tokens) REQUIRED when on. A reasoning-trained model MUST reason — off reroutes its thought into the grammar's legal free zone as prose. One setting is right everywhere: providers clamp reasoning to none on in-band (response_format) grammar calls themselves, so the channel-topology parallax needs no per-backend override. F7 coupling: llama-server honors only the box's `--reasoning-budget` launch flag (must equal the budget; boot-warned). |
|
|
850
852
|
| `PLURNK_PROVIDERS_FETCH_TIMEOUT` | `600000` | Service-wide ms ceiling on any outbound request (providers, future http schemes). Module-specific overrides are allowed below the ceiling. |
|
|
851
853
|
|
|
852
854
|
Every knob listed is enforced — the engine reads and acts on it; `.env.defaults` is the authoritative default (reader-declares, §operator-config-env-defaults).
|
|
@@ -1132,15 +1134,15 @@ Each entry: question, answer, rationale, migration path.
|
|
|
1132
1134
|
**Built.**
|
|
1133
1135
|
|
|
1134
1136
|
- **Provider tokens, stored at write.** `provider.countTokens` is the source of truth; `entry_channels.tokens` (via `_entry-crud`) and `log_entries.tokens` (via `Dispatcher.#writeLog`) are populated at write as a write-time snapshot. A `ceil(len/DIVISOR)` fallback (the divisor tripwire) applies only when no provider tokenizer is wired. {§tokenomics-tokens-stored-at-write}
|
|
1135
|
-
- **Render-weight budget.** The budget headline — `ceiling`, `tokenUsage`, `tokensFree` — is measured from the *assembled packet* (placeholders substituted after measuring), so it reflects what the model actually receives. A `SUM` of stored content-depth would mis-
|
|
1137
|
+
- **Render-weight budget.** The budget headline — `ceiling`, `tokenUsage`, `tokensFree` — is measured from the *assembled packet* (placeholders substituted after measuring), so it reflects what the model actually receives. A `SUM` of stored content-depth would mis-weigh the rendered packet; render-weight is the accurate measure. {§tokenomics-render-weight-budget}
|
|
1136
1138
|
- **Per-turn weight.** A markdown table groups render-weight by turn — the `loop/turn` coordinate prefix — listed chronologically (oldest first). The turn is the grinder's fold unit, and the rail folds only the **newest** (§grinder); the model sees which turns are fat and can FOLD ahead of the rail. {§tokenomics-turn-totals}
|
|
1137
|
-
- **Heaviest entries.** A second table lists the five heaviest log entries by their **body
|
|
1139
|
+
- **Heaviest entries.** A second table lists the five heaviest log entries by their **body weight — the same number the row's own `tokens` shows** (what a FOLD saves; #466: the budget and the log never disagree about one row), each by its `log:///<coord>/<op>` handle — the FOLD targets behind the turn weight. A bodyless row is no FOLD target and never ranks. The handle carries the turn, so the two tables interlock. {§tokenomics-largest-entries}
|
|
1138
1140
|
- **Context-window percent.** The headline carries usage as a percent of the ceiling — `usage Y (P%)` — a fullness gauge beside the absolutes. Reads the ceiling already in hand; no extra provider call. {§tokenomics-context-percent}
|
|
1139
|
-
- **The window is a partition, never a fraction.**
|
|
1141
|
+
- **The window is a partition, never a fraction.** the window and both reserves are **provider-owned** (#507, owner-ruled): `provider.contextWindow` (probed/declared in the provider tier), `provider.reasoningReserve` and `provider.completionReserve` (ingested or `PLURNK_PROVIDERS_*`-pinned there, percent or absolute). A null window or null reserves is genuinely-unknown — nobody chose an envelope — and is treated as **NO-CAP** — the prompt is unbounded, the budget and ceiling resolve to null and the gauge omits its headline — rather than let numbers nobody chose silently stand in for unknown physics; a probe blip must degrade to no-cap, never crash the loop {§tokenomics-window-unpollable-deliberate}) splits **without remainder**: `promptBudget = provider.contextWindow − reasoningReserve − completionReserve − PLURNK_SERVICE_SAFETY` is what the service may send, `max_tokens = reasoningReserve + completionReserve` is the generation envelope passed on every `generate({maxTokens})` when the reserves are known, and `SAFETY` — core's ONE remaining partition knob, the ruler's own packing margin — covers chat-template overhead no content counter sees. The retired `PLURNK_SERVICE_{CONTEXT_WINDOW,REASONING,COMPLETION}` names fail hard at boot naming their provider-tier successors. The prompt ceiling is **derived, never set**: a settable ceiling lets policy contradict physics, and a fractional one budgets the prompt against the window while *forgetting the response lives there too* (at 0.9 × 49152, any emission past ~4.9k overflowed gemma with a perfectly honest ruler). Reserves exceeding the window (`promptBudget ≤ 0`) are a configuration contradiction and fail hard — post-migration its one cause is pinned absolute reserves over a detected window; percent reserves derive and cannot contradict. When native reasoning is on, llama-server ignores per-request numeric budgets — the serving box's `--reasoning-budget` launch flag must equal the alias's resolved reasoning reserve, and the service warns at boot when it cannot verify that coupling. The LOCAL example partition (turboderp suffix, 78848 − 4096 − 8192 − 1024) yields exactly 65536 prompt tokens; the bare cloud defaults yield a large decode envelope the backend self-clamps. {§tokenomics-window-partition} **The partition is PER ALIAS** (#352): each knob resolves `PLURNK_SERVICE_<KNOB>_<alias>` over the bare fallback (providers' `scopeEnvToAlias`), scoped by the alias that produced the turn's provider (the service's own provider→alias side-table, falling back to the active boot alias). BARE defaults are cloud-generous — the generation envelope is a large `max_tokens` the backend self-clamps to its true output limit (cloud APIs accept and silently clamp; none expose a max-output field, so generous-and-clamp is the honest posture, and a cloud reasoner burning ~16k on thought finishes its DSL instead of dying at `finish=length`). LOCAL (llama-server) aliases MUST keep a measured envelope — `n_predict` is honored to the context wall, so an unbounded decode wall-runs (providers#10, the cap doctrine's origin); a local alias whose partition contradicts its real window fails HARD at build time, naming the per-alias knobs to set.
|
|
1140
1142
|
- **Derivation is off the hot path; search is never degraded by it.** The per-turn derivation pump and the workspace warm run on a background chain (serialized, drained at daemon stop, failures logged) — **a turn never waits on an embedding** (a 2-CPU container CPU-embedding a 335-entry ingest starved every loop ~28min). Full fidelity survives the move on both fusion halves: the **keyword half indexes at the write** (plain string→FTS, no handler invoked — a cold workspace's first query narrows over everything ever written) {§semantic-fts-at-write}, and a `~` query **derives its own FTS-narrowed candidate slice inline** at dispatch (bounded, cap-telemetered) — ranking only ever scores the narrowed set, so warming exactly that slice is bit-identical to a fully-warm corpus, from turn 1, on any hardware {§semantic-cold-query-full-fidelity}. {§derivation-off-hot-path}
|
|
1141
1143
|
- **Binary truth beats the label; no entry dominates the corpus.** A tracked member whose HEAD bytes contain NUL is materialized as a binary marker (empty body, `application/octet-stream`, READ-415) **regardless of what extension-based detection claims** — the markdown default for unmapped extensions once shipped a 3.3MB `.wasm` blob into the semantic corpus as prose, three copies, ~10M tokens (#320) {§membership-binary-sniff}. And the per-entry chunk cap is a **latency stage, never a coverage bound**: the inline (dispatch-time) slice embeds head-first up to its budget so a cold `~` answers in bounded seconds — and a capped pass does NOT stamp the deep hash, so the background pump completes the entry to full depth (a 300-page book is entirely searchable at steady state; rank cannot be dominated regardless — `semantic_rank` takes one best chunk per entry). A flat cap would silently foreclose legitimate large texts: head-only vectors under a whole-file keyword narrow return head-biased spans, permanently. {§semantic-entry-chunk-cap}
|
|
1142
1144
|
- **A turn is never blank; the provider never adjudicates.** A completed exchange ALWAYS returns from the provider — the model's bytes flow in `assistant` no matter what, with any grammar-conformance verdict riding `response.telemetry` as an **observation** (providers 0.32; the provider transports and observes — the engine's own parse is the judge). Every emission takes the one pipeline: complete statements dispatch, malformed text mints position-carrying parse-error rows the model reads next turn, nothing-parsed is the ordinary no-ops 422, and the record keeps the emission verbatim with its real usage billed. A `ProviderError` reaching the engine means NO completed exchange exists (auth, network beyond retries, rate limit) — an infrastructure failure, and the loop dies 500 carrying the cause, first occurrence: tolerating an infrastructure violation is how a bug accretes concentric layers of policy instead of a fix (the retired empty-turn fallback laundered provider adjudications into model-behavior 422s, and forensics chased the wrong suspect for days). {§turn-never-blank}
|
|
1143
|
-
- **One model-agnostic ruler; exact only at the materialization gate.** The daemon runs many workers on DIFFERENT models in ONE workspace concurrently (per-loop model selection, #414), and token accounting is workspace-wide (the catalog lists every workspace entry). A single per-model "true" count is therefore a fiction — there is no one model to be true to — and exact tokenizers exist only for models with a bundled `tokenizer.json` (most frontier models degrade to the chars ruler anyway). So the ENTIRE model-facing perspective — catalog per-entry
|
|
1145
|
+
- **One model-agnostic ruler; exact only at the materialization gate.** The daemon runs many workers on DIFFERENT models in ONE workspace concurrently (per-loop model selection, #414), and token accounting is workspace-wide (the catalog lists every workspace entry). A single per-model "true" count is therefore a fiction — there is no one model to be true to — and exact tokenizers exist only for models with a bundled `tokenizer.json` (most frontier models degrade to the chars ruler anyway). So the ENTIRE model-facing perspective — catalog per-entry weights, `tokensFree`, ceiling, per-turn weights, write-time stamps — uses ONE model-INDEPENDENT ruler: `rulerCount = ceil(chars/2)` (mimetypes' no-exact heuristic). One number per content, identical no matter which model reads it; zero per-model state; trivially concurrent-safe. It is a conservative UPPER bound, so the model's whole ledger is coherent AND safe: comparing ruler-weight to the real-token ceiling (window − reserves, no calibration ratio) is itself the conservative bias — the model curates against less room than it has and never overflows for typical content, at the cost of some window under-utilization (accepted). The provider's EXACT count is used at exactly ONE point: the packet-materialization fit-gate (`exactPacketTokens` — provider.countTokens of the assembled packet, once per turn), which guards the pathological tail where the ruler under-counts token-dense content. The prior per-alias usage-calibration ratio and the (content_hash, tokenizer_id) per-tokenizer cache were RETIRED (owner ruling 2026-07-13): they delivered per-model exactness the real mixed-model topology mostly can't provide, at the cost of workspace-wide concurrent recount passes — latent-bug surface that reads as model weakness on benchmarks. A provider failure that still escapes lands as a loop-terminal **500 carrying the cause** on both the row and the `loop/terminated` broadcast. {§tokenomics-agnostic-ruler}
|
|
1144
1146
|
- **Curation pressure gates on occupancy.** The budget section's Turns/Heaviest tables — a standing FOLD-target list — render only at **50%+ occupancy** (assembled total / ceiling); below that the headline's numbers stand alone. A high-headroom model reads the tables as a todo and burns turns on token hygiene at 3–25% occupancy (#308, the bench grok run); a null ceiling (an unbounded window, §tokenomics-window-unpollable-deliberate) can't calibrate occupancy, so the headline is omitted and the tables ship un-truncated. The requirements footer carries only the overflow-RECOVERY must, never ambient optimize-your-context pressure. {§tokenomics-pressure-gates-on-occupancy}
|
|
1145
1147
|
- **The Budget renders as visual mermaid.** {§budget-mermaid} By default (`PLURNK_SERVICE_BUDGET_MERMAID`; set `off` to A/B against the tabular baseline, #440's before/after), the Budget section keeps its ceiling/usage/free headline (weighability) and REPLACES the **Turns** table with two budget-scaled mermaid diagrams — turn-composition `treemap-beta` (turn boxes + `system + context` + `free` compose the whole ceiling, the per-turn FOLD surface labeled `turn L/T`), and a used-vs-free `pie` gauge (which doubles as a training exemplar for the model's own user-facing SENDs). The **Heaviest-items** list stays a plain ranked table (`log:///` handle + tokens) — a ranking is not a composition, so it is never a chart, and two mermaid diagrams are enough visual examples (#450). Both diagrams scaled to the CEILING, so salience tracks pressure: `free` dominates when calm, the turn boxes fill toward urgent as the ceiling nears — so unlike the tabular Turns/Heaviest pair a mermaid budget is NEVER <50%-truncated (the calm low-usage view is the point). `free`/`used`/`system+context` resolve post-assembly like the headline figures. Syntax validated to render on GitHub (plurnk-plurnkdown/demo/budget-mermaid.md). #440
|
|
1146
1148
|
- **Depth re-counted at render.** The manifest re-tokenizes each entry's `tokens` through the live provider at build — never the write-time snapshot — so a model change between loops can't stale the catalog. Every token figure in the packet is render-fresh, manifest and budget alike; nothing trusts a cross-loop cached total.
|
|
@@ -1150,7 +1152,7 @@ Each entry: question, answer, rationale, migration path.
|
|
|
1150
1152
|
**Rejected / obviated.**
|
|
1151
1153
|
|
|
1152
1154
|
- **Hot model-switch recompute** — *obviated* by render-fresh depth (above). There's no cross-loop cache to recompute: the manifest re-tokenizes at build, the budget always did. A model change between loops can't stale a number nothing caches.
|
|
1153
|
-
- **Reasoning-token surfacing** — *rejected* for the model-facing budget: reasoning is *output*, not window-context, and the model can't FOLD it. The
|
|
1155
|
+
- **Reasoning-token surfacing** — *rejected* for the model-facing budget: reasoning is *output*, not window-context, and the model can't FOLD it. The reasoning-vs-output distinction is cost-forensics (the usage breakdown is stored on every packet), not a curation signal.
|
|
1154
1156
|
|
|
1155
1157
|
**Rationale.** Rummy used chars/DIVISOR + compute-at-SELECT only because its sync-only SQL couldn't call a tokenizer. plurnk has real `countTokens`: store content tokens once at write (the depth), measure the small rendered output for the budget (the weight). Approximation can't ground curation — the model only curates against numbers it trusts.
|
|
1156
1158
|
|
|
@@ -1166,7 +1168,7 @@ Each entry: question, answer, rationale, migration path.
|
|
|
1166
1168
|
|
|
1167
1169
|
**Workspace identity.** No `projects` table; `workspaces.project_root TEXT` (nullable = headless) anchors the workspace. `entries.scope ∈ {'workspace','worker'}` (agent-scope retired). Workspace = workspace; no users/auth/multi-tenant.
|
|
1168
1170
|
|
|
1169
|
-
**Search declares web members.** A search runtime enumerates candidate URLs and hands each to the engine as a `content: null` `entry()` request (§exec-entry-sink): the guarded `WebFetcher` sink fetches every candidate — in parallel, off the write-serialization chain — materializes the live ones as ordinary http entries and rejects the dead, so the executor writes `#results` as survivors only (chooser context: title/url/snippet/publishedDate).
|
|
1171
|
+
**Search declares web members.** A search runtime enumerates candidate URLs and hands each to the engine as a `content: null` `entry()` request (§exec-entry-sink): the guarded `WebFetcher` sink fetches every candidate — in parallel, off the write-serialization chain — materializes the live ones as ordinary http entries and rejects the dead, so the executor writes `#results` as survivors only (chooser context: title/url/snippet/publishedDate). Weights never ride the digest — FIND over the tagged set is the real-weight listing, its body-less catalog rows carrying `{mimetype, tokens, lines}` natively. Because the search family is in `PLURNK_SERVICE_EXEC_HOLD` the cycle holds until the stream concludes (§exec-hold-until-concluded), and the executor awaits every `entry()` before it concludes — so the digest the model's next packet carries is post-fetch truth, dead rows already gone, with no separate pass. A materialized page is INDISTINGUISHABLE from a hand-READ page: same channels, same validators, same catalog row — there are no magic entries. **The search gates** (#406, owner ruling) are rail-family accounting — in-memory per-loop state cleaned at the same seam as strikes, restart-drop accepted (a post-restart duplicate re-fetches; the TTL makes it cheap): an IDENTICAL duplicate (same runtime + command in one loop) **strikes and serves** — status 409 (the strike rail counts the turn failure) carrying the prior survivor digest re-read live from the original exec entry, no re-fetch, no provenance prose; the per-turn CAP (`PLURNK_SERVICE_SEARCH_MAX_PER_TURN`) is flood control — 429 with a legible steer, nothing served. {§search-gate}
|
|
1170
1172
|
|
|
1171
1173
|
**git is the substrate.** {§membership-git-membership} git-tracked files (`git ls-files` semantics) are members with no explicit overlay — channel-less markers, disk is truth. git absent → no fs-walk (non-git/headless get no substrate membership); `pick` is then the sole source. **Every core git read is in-process by default** {§git-portable-default}: isomorphic-git (exact-pinned), an explicit-`dir` library read — no subprocess, no ambient `GIT_*`, no global/system config — so the read is hermetic **by construction** and identical on a sandboxed or git-less host; linked worktrees and submodule boundaries (gitlinks filtered) behave as native. The untracked scan is a pruning ignore-walk, **differential-gated**: it must reproduce native `ls-files --others --exclude-standard` across a gitignore edge-case corpus (negations, anchors, nested `.gitignore` precedence, `.git/info/exclude`, dir-vs-glob patterns, embedded-repo boundaries) or it does not ship. `PLURNK_SERVICE_GIT_NATIVE=1` routes membership + status to the system git binary {§git-native-flag} — in-process membership measures ~8× native (~130ms at 20k files) and the status read ~55× (statusMatrix hashes the workdir), so a large-repo host with git installed buys the hot path back. **That native arm scrubs ambient `GIT_*`** (#401): a process launched from a git hook inherits `GIT_DIR` — absolute in a worktree checkout — which retargets every child git at the enclosing repo regardless of cwd; project git state binds to the workspace's `project_root`, never to whoever spawned the daemon. One boundary (`git-env.hermeticGitEnv`), threaded through the native arm and every shell-git test fixture. {§membership-git-hermetic}
|
|
1172
1174
|
|
|
@@ -1214,7 +1216,7 @@ The CAS is the **hard backstop**, at the moment of writing, on every accept path
|
|
|
1214
1216
|
- **The hard overflow is a RECOVERY TURN first** {§grinder-hard-413-recovery} — (Q4, owner ruling: recoverable strike, NO margin). The overflow lives in foldable HISTORY the model owns, and the grinder never touches history (§grinder-layer1-rollback) — so the first hard overflow, when the packet is over the POLICY ceiling but still within PHYSICS (the provider's real window minus the decode reserve), is sent ANYWAY, once: a minted `budget_overflow` steer states the fact, the remedy (KILL/FOLD history; the budget table lists the heaviest), and the consequence — and the turn counts a strike (`budgetStruck`). The model curates → the next fitting turn clears the grant (a later overflow earns a fresh recovery; chronic overflow still strikes out via the rail). A recovery turn that CONCLUDES is a legitimate 200 — finishing is a way to stop overflowing. **No 95% margin**: the pointer fires at 100% of budget — a margin masks the signal, and the decode is separately reserved.
|
|
1215
1217
|
- **Hard stop.** A physically-unsendable packet (over the provider's real window too — physics doesn't negotiate), or a SECOND consecutive hard overflow after the recovery turn (the model was told and declined), abandons the loop at **413 Content Too Large** (`engine_loop_set_status`). Its sibling engine-imposed terminals are HTTP-precise too: `maxTurns` → 429, a strike-out → 500 (508 when cycle-driven) — no longer the old catch-all 499. No further passes. {§grinder-hard-413-abort}
|
|
1216
1218
|
|
|
1217
|
-
- **Output truncation states its cause** {§tokenomics-output-truncated} — a `finish=length` turn was guillotined at the decode pool (`REASONING + COMPLETION`, one undifferentiated `max_tokens` — §tokenomics-window-partition), and its parse errors are truncation ARTIFACTS: without the cause stated, the model reads "unclosed block" and fixes syntax forever instead of emitting less (run29: unclamped cloud reasoning + a 9-edit emission vs a 12288 pool; the binding cap was plurnk's own — `completion 12281`). One terse `output_truncated` row (413) leads the artifact rows it explains, naming the cap and distinguishing its two shapes so the model reads the right cause: content cut **mid-op** (a valid prefix dispatched; the parse errors are the severed tail) vs the pool consumed with **nothing emitted** (reasoning ran away — the parse `must begin with PLAN` is an artifact of the empty emission, not a malformed turn). The row states the fact, not a remedy. The parse rows stay — the record never hides — and the partition's bluntness for unclamped cloud reasoning (
|
|
1219
|
+
- **Output truncation states its cause** {§tokenomics-output-truncated} — a `finish=length` turn was guillotined at the decode pool (`REASONING + COMPLETION`, one undifferentiated `max_tokens` — §tokenomics-window-partition), and its parse errors are truncation ARTIFACTS: without the cause stated, the model reads "unclosed block" and fixes syntax forever instead of emitting less (run29: unclamped cloud reasoning + a 9-edit emission vs a 12288 pool; the binding cap was plurnk's own — `completion 12281`). One terse `output_truncated` row (413) leads the artifact rows it explains, naming the cap and distinguishing its two shapes so the model reads the right cause: content cut **mid-op** (a valid prefix dispatched; the parse errors are the severed tail) vs the pool consumed with **nothing emitted** (reasoning ran away — the parse `must begin with PLAN` is an artifact of the empty emission, not a malformed turn). The row states the fact, not a remedy. The parse rows stay — the record never hides — and the partition's bluntness for unclamped cloud reasoning (reasoning-tokens and emission-tokens sharing one pool) is a known limit: sizing REASONING to the model class is the operator lane; a per-API split decode budget is a providers-contract candidate, owner-gated (run52 made it concrete — a cloud reasoner spent the entire 65536 pool on reasoning, emitting zero content, three times).
|
|
1218
1220
|
|
|
1219
1221
|
- **Fetch-fits-free — the pressure law** {§tokenomics-fetch-fits-free} — under budget pressure, a retrieval larger than the headline's Tokens Free arrives ALREADY FOLDED: the result lands in the next build, the build overflows, and the grinder's one rule folds the newest boundary — which is exactly that result. The model never sees an oversized fetch open, however many times it re-fetches (the read→grind→re-read spiral: run24 on grok, the jumbo fixture on gemma — five turns of narrowing asks, three grind-strikes, 500). The engine's numbers are honest and the grinder is doctrine-correct; the model's ONLY working lever is ordering: FOLD history first (the Heaviest-items table is the target list), then fetch within the room made. The 413 error row states this law verbatim — the signal fires exactly when the lesson applies. Corollary: each turn accrues ~a couple hundred tokens of unavoidable meta (the mirror row, PLAN, budget growth), so a Tokens Free below that accretion is a dead state the NEXT build inherits — conclude or fold before it reaches zero, not at zero.
|
|
1220
1222
|
|
|
@@ -1298,7 +1300,11 @@ type Packet = {
|
|
|
1298
1300
|
|
|
1299
1301
|
The wire projection (`PacketWire.renderSlot`) groups sections by slot into the system + user ChatMessages; the digest re-renders the same stored sections byte-for-byte.
|
|
1300
1302
|
|
|
1301
|
-
**The
|
|
1303
|
+
**The lexicon has two standards, split by audience.** {§lexicon} The OPERATOR/WIRE/STORAGE layers follow the industry lexicon — where a concept is standard, its standard name (OpenAI's for provider quantities: `contextWindow`, `reasoning`, `completion`, `finish_reason`, usage nouns). The MODEL-FACING packet follows the training distribution — ops mirror HTTP/shell, `display` mirrors CSS, jsonplurnk is JSON — because that is the standard the model has actually read; renaming packet vocabulary to API-speak trades load-bearing resonance for a standard the model never sees at that layer. A standard name on a NON-standard concept is the worst case (a label that lies with credentials): `promptBudget` is deliberately not `contextWindow` because it holds window-minus-reserves. **Deliberate keeps** (each a plurnk-native concept with no honest standard name): `worker`/`loop`/`turn` — the OS process paradigm; the industry's countable ("run") was retired EMPTY and the client seam translates `loop` → AG-UI RUN events; `packet` — the assembled address space, a kernel concept, not a "request"; `costPico` — no standard cost field exists; picodollar integers avoid float cents; `PLURNK_SERVICE_SAFETY` — the ruler's packing margin, a service fact no provider owns; the `chars/2` ruler — model-agnostic BY DESIGN (§tokenomics-agnostic-ruler). Retired terms stay retired: the lexicon guard fails CI on `thinking`, the `session` noun, `contextSize`, `decodeBudget`, and the moved partition knob names — drift is caught at commit time, not the next audit. <!-- lexicon-allow: this section QUOTES the retired terms it bans -->
|
|
1304
|
+
|
|
1305
|
+
**Sealed reasoning rides the mirror row.** {§sealed-reasoning-carrier} A provider's ENCRYPTED reasoning — the OpenAI Responses reasoning-item LIST (`[{id, subtype, encrypted: [{data, format}]}]` — a turn can carry N distinct-id items; normalized by the provider tier: id + subtype from the wire, never synthesized) — lands VERBATIM in the model mirror row's `attrs.reasoning` — per turn, on the same `log/entry` broadcast and `readLog` read the client seam already consumes; agui projects one correlated `REASONING_ENCRYPTED_VALUE` span per item (#482). A cross-lane conformance test drives core's real write through agui's real Translator, so a future non-meeting seam is a red pre-push gate, not a shipped-broken main. The blobs are never decoded, never synthesized, and never rendered into a packet — the packet renderer reads no foreign attrs keys, so the model never pays tokens for ciphertext it cannot read. Readable text keeps riding `assistant.reasoning`; the two never mix.
|
|
1306
|
+
|
|
1307
|
+
**The arrival law.** {§arrival-law} Every body lands as an entry and appears in the packet as a weighted meta line; how much body rides OPEN on arrival is set by INVITATION. **Self-invited** content — what this worker addressed by its own op (READ/FIND/OPEN results, its own exec streams) — rides open within free headroom; over, it arrives folded (the grinder + budget_overflow contract). **Pushed** content — text another actor put in front of this worker (the user prompt, a child's deliverable, a sibling irc) — rides the PREVIEW: whole when under the bound, the head + the cut statement + the pull address when over. The bound is `PLURNK_SERVICE_ARRIVAL_PREVIEW_LINES` (default 16) lines AND 80×N chars — the char cap guards single-line bombs. The trust line is authorship: a worker's own ask is its curation, weighed in advance; another actor's push is unratified by the recipient at any size (run111: a child's legally-terminal 19,363-token deliverable landed whole in its parent, was relayed into a 40k child prompt, and cascaded the pipeline — #499). A child whose loop ends non-2xx already lands FOLDED (the termination-delta insert); the preview bounds the ratified-giant case. New lanes inherit the law at the arrival boundary — a future scheme or module cannot reopen the hole by adding a feature. The preview is enforced at RENDER for every pushed lane — the prompt foist's line-slice bounds lines, and the render's char cap cuts what a line-slice cannot (a single-line char-bomb prompt renders as its 80×N-char head + the cut statement + its address).
|
|
1302
1308
|
|
|
1303
1309
|
**Prompt as a first-class entry.** Each loop's prompt is written on loop start as a plurnk-origin `EDIT` against `plurnk://prompt/<worker>/<loop>/<N>` (indexable, body channel, text/markdown); injected mid-loop prompts land the same way at their turn's slot. The **User Prompts** section sits at the system packet's very bottom as a PATHS-ONLY list (the errors shape — `* plurnk://prompt/<worker>/<loop>/<N>`, no bodies): every prompt the loop holds stays listed and READable by address, so prior prompts are never silently lost and recovering them is never a curation imposition. {§prompt-auto-read} **The address is worker-qualified** (#382): entries are workspace-scoped while loop sequences are per-worker — every worker's first loop is sequence 1, so an unqualified `/prompt/1/1` is one key shared by a parent and every WORK-spawned sister, and a worker's turn-1 foist would overwrite the parent's task. The worker id in the path is `/proc/<pid>`-style process qualification — one filesystem, collision-free coordinates; every writer and query builds the path through one boundary (`plurnk-uri.promptPathname`/`promptLoopPrefix`). The model never constructs the address — it READs what the section lists. {§prompt-worker-qualified} The prompt's CONTENT reaches the model through a foisted auto-`READ` of its own entry — `<1,16>` (the §arrival-law preview bound, `PLURNK_SERVICE_ARRIVAL_PREVIEW_LINES`), or `<1,-1>` when the prompt runs under it (the whole-read form doubles as teaching) — dispatched on the first turn for the loop prompt and on the landing turn for each injected prompt, so an arriving prompt always opens in the log like any retrieval. The foisted `EDIT`'s **log row is folded by default** (`expanded=0`): the body arrives via the auto-READ, so the log keeps the write for forensics while collapsing the duplicate, re-OPENable like any fold (§open-fold). {§prompt-fold} **A model `FOLD` targeting a prompt row is ILLEGAL** (#382 — a weak model in a housekeeping turn folded its own task auto-READ and lost the plot): the fold is refused (*"Illegal attempt to FOLD a user prompt. Use KILL if you want it removed."*), so the frame can't be accidentally hidden — while `KILL` still deletes it deliberately, preserving the model's curation of a prompt it genuinely wants gone. A glob sweep silently spares the prompt and folds the rest. {§prompt-fold-illegal}
|
|
1304
1310
|
|
|
@@ -1347,7 +1353,7 @@ Strike accounting, cycle detection, sudden-death thresholds, and no-ops bookkeep
|
|
|
1347
1353
|
|
|
1348
1354
|
**Client surface.** Engine NOTICES broadcast live via the `telemetry/event` WS notification — same envelope as the model's drained copy (`{ source, kind, level, message?, position?, …kind-specific }` per the grammar's `TelemetryEvent` schema), the moment they land, scoped to the loop's workspace (a `grammar_unenforced` snippet in a debug panel, a workspace timeline). ERRORS do not broadcast on this surface: they are log rows, and the client reads them the same way the model curates them — `log.read` / the `log/entry` notification, the durable log. {§telemetry-telemetry-event-notify}
|
|
1349
1355
|
|
|
1350
|
-
**Turn-lifecycle liveness.** The provider `generate()` call is the one long, opaque window in a turn — submit → first committed op is provider latency plus a full first-turn generation (tens of seconds on a local model); a static client screen there is indistinguishable from a hang. The engine brackets `generate()` with two `telemetry/event` NOTICES (`source: "engine:turn"`, `level: "info"`): `turn_awaiting_model` the instant it calls the provider, `turn_generated` when the call resolves and op-parsing begins — a legible
|
|
1356
|
+
**Turn-lifecycle liveness.** The provider `generate()` call is the one long, opaque window in a turn — submit → first committed op is provider latency plus a full first-turn generation (tens of seconds on a local model); a static client screen there is indistinguishable from a hang. The engine brackets `generate()` with two `telemetry/event` NOTICES (`source: "engine:turn"`, `level: "info"`): `turn_awaiting_model` the instant it calls the provider, `turn_generated` when the call resolves and op-parsing begins — a legible reasoning→working heartbeat, NOT model token-content (that stays out of the Log, a paradigm break). Both are suppressed on an aborted loop and broadcast to the workspace like any notice (§telemetry-telemetry-event-notify). Optional intra-generation ticks (a moving counter during the long wait) are a later provider-contract enhancement (an `onProgress` on `generate()`, the `embedBatch` shape); the two-beat bracket needs no provider change. {§turn-lifecycle}
|
|
1351
1357
|
|
|
1352
1358
|
**Content-offset position.** An emission-level error carries a `position: { type: "content-offset", line, column }` into the model's own emission — a parse-error LOG ROW (op='error', §model-entry) and a content-offset NOTICE (e.g. a provider's `grammar_unenforced`) both report the line, not the bytes. The model resolves it against its own emission: the `model` mirror row (§model-entry, always folded) holds the line-numbered emission, and the model `READ`s the folded row at the cited lines — surgical, budget-bounded, no auto-opening. No snippet is embedded — that would duplicate an emission the model can already introspect. {§telemetry-content-offset-pointer}
|
|
1353
1359
|
|
|
@@ -50,7 +50,7 @@ export default class Dispatcher {
|
|
|
50
50
|
loopId: number;
|
|
51
51
|
origin?: WriterTier;
|
|
52
52
|
}): Promise<DispatchResult>;
|
|
53
|
-
writeModelEntry({ verbatim, workerId, loopId, turnId, sequence, folded, origin }: {
|
|
53
|
+
writeModelEntry({ verbatim, workerId, loopId, turnId, sequence, folded, origin, reasoningItems }: {
|
|
54
54
|
verbatim: string;
|
|
55
55
|
workerId: number;
|
|
56
56
|
loopId: number;
|
|
@@ -58,6 +58,14 @@ export default class Dispatcher {
|
|
|
58
58
|
sequence: number;
|
|
59
59
|
folded: boolean;
|
|
60
60
|
origin?: WriterTier;
|
|
61
|
+
reasoningItems?: ReadonlyArray<{
|
|
62
|
+
id: string | null;
|
|
63
|
+
subtype: string;
|
|
64
|
+
encrypted: ReadonlyArray<{
|
|
65
|
+
data: string;
|
|
66
|
+
format: string | null;
|
|
67
|
+
}>;
|
|
68
|
+
}>;
|
|
61
69
|
}): Promise<number>;
|
|
62
70
|
}
|
|
63
71
|
//# sourceMappingURL=Dispatcher.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dispatcher.d.ts","sourceRoot":"","sources":["../../src/core/Dispatcher.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"Dispatcher.d.ts","sourceRoot":"","sources":["../../src/core/Dispatcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAmD,MAAM,wBAAwB,CAAC;AAC/G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAC;AAO5D,OAAO,KAAK,EAAkB,UAAU,EAAkC,MAAM,mBAAmB,CAAC;AAEpG,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAerH,MAAM,MAAM,eAAe,GAAG;IAC1B,SAAS,EAAE,eAAe,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAG1C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAgBxF,MAAM,CAAC,OAAO,OAAO,UAAU;;IAuB3B,YAAY,EAAE,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,EAAE;QACpM,EAAE,EAAE,EAAE,CAAC;QACP,OAAO,EAAE,cAAc,CAAC;QACxB,SAAS,EAAE,SAAS,CAAC;QACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;QACnC,SAAS,EAAE,gBAAgB,CAAC;QAC5B,SAAS,EAAE,iBAAiB,CAAC;QAC7B,SAAS,EAAE,MAAM,gBAAgB,GAAG,SAAS,CAAC;QAC9C,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,WAAW,GAAG,SAAS,CAAC;QACxD,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;QACtC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;QACpC,YAAY,CAAC,EAAE,kBAAkB,CAAC;QAClC,YAAY,CAAC,EAAE,kBAAkB,CAAC;QAClC,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACpC,UAAU,CAAC,EAAE,OAAO,kBAAkB,EAAE,OAAO,CAAC;QAChD,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;KAC7B,EAgBA;IAEK,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAiIhE;IAOK,IAAI,CAAC,OAAO,EAAE;QAChB,SAAS,EAAE,eAAe,CAAC;QAC3B,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QACtD,MAAM,CAAC,EAAE,UAAU,CAAC;KACvB,GAAG,OAAO,CAAC,cAAc,CAAC,CAQ1B;IA0ZK,eAAe,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAgB,EAAE,cAAc,EAAE,EAAE;QAC9G,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,UAAU,CAAC;QAQ3H,cAAc,CAAC,EAAE,aAAa,CAAC;YAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,aAAa,CAAC;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;aAAE,CAAC,CAAA;SAAE,CAAC,CAAC;KAC7I,GAAG,OAAO,CAAC,MAAM,CAAC,CAelB;CAiZJ"}
|
package/dist/core/Dispatcher.js
CHANGED
|
@@ -645,7 +645,7 @@ class Dispatcher {
|
|
|
645
645
|
// the model can finally SEE its own prior output (and reason through its own syntax errors).
|
|
646
646
|
// Born FOLDED by default (budget-neutral until OPENed); the turn-0 exemplar passes folded:false
|
|
647
647
|
// (born open — the one worked example the model orients on, thinning the grammar). text/vnd.plurnk.
|
|
648
|
-
async writeModelEntry({ verbatim, workerId, loopId, turnId, sequence, folded, origin = "model" }) {
|
|
648
|
+
async writeModelEntry({ verbatim, workerId, loopId, turnId, sequence, folded, origin = "model", reasoningItems }) {
|
|
649
649
|
const row = await this.#db.engine_insert_log_entry.get({
|
|
650
650
|
worker_id: workerId, loop_id: loopId, turn_id: turnId, sequence,
|
|
651
651
|
origin, source: null, op: "model", suffix: "", signal: null,
|
|
@@ -654,7 +654,8 @@ class Dispatcher {
|
|
|
654
654
|
tx: "", mimetype_tx: "text/vnd.plurnk",
|
|
655
655
|
rx: JSON.stringify({ content: verbatim, mimetype: "text/vnd.plurnk" }),
|
|
656
656
|
mimetype_rx: "application/json",
|
|
657
|
-
status_rx: 200, tokens: this.#tokenize(verbatim), state: "resolved", outcome: null,
|
|
657
|
+
status_rx: 200, tokens: this.#tokenize(verbatim), state: "resolved", outcome: null,
|
|
658
|
+
attrs: reasoningItems !== undefined && reasoningItems.length > 0 ? JSON.stringify({ reasoning: reasoningItems }) : "{}",
|
|
658
659
|
});
|
|
659
660
|
if (row === undefined)
|
|
660
661
|
throw new Error("Dispatcher.writeModelEntry: insert returned no row");
|