@plurnk/plurnk-service 0.52.0 → 0.54.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/SPEC.md +34 -10
- package/dist/core/Engine.d.ts +1 -0
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +120 -37
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +39 -7
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +1 -2
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/packet-wire.d.ts +1 -2
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +111 -138
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/teaching.d.ts +1 -1
- package/dist/core/teaching.d.ts.map +1 -1
- package/dist/core/teaching.js +6 -4
- package/dist/core/teaching.js.map +1 -1
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +11 -24
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +3 -2
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Run.js +1 -1
- package/dist/schemes/Run.js.map +1 -1
- package/dist/schemes/_entry-find.d.ts +1 -0
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +8 -2
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +5 -8
- package/dist/server/Daemon.js.map +1 -1
- package/docs/log.md +1 -1
- package/package.json +16 -15
- package/requirements.md +3 -9
- package/dist/schemes/exec-receipt.d.ts +0 -4
- package/dist/schemes/exec-receipt.d.ts.map +0 -1
- package/dist/schemes/exec-receipt.js +0 -25
- package/dist/schemes/exec-receipt.js.map +0 -1
package/SPEC.md
CHANGED
|
@@ -562,8 +562,26 @@ AST: `{ op: "FIND", target (scope), body: MatcherBody | null (predicate), signal
|
|
|
562
562
|
|
|
563
563
|
AST: `{ op: "SEND", target: ParsedPath | null, body: SendBody | null, signal: number | null }`.
|
|
564
564
|
|
|
565
|
-
- **Broadcast** (path null):
|
|
566
|
-
- **Directed** (path non-null): routes to `scheme.send` per §send-dispatch.
|
|
565
|
+
- **Broadcast** (path null): the loop's disposition verb. `signal` is the model's *claim* about the run's state — see the terminal contract.
|
|
566
|
+
- **Directed** (path non-null): routes to `scheme.send` per §send-dispatch — stream control / cross-run irc, never a loop terminal.
|
|
567
|
+
|
|
568
|
+
**Terminal contract — the model's surface.** A broadcast SEND's status is a claim the engine **verifies against the run's actual state**, never a verdict it trusts. The model is trusted with exactly four codes:
|
|
569
|
+
|
|
570
|
+
| signal | claim | effect |
|
|
571
|
+
|---|---|---|
|
|
572
|
+
| **102** | continue | turn closes, another turn fires. Not terminal. |
|
|
573
|
+
| **200** | done | terminal — *only* when the run holds no live stream/spawn; otherwise the Premature-Terminate state below fires. Updates `loop.status`, ends the loop. |
|
|
574
|
+
| **202** | hibernate | terminal-but-resumable: the loop **sleeps** awaiting a wake edge — a stream-status transition or a directed prompt (§actor-boundary-passive-wake, §run-lifecycle-wake-liveness). NOT advertised to the model: it lives in `run.md` and reaches the model only via the engine's steering, never the hot-path packet. Distinct from the dispatch-internal proposal-202 (§proposal), which the model never emits. |
|
|
575
|
+
| **499** | give up | terminal — the model's **one** self-decided failure (a self-cancel; 499 = cancelled, §state-terms). The only failure it is trusted to declare for itself. |
|
|
576
|
+
|
|
577
|
+
The engine's failure terminals — **500** (strike threshold) and **508** (cycle), §engine-rails — are never the model's to pick; they are the engine ruling the loop failed. The surface is small on purpose: the model says done, waiting, or giving up, and is never asked to hold a correct opinion about *how* it failed or *whether* it can be woken — the engine decides those from state.
|
|
578
|
+
|
|
579
|
+
**Two engine error states verify the claim.** Neither is a status code the model learns; both are engine machinery (§engine-rails), pushed to the model as a steering hint on the next packet and **never** as the strike itself (the model sees errors that happened, never the engine's accounting — the gamification policy, §engine-rails). Each strikes (`turnErrors`) and lets the loop continue so the model can correct; a model that ignores the hint and keeps offending spins out to the engine's 500, seeing only the repeated hint, never the count. (Both live at `Engine.runLoop`'s turn close.)
|
|
580
|
+
|
|
581
|
+
- **Idle turn** {§send-idle-turn} — a continuing turn (102) whose ops are only PLAN/SEND — no work op. The model continued with nothing to do. The steer, verbatim: *"If the turn's work is complete, terminate with 200. If awaiting a stream or run trigger, terminate with 202 to hibernate."*
|
|
582
|
+
- **Premature terminate** {§send-premature-terminate} — a `SEND[200]` while the run holds a live stream or spawn (§subscriptions, §run-lifecycle-total-reap). The model declared done with work still running. The engine **downgrades the 200 to 102** — the turn continues *and the SEND's body is preserved* (it dispatches as a continue, never discarded) — and steers, verbatim: *"Attempted termination with active streams. Terminate with 202 to hibernate until stream completion, KILL(path) with 200 again to clean up, or 499 to fail."*
|
|
583
|
+
|
|
584
|
+
**Dead-park caveat — read before changing 202.** A 202 is a real hibernation only when a wake edge exists; a 202 with *no* wake edge sleeps forever. Today's guard is **preventive** — un-advertising 202 plus the two steering states keep the model from declaring a spurious 202 — **not** a daemon-side wake-edge check that terminates a wake-edge-less 202. That check is the known backstop, **deferred**. Until it lands, a model that emits 202 with nothing to wake it *can* hang; do not assume a wake-edge guarantee exists.
|
|
567
585
|
|
|
568
586
|
### §exec EXEC
|
|
569
587
|
|
|
@@ -573,7 +591,9 @@ Engine routes unconditionally to `exec` scheme (path slot is `cwd`, not a URI).
|
|
|
573
591
|
|
|
574
592
|
**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 run waits for a human gate, then spawns and writes stdout/stderr to channels of an `exec:///<runtime>/<loop>/<turn>/<seq>` entry (the executor is the URI authority; the coordinate that follows matches the op's log-row coordinate, e.g. `exec:///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}
|
|
575
593
|
|
|
576
|
-
A `read` runtime (observes external state, e.g. search) or `pure` runtime (no observable effect, e.g. `:memory:` sqlite) is side-effect-free → **auto-run
|
|
594
|
+
A `read` runtime (observes external state, e.g. search) or `pure` runtime (no observable effect, e.g. `:memory:` sqlite) is side-effect-free → **auto-run**: no proposal, no human gate, no notification. It skips the gate a host command faces, but it does NOT resolve in-band — like every exec it backgrounds and streams, its output reaching the model through the environment-observation injector (a foisted READ of the stream's new bytes each turn, §exec-stream), never a same-turn receipt. {§exec-readpure-ungated}
|
|
595
|
+
|
|
596
|
+
**Stream surfacing.** An exec's output is *observed, not fetched*. Each turn the environment-observation injector — the same machine §env-delta rides — reads each of the run's open channels from a per-channel byte cursor and foists the new bytes as an `origin=plurnk` READ at `<runtime>:///<coord>#<channel>`, then advances the cursor — each delta carries the `startLine` that cursor implies, so a stream spanning turns numbers into one continuous sequence (lines 1–k, then k+1–m), not a fresh `1:` each turn. The delta is **folded** while the channel streams and auto-**opened** on the terminal one (the channel closed): a model ignores a chatty background run but always SEES a finished one. It never types these READs — it consumes them. The EXEC row itself renders the *command* it ran, `:::`-fenced and line-numbered per §render-rule so the model can line-reference its own code — the input, distinct from the stream above (the output). This is exec as an instance of one ambient machine, env-delta as another (sibling edits, timestamp cursor, always folded). {§exec-stream}
|
|
577
597
|
|
|
578
598
|
`SEND[499](exec:///<runtime>/<loop>/<turn>/<seq>)` cancels in-flight subprocess via subscription registry's stored AbortController (§stream-control).
|
|
579
599
|
|
|
@@ -1185,7 +1205,7 @@ The CAS is the **hard backstop**, at the moment of writing, on every accept path
|
|
|
1185
1205
|
type PacketSection = {
|
|
1186
1206
|
name: string; // stable id: definition, tools, schemes, log, prompt, budget, errors, git, requirements — or a plugin's own
|
|
1187
1207
|
slot: "system" | "user"; // the prompt-cache boundary; system-slot sections build the cache-stable system message
|
|
1188
|
-
header: string | null; // "
|
|
1208
|
+
header: string | null; // "## Plurnk System X", or null (definition renders verbatim)
|
|
1189
1209
|
content: string; // rendered markdown — what the model saw
|
|
1190
1210
|
tokens: number; // measured render-weight
|
|
1191
1211
|
};
|
|
@@ -1217,7 +1237,7 @@ Telemetry the model MUST react to immediately. Errors render in their own `error
|
|
|
1217
1237
|
|
|
1218
1238
|
- `budget` per §tokenomics: turn-weight and heaviest-entries tables with `tokenCeiling`/`tokenUsage`/`tokensFree`.
|
|
1219
1239
|
- `errors[]` from previous turn's dispatch. Required: `kind` discriminator. Additional kind-specific fields are flat on the element — NO nested `detail`. Canonical-JSON serialization sorts keys for prefix-cache friendliness.
|
|
1220
|
-
- Wire: one `* {canonical-JSON}` line per error under
|
|
1240
|
+
- Wire: one `* {canonical-JSON}` line per error under `## Plurnk System Errors`, push order. Buffer drains on read. {§telemetry-drain-on-read}
|
|
1221
1241
|
- **No prose `message` field.** Errors carry structured facts. The `kind` is the alert; the named fields are the data. Guidance, advice, hints, and exhortation MUST NOT appear in telemetry. Letting the model infer what to do from facts (and the log) beats handing it instructions it will second-guess.
|
|
1222
1242
|
- **Gamification policy (rummy precedent, plugins/error/error.js).** The model sees errors that **happened** — its actions failed, its emission didn't parse, its ops were truncated. The model does NOT see the engine's accounting *about* errors: strike streaks, cycle detection, sudden-death thresholds, no-ops bookkeeping. Surfacing internal state creates a gamification surface where the model optimizes for engine metrics (manufacturing a clean turn to reset the strike counter, e.g.) instead of the task. Engine bookkeeping drives abandonment silently; the model just sees its actual failures.
|
|
1223
1243
|
|
|
@@ -1240,23 +1260,23 @@ Strike accounting, cycle detection, sudden-death thresholds, and no-ops bookkeep
|
|
|
1240
1260
|
|
|
1241
1261
|
### §tools user.tools — the capability sheet
|
|
1242
1262
|
|
|
1243
|
-
The tools capability lines render **titleless**, directly under the `definition` (plurnk.md) section — the examples flow on from plurnk.md with no separate header — and **above**
|
|
1263
|
+
The tools capability lines render **titleless**, directly under the `definition` (plurnk.md) section — the examples flow on from plurnk.md with no separate header — and **above** `## Plurnk System Requirements`, so the model sees what it can *do* before the rules it must follow. Each enabled capability contributes one line via `Engine.#collectTools`; the section is omitted when nothing is enabled. {§tools-capability-sheet}
|
|
1244
1264
|
|
|
1245
|
-
**Contributors: the wired executor tags.** Each available executor tag *with an example* contributes ONE line — its canonical usage —
|
|
1265
|
+
**Contributors: the wired executor tags.** Each available executor tag *with an example* contributes ONE line — its canonical usage — via the shared `teachingLine` (identical shape to the scheme directory, §schemes); its doc is materialized at `plurnk://docs/<tag>.md` and discovered via the turn-1 `FIND(plurnk://docs/**)` foist, not linked inline (#270). A tag with no example contributes nothing; `PLURNK_DOCS_EXCLUDE` drops a named tag's line + doc. The boot `ExecutorRegistry` probes availability per tag, retiring the model's blind `<<EXEC[sh]…`.
|
|
1246
1266
|
|
|
1247
1267
|
### §schemes user.schemes — the scheme directory
|
|
1248
1268
|
|
|
1249
|
-
A
|
|
1269
|
+
A `## Plurnk System Schemes` section renders in the system slot **after the definition (plurnk.md — grammar + imperatives) and the tools sheet** — a terse directory of the scheme families available this session, so the model knows what URI schemes exist before it acts. Each scheme that ships a `manifest.example` contributes ONE line — its canonical usage (no scheme prefix; the example self-documents). The doc is NOT linked inline (#270) — it is materialized at `plurnk://docs/<scheme>.md` and discovered via the turn-1 `FIND(plurnk://docs/**)` foist, keeping the raw packet free of doc links. The in-tree core schemes author their depth in `docs/<name>.md` (loaded at boot, shipped with the package); daughter schemes ship `manifest.documentation`. The verbose semantics live in that pull doc (materialized like any entry, READ on demand), not the hot path — terse pushes, depth pulls, via the same `teachingLine` as the tools sheet (§tools). A scheme with no example (provisional) is omitted; `PLURNK_DOCS_EXCLUDE` drops a named scheme's line + doc. {§schemes-directory}
|
|
1250
1270
|
|
|
1251
1271
|
### §inject system.inject — the operator injection
|
|
1252
1272
|
|
|
1253
|
-
When `PLURNK_PACKET_INJECT` names a readable markdown file, its content renders as a
|
|
1273
|
+
When `PLURNK_PACKET_INJECT` names a readable markdown file, its content renders as a `## Plurnk Operator Notes` section in the system slot **right after the teaching** (definition → tools → schemes → inject), before the log — part of the cached prefix. Read per-turn so the operator's edits take effect live; a set-but-unreadable path fails the turn hard (a deliberate setting with a broken path is a misconfig, surfaced not hidden). `~/` 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. {§packet-inject}
|
|
1254
1274
|
|
|
1255
1275
|
**The scheme self-doc contract.** `example` is the hot-path one-liner; `documentation` is the deep doc — the exact shape execs already use (`example` + `documentation`). `SchemeRegistry.teach()` renders the directory; `docEntries()` materializes the docs (per loop.run, alongside the operator docs). `documentation` rides a service-side `SchemeManifest` extension until plurnk-schemes#25 lands it in the contract.
|
|
1256
1276
|
|
|
1257
1277
|
### §requirements The requirements section — static per-turn rules
|
|
1258
1278
|
|
|
1259
|
-
Rendered at the END of the user packet under
|
|
1279
|
+
Rendered at the END of the user packet under `## Plurnk System Requirements` {§requirements-requirements-render-last} — closest to the assistant turn so the contract the model has to honor is the most recent text it sees. The header is omitted entirely when the requirements string is empty. {§requirements-requirements-omitted-when-empty} Contains rules the grammar block doesn't cover (canonical example: "Conclude the loop with `<<SEND[200]:answer:SEND`"). The op syntax leads the section. PLAN is mandated unconditionally by plurnk.md §Imperatives (grammar 0.70 requires every turn to lead with `<<PLAN`), so the service injects no separate plan directive here.
|
|
1260
1280
|
|
|
1261
1281
|
**Sourcing:** caller supplies the string via `runLoop({ requirements })` / `runTurn({ requirements })`. Plurnk-service exposes `PATHS.defaultRequirements` (resolves `PLURNK_REQUIREMENTS` env → in-package `requirements.md`). No DB cascade — same string every turn.
|
|
1262
1282
|
|
|
@@ -1376,6 +1396,10 @@ Same rule applies across Known, Unknown, Skill, Plurnk, File. Effective mimetype
|
|
|
1376
1396
|
- **Line-navigable** (text/markdown, text/plain, csv, source code, yaml, toml) → `N:\t` line-number prefix per line {§render-rule-line-navigable-prefix}
|
|
1377
1397
|
- **Tree-navigable** (application/json, application/xml, text/html, +json/+xml suffixes) → verbatim body (no `N:\t` — outer line numbers would collide with structural navigation like jsonpath/xpath) {§render-rule-tree-navigable-verbatim}
|
|
1378
1398
|
|
|
1399
|
+
A log row renders its **result body** for the content-returning ops — `READ@200` (the content it pulled) and `FIND@200` (the catalog rows / matched entries it returned) — under the query's fence, mimetype-driven per the rules above; every other op re-emits its statement. FIND included: the model must see what a find *returned*, not just its echoed query, and the turn-0 foisted `FIND(scheme:///**)` reaches the packet through this branch — without it the catalog preview is invisible. {§render-rule-find-renders-result}
|
|
1400
|
+
|
|
1401
|
+
An `EDIT` log row renders its **resulting span** — the edited area as it looks now (`rx.span`), under the target's fence — not the input statement: the log reads "and here's X now," so the model sees its edit's effect. The meta line still carries op + target; the model's own EDITs and the system delta-EDITs (§env-delta) render identically; an emptied span → meta line only. With no span stored, the row falls back to re-emitting the statement (the heredoc the model wrote). {§edit-result-render}
|
|
1402
|
+
|
|
1379
1403
|
The `N:\t` prefix is presentation/reference per plurnk.md ("not part of the source"); stripped before any matcher operation on the log entry.
|
|
1380
1404
|
|
|
1381
1405
|
### §markdown-primitive Mimetype primitive: text/markdown
|
package/dist/core/Engine.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"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"}
|
package/dist/core/Engine.js
CHANGED
|
@@ -442,6 +442,8 @@ class Engine {
|
|
|
442
442
|
// SPEC §grinder: a non-soft grinder fire counts toward the strike streak.
|
|
443
443
|
if (turn.budgetStruck)
|
|
444
444
|
state.turnErrors++; // a grinder fire bumps the strike streak — §grinder-strike-coupling
|
|
445
|
+
if (turn.steerStruck)
|
|
446
|
+
state.turnErrors++; // idle / premature-terminate steer struck — §send the terminal contract
|
|
445
447
|
this.#strikeState.set(loopId, state);
|
|
446
448
|
// Rail #38: strike accounting. Three sources strike a turn:
|
|
447
449
|
// 1. recordedFailed — any action-entry at hard failure status
|
|
@@ -616,15 +618,33 @@ class Engine {
|
|
|
616
618
|
// one row per scheme that has entries (scheme=null → file). log:// is absent —
|
|
617
619
|
// it lives in log_entries, not the catalog (present-mode, the # Log section).
|
|
618
620
|
const catalogSchemes = await this.#db.engine_scheme_catalog_summary.all({ session_id: sessionId });
|
|
619
|
-
|
|
621
|
+
// known:/// + unknown:/// ALWAYS foist, even at zero entries — else the model
|
|
622
|
+
// burns a turn running FIND(known:///**) itself, assuming its memory is merely
|
|
623
|
+
// being withheld. Every other scheme keeps the with-entries default (an empty
|
|
624
|
+
// catalog foist is noise for schemes the model isn't expected to pre-populate).
|
|
625
|
+
const foistSchemes = [...catalogSchemes];
|
|
626
|
+
for (const always of ["known", "unknown"]) {
|
|
627
|
+
if (!foistSchemes.some((c) => c.scheme === always))
|
|
628
|
+
foistSchemes.push({ scheme: always, entries: 0 });
|
|
629
|
+
}
|
|
630
|
+
for (const { scheme, entries } of foistSchemes) {
|
|
620
631
|
const schemeName = scheme ?? "file";
|
|
621
|
-
|
|
632
|
+
// plurnk → its docs subtree (FIND(plurnk://docs/**), uncapped) — the self-
|
|
633
|
+
// documenting surface. The prompt is shown in # Prompt, so the plurnk catalog
|
|
634
|
+
// the model orients on IS the docs; doc links are no longer rendered inline (#270).
|
|
635
|
+
const isPlurnk = schemeName === "plurnk";
|
|
636
|
+
// entries===0 (an always-foisted empty known/unknown) → uncapped: nothing to
|
|
637
|
+
// clamp, and Math.min(items, 0)=0 would emit a degenerate <1,0> marker.
|
|
638
|
+
const cap = isPlurnk || manifestItems < 0 || entries === 0 ? null : Math.min(manifestItems, entries);
|
|
622
639
|
const catalogFind = {
|
|
623
640
|
op: "FIND", suffix: "", signal: null,
|
|
624
641
|
target: {
|
|
625
|
-
kind: "url",
|
|
642
|
+
kind: "url",
|
|
643
|
+
raw: isPlurnk ? "plurnk://docs/**" : `${schemeName}:///**`,
|
|
644
|
+
scheme: schemeName,
|
|
626
645
|
username: null, password: null, hostname: null, port: null,
|
|
627
|
-
pathname: ""
|
|
646
|
+
pathname: isPlurnk ? "/docs/**" : "/**",
|
|
647
|
+
params: {}, fragment: null,
|
|
628
648
|
},
|
|
629
649
|
body: null,
|
|
630
650
|
lineMarker: cap === null ? null : { marks: [1, cap] },
|
|
@@ -686,10 +706,13 @@ class Engine {
|
|
|
686
706
|
nextActionIndex++;
|
|
687
707
|
}
|
|
688
708
|
}
|
|
689
|
-
// §
|
|
690
|
-
//
|
|
691
|
-
//
|
|
709
|
+
// §environment-observation — pre-seed the run's ambient observations (what changed since
|
|
710
|
+
// it last looked) as foisted rows before the packet composes; advance the action index
|
|
711
|
+
// past them so model ops continue after. Two instances of one machine: env-delta (sibling
|
|
712
|
+
// edits · timestamp cursor · always folded) and exec streams (channel bytes · byte cursor ·
|
|
713
|
+
// terminal delta opens). §env-delta §exec-stream
|
|
692
714
|
nextActionIndex += await this.#materializeEnvironmentDeltas({ sessionId, runId, loopId, turnId, fromSequence: nextActionIndex });
|
|
715
|
+
nextActionIndex += await this.#materializeStreamDeltas({ runId, loopId, turnId, fromSequence: nextActionIndex });
|
|
693
716
|
// SPEC §telemetry — git working-tree state for the telemetry section, read once
|
|
694
717
|
// (a service-side `git status` shell-out) and threaded into the budget
|
|
695
718
|
// rebuild too so it isn't re-shelled on overflow.
|
|
@@ -719,7 +742,7 @@ class Engine {
|
|
|
719
742
|
usage_prompt: 0, usage_completion: 0, usage_cached: 0, usage_cost_pico: 0,
|
|
720
743
|
finish_reason: "budget_hard_stop", model: provider.model,
|
|
721
744
|
});
|
|
722
|
-
return { turnId, status: 413, statuses: [], fingerprint: "", budgetStruck: enforced.struck, budgetHardStop: true };
|
|
745
|
+
return { turnId, status: 413, statuses: [], fingerprint: "", budgetStruck: enforced.struck, budgetHardStop: true, steerStruck: false };
|
|
723
746
|
}
|
|
724
747
|
const modelMessages = this.#packetToWireMessages(requestPacket);
|
|
725
748
|
// maxTokens = remaining context window (loop policy, plurnk-providers#10).
|
|
@@ -794,12 +817,43 @@ class Engine {
|
|
|
794
817
|
// as no-ops, and the terminal scan ignores 1xx so they never set turnStatus.
|
|
795
818
|
const realOpsCount = packetAssistant.ops.filter((op) => op.op !== "PLAN" && !(op.op === "SEND" && op.signal === 103 && op.target === null)).length;
|
|
796
819
|
const sendOp = packetAssistant.ops.findLast((op) => op.op === "SEND" && typeof op.signal === "number" && op.signal >= 200);
|
|
797
|
-
//
|
|
798
|
-
//
|
|
799
|
-
//
|
|
820
|
+
// §send the terminal contract — two engine error states verify a terminal claim against run
|
|
821
|
+
// state, never trusting the model's code. Both strike via turn.steerStruck (turnErrors,
|
|
822
|
+
// §grinder-strike-coupling): the loop continues, the model sees the steering hint not the strike
|
|
823
|
+
// count, and a non-resolver spins out to the engine's 500.
|
|
824
|
+
let steerStruck = false;
|
|
825
|
+
// Premature terminate: a SEND[200] while the run still holds a live stream/spawn — the model
|
|
826
|
+
// declared done with work running. Downgrade the 200 to 102 so it dispatches as a continue (its
|
|
827
|
+
// body is preserved, not discarded) and steer; the stream's own conclusion or a KILL is the exit.
|
|
828
|
+
if (sendOp?.signal === 200) {
|
|
829
|
+
const openSubs = await this.#db.find_open_subscriptions_for_run.all({ run_id: runId });
|
|
830
|
+
const execHandler = this.#schemes.get("exec");
|
|
831
|
+
if (openSubs.length > 0 || execHandler?.hasActiveSpawns?.(runId) === true) {
|
|
832
|
+
sendOp.signal = TURN_STATUS_IMPLICIT_CONTINUE; // 102 — downgraded, no longer a terminal
|
|
833
|
+
steerStruck = true;
|
|
834
|
+
this.#pushTelemetry(sessionId, loopId, {
|
|
835
|
+
source: "engine:rail",
|
|
836
|
+
kind: "premature_terminate",
|
|
837
|
+
message: "Attempted termination with active streams. Terminate with 202 to hibernate until stream completion, KILL(path) with 200 again to clean up, or 499 to fail.",
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
// Rail #41 (revised): the per-turn requirement is "emit at least one op," not "emit a terminal
|
|
842
|
+
// SEND." SEND is purely a signal verb; many turns pass without one. An empty op list strikes.
|
|
800
843
|
const turnStatus = sendOp !== undefined
|
|
801
844
|
? sendOp.signal
|
|
802
845
|
: realOpsCount === 0 ? TURN_STATUS_NO_OPS : TURN_STATUS_IMPLICIT_CONTINUE;
|
|
846
|
+
// Idle turn: an implicit-continue (102) that did no WORK — its ops are only PLAN/SEND, no mid op.
|
|
847
|
+
// The model continued with nothing to do. (Skipped when premature already steered this turn.)
|
|
848
|
+
const midOpsCount = packetAssistant.ops.filter((op) => op.op !== "PLAN" && op.op !== "SEND").length;
|
|
849
|
+
if (!steerStruck && turnStatus === TURN_STATUS_IMPLICIT_CONTINUE && midOpsCount === 0) {
|
|
850
|
+
steerStruck = true;
|
|
851
|
+
this.#pushTelemetry(sessionId, loopId, {
|
|
852
|
+
source: "engine:rail",
|
|
853
|
+
kind: "idle_turn",
|
|
854
|
+
message: "If the turn's work is complete, terminate with 200. If awaiting a stream or run trigger, terminate with 202 to hibernate.",
|
|
855
|
+
});
|
|
856
|
+
}
|
|
803
857
|
// Close the turn with the final packet, status, and usage stats.
|
|
804
858
|
const packet = this.#completePacket(requestPacket, packetAssistant, response.assistantRaw, provider);
|
|
805
859
|
const { usage, finishReason, model } = callMetadata;
|
|
@@ -860,7 +914,7 @@ class Engine {
|
|
|
860
914
|
// nothing. Strike accounting (engine-internal) treats it as a
|
|
861
915
|
// struck turn; the model just sees an empty packet next turn.
|
|
862
916
|
// Per SPEC §telemetry gamification policy.
|
|
863
|
-
return { turnId, status: turnStatus, statuses, fingerprint: _a.fingerprintTurn(packetAssistant.ops), budgetStruck: enforced.struck, budgetHardStop: false };
|
|
917
|
+
return { turnId, status: turnStatus, statuses, fingerprint: _a.fingerprintTurn(packetAssistant.ops), budgetStruck: enforced.struck, budgetHardStop: false, steerStruck };
|
|
864
918
|
}
|
|
865
919
|
// Split the wire-level ProviderResponse into the two destinations:
|
|
866
920
|
// packet.assistant gets the model's emission (content, ops, reasoning);
|
|
@@ -952,11 +1006,11 @@ class Engine {
|
|
|
952
1006
|
// requirements). Read Paths.defaultRequirements (PLURNK_REQUIREMENTS env →
|
|
953
1007
|
// requirements.md) fresh each build so edits take effect; a non-empty param wins.
|
|
954
1008
|
const baseRequirements = requirements.length > 0 ? requirements : await readFile(Paths.defaultRequirements, "utf8");
|
|
955
|
-
//
|
|
956
|
-
//
|
|
957
|
-
//
|
|
958
|
-
//
|
|
959
|
-
|
|
1009
|
+
// No injected syntax line: the grammar already headlines the system definition (§Syntax) and
|
|
1010
|
+
// leads requirements.md, so a third copy here was pure duplication in the model's packet. PLAN
|
|
1011
|
+
// is mandated unconditionally by plurnk.md §Imperatives (grammar 0.70 requires every turn to
|
|
1012
|
+
// lead with PLAN), so the service injects no separate plan directive either (the former
|
|
1013
|
+
// PLURNK_PLAN gating is retired — PLURNK_PLAN is no longer a flag).
|
|
960
1014
|
const log = await this.#buildLog(runId);
|
|
961
1015
|
const telemetryErrors = presetTelemetry ?? await this.#buildTelemetryErrors(loopId, currentTurnSeq);
|
|
962
1016
|
const countTokens = (t) => provider.countTokens(t); // §provider-surface-counttokens
|
|
@@ -970,9 +1024,6 @@ class Engine {
|
|
|
970
1024
|
// omitted, section lines still shown). §tokenomics-render-weight-budget
|
|
971
1025
|
const ceiling = _a.computeCeiling(provider.contextSize, this.#budgetCeiling);
|
|
972
1026
|
const budgetReadout = this.#renderBudget(PacketWire.measureLogBudget(log, countTokens), ceiling);
|
|
973
|
-
// Per-scheme tally (§packet) so the model sees which schemes hold content without
|
|
974
|
-
// probing e.g. FIND(known://**) every turn. "" when empty → the section is omitted.
|
|
975
|
-
const catalogSummary = await this.#db.engine_scheme_catalog_summary.all({ session_id: sessionId });
|
|
976
1027
|
// The default packet: an ordered list of sections, each addressable state
|
|
977
1028
|
// (§packet-construction). `slot` is the prompt-cache boundary; the STATIC
|
|
978
1029
|
// sections (definition, tools) lead the system slot so they form the cached
|
|
@@ -986,13 +1037,12 @@ class Engine {
|
|
|
986
1037
|
{ name: "tools", slot: "system", header: null, content: tools.join("\n"), tokens: 0 }, // titleless — the examples flow on from plurnk.md (definition) directly above
|
|
987
1038
|
{ name: "schemes", slot: "system", header: "Plurnk System Schemes", content: this.#schemes.teach(), tokens: 0 },
|
|
988
1039
|
...(inject !== null ? [{ name: "inject", slot: "system", header: "Plurnk Operator Notes", content: inject, tokens: 0 }] : []),
|
|
989
|
-
{ name: "log", slot: "system", header: "Plurnk System Log", content: PacketWire.renderLog(log), tokens: 0 },
|
|
1040
|
+
{ name: "log", slot: "system", header: "Plurnk System Log", content: PacketWire.renderLog(log, countTokens), tokens: 0 },
|
|
990
1041
|
{ name: "prompt", slot: "user", header: "Plurnk System User Prompt", content: prompt, tokens: 0 },
|
|
991
1042
|
{ name: "budget", slot: "user", header: "Plurnk System Budget", content: budgetReadout, tokens: 0 },
|
|
992
1043
|
{ name: "errors", slot: "user", header: "Plurnk System Errors", content: PacketWire.renderErrors(telemetryErrors), tokens: 0 },
|
|
993
1044
|
{ name: "git", slot: "user", header: "Plurnk System Git Status", content: PacketWire.renderGit(gitStatus), tokens: 0 },
|
|
994
|
-
{ name: "
|
|
995
|
-
{ name: "requirements", slot: "user", header: "Plurnk System Requirements", content: requirementsText, tokens: 0 },
|
|
1045
|
+
{ name: "requirements", slot: "user", header: "Plurnk System Requirements", content: baseRequirements, tokens: 0 },
|
|
996
1046
|
];
|
|
997
1047
|
// Plugin packet control (§packet-construction): trusted schemes rewrite the
|
|
998
1048
|
// default list — add, remove, reorder — in-process, before measurement.
|
|
@@ -1018,20 +1068,22 @@ class Engine {
|
|
|
1018
1068
|
const packetTokens = countTokens(PacketWire.renderSlot(sections, "system")) + countTokens(PacketWire.renderSlot(sections, "user"));
|
|
1019
1069
|
return { tokens: packetTokens, sections, telemetryErrors };
|
|
1020
1070
|
}
|
|
1021
|
-
// Budget readout body, rendered into the
|
|
1071
|
+
// Budget readout body, rendered into the `## Plurnk System Budget` section.
|
|
1022
1072
|
// Headline `ceiling/free` only when a ceiling exists; section lines for the
|
|
1023
1073
|
// curatable index/log weight the model can FOLD back. tokensFree is a
|
|
1024
1074
|
// placeholder here — buildSystem substitutes it after measuring the packet.
|
|
1025
1075
|
#renderBudget(log, ceiling) {
|
|
1026
1076
|
const lines = [];
|
|
1027
1077
|
if (ceiling !== null)
|
|
1028
|
-
lines.push(`
|
|
1078
|
+
lines.push(`Token Ceiling ${ceiling} · Token Usage ${TOKEN_USAGE_PLACEHOLDER} (${TOKEN_PERCENT_PLACEHOLDER}%) · Tokens Free ${TOKENS_FREE_PLACEHOLDER}`);
|
|
1029
1079
|
if (log.entries > 0) {
|
|
1080
|
+
if (lines.length > 0)
|
|
1081
|
+
lines.push("");
|
|
1030
1082
|
lines.push(`Log entries: ${log.entries} entries, ${log.tokens} tokens`);
|
|
1031
1083
|
// Per-turn weight — the grinder's rollback unit, oldest first: the
|
|
1032
1084
|
// model sees what's first to go (§tokenomics {§tokenomics-turn-totals}).
|
|
1033
1085
|
if (log.byTurn.length > 0) {
|
|
1034
|
-
lines.push("Turns:", "| turn | tokens |", "|---|--:|");
|
|
1086
|
+
lines.push("", "Turns:", "| turn | tokens |", "|---|--:|");
|
|
1035
1087
|
for (const t of log.byTurn)
|
|
1036
1088
|
lines.push(`| ${t.turn} | ${t.tokens} |`);
|
|
1037
1089
|
}
|
|
@@ -1040,14 +1092,14 @@ class Engine {
|
|
|
1040
1092
|
// lists log:/// rows (log items), distinct from catalog entries (plurnk.md: "EDIT
|
|
1041
1093
|
// is only for entries. Do not attempt to edit log items.").
|
|
1042
1094
|
if (log.largest.length > 0) {
|
|
1043
|
-
lines.push("Heaviest items:", "| item | tokens |", "|---|--:|");
|
|
1095
|
+
lines.push("", "Heaviest items:", "| item | tokens |", "|---|--:|");
|
|
1044
1096
|
for (const e of log.largest)
|
|
1045
1097
|
lines.push(`| ${e.path} | ${e.tokens} |`);
|
|
1046
1098
|
}
|
|
1047
1099
|
}
|
|
1048
1100
|
return lines.join("\n");
|
|
1049
1101
|
}
|
|
1050
|
-
// The
|
|
1102
|
+
// The ## Plurnk System Tools capability sheet (SPEC §tools). A hook: each enabled
|
|
1051
1103
|
// capability contributes one line, rendered above Requirements so the model sees what
|
|
1052
1104
|
// it can do before the rules. Each available executor tag contributes its self-documenting
|
|
1053
1105
|
// example (plurnk-execs#7), retiring the blind EXEC.
|
|
@@ -1070,7 +1122,7 @@ class Engine {
|
|
|
1070
1122
|
// the fuller doc (materialized at plurnk://docs/<tag>.md) rides an inline link whose
|
|
1071
1123
|
// token cost lives on that manifest entry. No example → no line (like a provisional scheme).
|
|
1072
1124
|
if (entry?.example)
|
|
1073
|
-
tools.push(teachingLine(entry.example
|
|
1125
|
+
tools.push(teachingLine(entry.example));
|
|
1074
1126
|
}
|
|
1075
1127
|
}
|
|
1076
1128
|
return tools;
|
|
@@ -1221,6 +1273,7 @@ class Engine {
|
|
|
1221
1273
|
mimetype_tx: r.mimetype_tx,
|
|
1222
1274
|
folded: r.expanded === 0,
|
|
1223
1275
|
source: r.source,
|
|
1276
|
+
attrs: r.attrs === null ? null : JSON.parse(r.attrs),
|
|
1224
1277
|
}));
|
|
1225
1278
|
}
|
|
1226
1279
|
// §env-delta (§actor-boundary-no-mutex: runs share without locks; a conflict surfaces as a delta, never prevented) — at pre-turn build, surface what changed in the shared world since this
|
|
@@ -1261,6 +1314,37 @@ class Engine {
|
|
|
1261
1314
|
}
|
|
1262
1315
|
return written;
|
|
1263
1316
|
}
|
|
1317
|
+
// §environment-observation — exec streams as an instance of the ambient-observe machine:
|
|
1318
|
+
// each turn, emit each owned channel's unshown byte-delta as a foisted READ@200 row. Folded
|
|
1319
|
+
// while the channel streams; the terminal delta (channel closed) auto-OPENs. The cursor is the
|
|
1320
|
+
// streamEnd recorded on the channel's prior delta — no exec-specific surfacing, just the
|
|
1321
|
+
// env-observe loop with a byte cursor where env-delta uses a timestamp. §exec-stream
|
|
1322
|
+
async #materializeStreamDeltas(args) {
|
|
1323
|
+
const { runId, loopId, turnId, fromSequence } = args;
|
|
1324
|
+
const channels = await this.#db.engine_run_stream_channels.all({ run_id: runId });
|
|
1325
|
+
let written = 0;
|
|
1326
|
+
for (const ch of channels) {
|
|
1327
|
+
const prior = await this.#db.engine_stream_cursor.get({
|
|
1328
|
+
run_id: runId, scheme: ch.runtime, pathname: ch.coord, fragment: ch.channel,
|
|
1329
|
+
});
|
|
1330
|
+
const cursor = prior !== undefined ? (JSON.parse(prior.attrs).streamEnd ?? 0) : 0;
|
|
1331
|
+
if (ch.content.length <= cursor)
|
|
1332
|
+
continue; // nothing new to show this turn
|
|
1333
|
+
const closed = ch.state === "closed" || ch.state === "errored";
|
|
1334
|
+
// startLine continues the line count across turns: a multi-turn stream's deltas number
|
|
1335
|
+
// into one sequence (lines N..M, then M+1..), not N independent "1:" restarts. §exec-stream
|
|
1336
|
+
const startLine = (ch.content.slice(0, cursor).match(/\n/g)?.length ?? 0) + 1;
|
|
1337
|
+
await this.#db.engine_insert_stream_delta.run({
|
|
1338
|
+
run_id: runId, loop_id: loopId, turn_id: turnId, sequence: fromSequence + written,
|
|
1339
|
+
scheme: ch.runtime, pathname: ch.coord, fragment: ch.channel,
|
|
1340
|
+
rx: JSON.stringify({ status: 200, content: ch.content.slice(cursor), mimetype: "text/stream", startLine }),
|
|
1341
|
+
attrs: JSON.stringify({ streamEnd: ch.content.length }),
|
|
1342
|
+
expanded: closed ? 1 : 0, // §exec-stream — terminal delta auto-OPENs; ongoing folds
|
|
1343
|
+
});
|
|
1344
|
+
written++;
|
|
1345
|
+
}
|
|
1346
|
+
return written;
|
|
1347
|
+
}
|
|
1264
1348
|
// §env-delta — the filesystem as an actor. Ambient disk divergences detected at
|
|
1265
1349
|
// pre-turn (git membership re-read) are logged as the plurnk run's source=file EDIT
|
|
1266
1350
|
// "fictions": no op happened, but EDIT is the only grammar the model has for "your
|
|
@@ -1996,12 +2080,12 @@ class Engine {
|
|
|
1996
2080
|
let attrsObj = (result.attrs !== undefined && result.attrs !== null)
|
|
1997
2081
|
? { ...result.attrs }
|
|
1998
2082
|
: {};
|
|
1999
|
-
// EXEC stream entry
|
|
2000
|
-
// <runtime>:///<loop_seq>/<turn_seq>/<sequence> (e.g. sh:///1/1/2)
|
|
2001
|
-
//
|
|
2002
|
-
//
|
|
2003
|
-
// <loop>/<turn>/<seq>, so the
|
|
2004
|
-
// from statement.signal (EXEC's runtime slot)
|
|
2083
|
+
// EXEC produces a stream entry addressed by RUNTIME TAG as authority (§exec): it lives
|
|
2084
|
+
// at <runtime>:///<loop_seq>/<turn_seq>/<sequence> (e.g. sh:///1/1/2). That address is a
|
|
2085
|
+
// SEPARATE `stream` link in attrs — NOT an overload of `target`, which stays faithful to
|
|
2086
|
+
// the EXEC's own slot (the cwd, or the path to the executable). The log:/// coordinate
|
|
2087
|
+
// shares the trailing <loop>/<turn>/<seq>, so the op still correlates to its stream.
|
|
2088
|
+
// Runtime comes from statement.signal (EXEC's runtime slot), resolvable for failed execs
|
|
2005
2089
|
// too; empty/absent = the default shell.
|
|
2006
2090
|
if (statement.op === "EXEC") {
|
|
2007
2091
|
const seqs = await this.#db.engine_loop_turn_seqs.get({
|
|
@@ -2011,9 +2095,8 @@ class Engine {
|
|
|
2011
2095
|
throw new Error(`Engine.#writeLog: loop_turn_seqs returned no row for loop=${loopId} turn=${turnId}`);
|
|
2012
2096
|
const runtime = (typeof statement.signal === "string" && statement.signal.length > 0) ? statement.signal : "sh";
|
|
2013
2097
|
const coordPathname = `/${seqs.loop_seq}/${seqs.turn_seq}/${sequence}`;
|
|
2014
|
-
target.scheme = runtime;
|
|
2015
|
-
target.pathname = coordPathname;
|
|
2016
2098
|
attrsObj.pathname = coordPathname;
|
|
2099
|
+
attrsObj.stream = `${runtime}://${coordPathname}`;
|
|
2017
2100
|
// Mutate the in-memory result.attrs too: the dispatch path
|
|
2018
2101
|
// hands originalResult.attrs to handler.applyResolution after
|
|
2019
2102
|
// proposal accept (see #acceptResolution). Both views — the
|