@plurnk/plurnk-service 0.55.0 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +0 -8
- package/SPEC.md +111 -93
- package/dist/content/matcher.d.ts.map +1 -1
- package/dist/content/matcher.js +62 -89
- package/dist/content/matcher.js.map +1 -1
- package/dist/core/ChannelWrite.d.ts +2 -1
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +2 -2
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +2 -2
- package/dist/core/Engine.d.ts +1 -0
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +115 -50
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +40 -2
- package/dist/core/fork.d.ts.map +1 -1
- package/dist/core/fork.js +20 -4
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +29 -0
- package/dist/core/packet-inject.d.ts +2 -0
- package/dist/core/packet-inject.d.ts.map +1 -1
- package/dist/core/packet-inject.js +28 -1
- package/dist/core/packet-inject.js.map +1 -1
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +22 -2
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/session-settings.d.ts +0 -5
- package/dist/core/session-settings.d.ts.map +1 -1
- package/dist/core/session-settings.js +1 -10
- package/dist/core/session-settings.js.map +1 -1
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +32 -6
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +5 -1
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts +4 -0
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +35 -19
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Log.sql +14 -11
- package/dist/schemes/Run.d.ts +3 -1
- package/dist/schemes/Run.d.ts.map +1 -1
- package/dist/schemes/Run.js +16 -0
- package/dist/schemes/Run.js.map +1 -1
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +17 -3
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-find.sql +23 -0
- package/dist/schemes/_entry-manifest.d.ts +1 -1
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +22 -4
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/exec-abort.d.ts +4 -0
- package/dist/schemes/exec-abort.d.ts.map +1 -1
- package/dist/schemes/exec-abort.js +6 -0
- package/dist/schemes/exec-abort.js.map +1 -1
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +128 -19
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/MethodRegistry.d.ts +1 -0
- package/dist/server/MethodRegistry.d.ts.map +1 -1
- package/dist/server/drain.sql +17 -5
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +0 -9
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/methods/log_read.d.ts.map +1 -1
- package/dist/server/methods/log_read.js +7 -1
- package/dist/server/methods/log_read.js.map +1 -1
- package/dist/server/methods/log_read.sql +17 -7
- package/dist/server/methods/session_create.d.ts.map +1 -1
- package/dist/server/methods/session_create.js +1 -8
- package/dist/server/methods/session_create.js.map +1 -1
- package/docs/run.md +3 -1
- package/migrations/0000-00-00.01_schema.sql +11 -1
- package/package.json +9 -9
- package/requirements.md +4 -0
package/dist/core/Engine.js
CHANGED
|
@@ -9,7 +9,7 @@ import GitState from "./git-state.js";
|
|
|
9
9
|
import Fork from "./fork.js";
|
|
10
10
|
import RunCap from "./run-cap.js";
|
|
11
11
|
import { teachingLine, docsExcludeSet } from "./teaching.js";
|
|
12
|
-
import { readPacketInject } from "./packet-inject.js";
|
|
12
|
+
import { readPacketInject, readSystemPolicy, readProjectPolicy } from "./packet-inject.js";
|
|
13
13
|
import SessionSettings from "./session-settings.js";
|
|
14
14
|
import { decodePathParens } from "./path-decode.js";
|
|
15
15
|
import { DEFAULT_LOOP_FLAGS } from "./scheme-types.js";
|
|
@@ -315,6 +315,9 @@ class Engine {
|
|
|
315
315
|
// #263 — the last turn's prompt tokens = current window occupancy (gauge numerator), NOT the
|
|
316
316
|
// summed promptTokens above, which overcounts a context that grows across turns.
|
|
317
317
|
contextTokens: row?.context ?? 0,
|
|
318
|
+
// #252 — the latest turn's opaque provider blob, parsed for the wire. Empty {} when the
|
|
319
|
+
// provider returned no meta. The service forwards it; it never reads a field within.
|
|
320
|
+
meta: JSON.parse(row?.meta ?? "{}"),
|
|
318
321
|
};
|
|
319
322
|
}
|
|
320
323
|
#pushTelemetry(sessionId, loopId, event) {
|
|
@@ -357,6 +360,26 @@ class Engine {
|
|
|
357
360
|
}
|
|
358
361
|
return slice.join("\n");
|
|
359
362
|
}
|
|
363
|
+
// A @plurnk/gbnf divergence position (providers#24) is a CODE-POINT offset into the
|
|
364
|
+
// model's content; the snippet/telemetry surface speaks 1-based line + 0-based column.
|
|
365
|
+
// Convert over code points (not UTF-16 units) so an astral char doesn't skew the line,
|
|
366
|
+
// clamping out-of-range offsets to the content's end.
|
|
367
|
+
#offsetToLineColumn(content, offset) {
|
|
368
|
+
const cps = Array.from(content);
|
|
369
|
+
const clamped = Math.max(0, Math.min(offset, cps.length));
|
|
370
|
+
let line = 1;
|
|
371
|
+
let column = 0;
|
|
372
|
+
for (let i = 0; i < clamped; i++) {
|
|
373
|
+
if (cps[i] === "\n") {
|
|
374
|
+
line++;
|
|
375
|
+
column = 0;
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
column++;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return { line, column };
|
|
382
|
+
}
|
|
360
383
|
async runLoop({ provider, messages, requirements = "", sessionId, runId, loopId, maxTurns = 50, maxStrikes = readMaxStrikes(), minCycles = readPositiveInt("PLURNK_MIN_CYCLES", DEFAULT_MIN_CYCLES), maxCyclePeriod = readPositiveInt("PLURNK_MAX_CYCLE_PERIOD", DEFAULT_MAX_CYCLE_PERIOD), origin = "model", signal, onDispatch, }) {
|
|
361
384
|
const turnIds = [];
|
|
362
385
|
const suddenDeathThreshold = maxTurns - maxStrikes;
|
|
@@ -611,7 +634,7 @@ class Engine {
|
|
|
611
634
|
// (clamped to the scheme's count so FIND's strict <L> never 416s); off by default.
|
|
612
635
|
if (seq === 1) {
|
|
613
636
|
// #231 — a session's client-chosen manifestItems REPLACES the env default outright.
|
|
614
|
-
const { manifestItems: sessionMI
|
|
637
|
+
const { manifestItems: sessionMI } = await SessionSettings.read(this.#db, sessionId);
|
|
615
638
|
const manifestItems = sessionMI !== null ? normalizeManifestItems(sessionMI) : readManifestItems();
|
|
616
639
|
if (manifestItems !== null && runFirstLoop) { // #269 — catalog preview is run-once
|
|
617
640
|
// engine_scheme_catalog_summary is the scheme source: session-scoped, ordered,
|
|
@@ -656,31 +679,19 @@ class Engine {
|
|
|
656
679
|
});
|
|
657
680
|
nextActionIndex++;
|
|
658
681
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
continue; // absent / non-git session → not a member → skip
|
|
671
|
-
const agentsRead = {
|
|
672
|
-
op: "READ", suffix: "", signal: null, lineMarker: null,
|
|
673
|
-
target: {
|
|
674
|
-
kind: "url", raw: `file://${pathname}`, scheme: "file",
|
|
675
|
-
username: null, password: null, hostname: null, port: null,
|
|
676
|
-
pathname, params: {}, fragment: null,
|
|
677
|
-
},
|
|
678
|
-
body: null, position: { line: 1, column: 1 },
|
|
682
|
+
// §run-scheme — Manifest(run) = session-scope ∪ THIS run's run-scope. Foist the
|
|
683
|
+
// building run's OWN scratch (run:///**, uncapped — a run needs the full view to
|
|
684
|
+
// manage its private workspace) so it's catalogued in ITS perspective alone; other
|
|
685
|
+
// runs reach it only via explicit FIND(run://<name>/**). A run with no scratch foists nothing.
|
|
686
|
+
const selfRun = await this.#db.run_name_by_id.get({ run_id: runId });
|
|
687
|
+
const scratch = selfRun === undefined ? 0 : (await this.#db.engine_run_scratch_count.get({ session_id: sessionId, owner_prefix: `/${selfRun.name}/*` }))?.entries ?? 0;
|
|
688
|
+
if (scratch > 0) {
|
|
689
|
+
const runFind = {
|
|
690
|
+
op: "FIND", suffix: "", signal: null,
|
|
691
|
+
target: { kind: "url", raw: "run:///**", scheme: "run", username: null, password: null, hostname: "", port: null, pathname: "/**", params: {}, fragment: null },
|
|
692
|
+
body: null, lineMarker: null, position: { line: 1, column: 1 },
|
|
679
693
|
};
|
|
680
|
-
await this.dispatch({
|
|
681
|
-
statement: agentsRead, sessionId, runId, loopId, turnId,
|
|
682
|
-
sequence: nextActionIndex, origin: "plurnk", onDispatch,
|
|
683
|
-
});
|
|
694
|
+
await this.dispatch({ statement: runFind, sessionId, runId, loopId, turnId, sequence: nextActionIndex, origin: "plurnk", onDispatch });
|
|
684
695
|
nextActionIndex++;
|
|
685
696
|
}
|
|
686
697
|
}
|
|
@@ -740,17 +751,18 @@ class Engine {
|
|
|
740
751
|
await this.#db.engine_close_turn.run({
|
|
741
752
|
id: turnId, status: 413, packet: JSON.stringify(hardPacket),
|
|
742
753
|
usage_prompt: 0, usage_completion: 0, usage_cached: 0, usage_cost_pico: 0,
|
|
743
|
-
finish_reason: "budget_hard_stop", model: provider.model,
|
|
754
|
+
finish_reason: "budget_hard_stop", model: provider.model, meta: "{}",
|
|
744
755
|
});
|
|
745
756
|
return { turnId, status: 413, statuses: [], fingerprint: "", budgetStruck: enforced.struck, budgetHardStop: true, steerStruck: false };
|
|
746
757
|
}
|
|
747
758
|
const modelMessages = this.#packetToWireMessages(requestPacket);
|
|
748
|
-
//
|
|
749
|
-
//
|
|
750
|
-
//
|
|
751
|
-
//
|
|
752
|
-
|
|
753
|
-
|
|
759
|
+
// No decode cap. Our budget governs the TRANSMISSION packet (the grinder folds
|
|
760
|
+
// the input under the ceiling); the model's decode — reasoning + emission — is
|
|
761
|
+
// out of band, owned by the provider's own context window. Deriving a maxTokens
|
|
762
|
+
// from our budget conflated the two and guillotined a reasoning model's
|
|
763
|
+
// out-of-band thinking as the packet filled (`ceiling - packet` → near-zero
|
|
764
|
+
// decode → finish=length mid-reasoning → no emission → strike spiral). The
|
|
765
|
+
// provider enforces its physical wall on its own.
|
|
754
766
|
let response;
|
|
755
767
|
// #249 — plugin attribution tags onto the per-turn generate() wire. Value is the
|
|
756
768
|
// active-plugin set (placeholder); real per-turn grounding is deferred.
|
|
@@ -761,7 +773,7 @@ class Engine {
|
|
|
761
773
|
// #249 — session-stable frontend id, forwarded as Plurnk-Client by the plurnk provider only.
|
|
762
774
|
const { client } = await SessionSettings.read(this.#db, sessionId);
|
|
763
775
|
try {
|
|
764
|
-
response = await provider.generate({ messages: modelMessages, runId: String(runId), signal, grammar: await this.#grammarConstraint(),
|
|
776
|
+
response = await provider.generate({ messages: modelMessages, runId: String(runId), signal, grammar: await this.#grammarConstraint(), attributions: attributions.length > 0 ? attributions : undefined, client: client ?? undefined }); // §provider-surface-generate §provider-guarantees-single-call §provider-guarantees-signal-wired §attribution-plurnk-namespace-reserved §client-telemetry
|
|
765
777
|
}
|
|
766
778
|
catch (err) {
|
|
767
779
|
// Every provider error surfaces as telemetry (the client/model sees the cause). #256:
|
|
@@ -770,6 +782,9 @@ class Engine {
|
|
|
770
782
|
// accepted: fall through as an empty no-op turn so the strike rail retries. Every other
|
|
771
783
|
// kind (rate_limit, network_failure, unauthorized, …) is terminal — telemetry'd, then
|
|
772
784
|
// propagated to end the loop (rather than only the opaque loop.run rejection).
|
|
785
|
+
// NOTE (providers 0.19.0 / #275): only the CONSTRAINED path still throws grammar_unenforced.
|
|
786
|
+
// In GBNF-filter mode the provider returns the bytes with a grammar_unenforced telemetry
|
|
787
|
+
// event instead — recovered on the success path below (response.telemetry), no empty turn.
|
|
773
788
|
if (err instanceof ProviderError) {
|
|
774
789
|
this.#pushTelemetry(sessionId, loopId, { source: "provider", kind: err.kind, message: err.message });
|
|
775
790
|
if (err.kind !== "grammar_unenforced")
|
|
@@ -801,14 +816,28 @@ class Engine {
|
|
|
801
816
|
// the snippet, the model sees "invalid xpath at 1:0" but can't
|
|
802
817
|
// connect that to what IT wrote — and tends to regenerate the
|
|
803
818
|
// same broken emission. See edit-todo demo for the canonical case.
|
|
804
|
-
|
|
819
|
+
// Parse errors are LOG ITEMS now (§telemetry — one budget surface): each failed-to-parse
|
|
820
|
+
// emission records an actionless `error` row below, after the turn's dispatched ops are
|
|
821
|
+
// sequenced (see the parse-error log write past the dispatch loop). The errors section
|
|
822
|
+
// derives a pointer to it from log≥400, uniform with action_failure.
|
|
823
|
+
// providers#24 / #275: non-fatal provider telemetry on a SUCCESSFUL turn. In GBNF-filter
|
|
824
|
+
// mode the provider no longer THROWS grammar_unenforced — it returns the model's bytes
|
|
825
|
+
// (here, packetAssistant.content) and attaches the conflict as a telemetry event carrying
|
|
826
|
+
// the divergence code-point position. Mirror the parse_error path: forward each event, and
|
|
827
|
+
// when it locates a position, render the model its own emission around that line so it can
|
|
828
|
+
// self-correct — the exact recovery affordance parse errors already get, instead of the
|
|
829
|
+
// empty-turn cascade the old throw produced.
|
|
830
|
+
for (const event of response.telemetry ?? []) {
|
|
831
|
+
const located = typeof event.position === "number"
|
|
832
|
+
? this.#offsetToLineColumn(packetAssistant.content, event.position)
|
|
833
|
+
: null;
|
|
805
834
|
this.#pushTelemetry(sessionId, loopId, {
|
|
806
|
-
source:
|
|
807
|
-
kind:
|
|
808
|
-
message,
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
835
|
+
source: event.source,
|
|
836
|
+
kind: event.kind,
|
|
837
|
+
message: event.message ?? "",
|
|
838
|
+
...(located !== null
|
|
839
|
+
? { position: { type: "content-offset", line: located.line, column: located.column }, snippet: this.#extractSnippet(packetAssistant.content, located.line, 2) }
|
|
840
|
+
: {}),
|
|
812
841
|
});
|
|
813
842
|
}
|
|
814
843
|
const opsCount = packetAssistant.ops.length;
|
|
@@ -822,19 +851,22 @@ class Engine {
|
|
|
822
851
|
// §grinder-strike-coupling): the loop continues, the model sees the steering hint not the strike
|
|
823
852
|
// count, and a non-resolver spins out to the engine's 500.
|
|
824
853
|
let steerStruck = false;
|
|
825
|
-
// Premature terminate: a SEND[200] while the run still holds a live
|
|
826
|
-
//
|
|
827
|
-
//
|
|
854
|
+
// Premature terminate: a SEND[200] while the run still holds a live thing — an open stream/spawn
|
|
855
|
+
// OR a non-terminal child run (§run-lifecycle: children and streams are the same kind of "live
|
|
856
|
+
// thing a run holds"). The model declared done with work running. Downgrade the 200 to 102 so it
|
|
857
|
+
// dispatches as a continue (its body is preserved, not discarded) and steer; the stream's/child's
|
|
858
|
+
// own conclusion (the wake edge) or a KILL is the exit.
|
|
828
859
|
if (sendOp?.signal === 200) {
|
|
829
860
|
const openSubs = await this.#db.find_open_subscriptions_for_run.all({ run_id: runId });
|
|
830
861
|
const execHandler = this.#schemes.get("exec");
|
|
831
|
-
|
|
862
|
+
const liveChild = await this.#db.engine_run_has_live_child.get({ run_id: runId });
|
|
863
|
+
if (openSubs.length > 0 || execHandler?.hasActiveSpawns?.(runId) === true || liveChild !== undefined) {
|
|
832
864
|
sendOp.signal = TURN_STATUS_IMPLICIT_CONTINUE; // 102 — downgraded, no longer a terminal
|
|
833
865
|
steerStruck = true;
|
|
834
866
|
this.#pushTelemetry(sessionId, loopId, {
|
|
835
867
|
source: "engine:rail",
|
|
836
868
|
kind: "premature_terminate",
|
|
837
|
-
message: "Attempted termination with active streams. Terminate with 202 to hibernate until
|
|
869
|
+
message: "Attempted termination with active streams or child runs. Terminate with 202 to hibernate until they complete, KILL(path) with 200 again to clean up, or 499 to fail.",
|
|
838
870
|
});
|
|
839
871
|
}
|
|
840
872
|
}
|
|
@@ -867,6 +899,9 @@ class Engine {
|
|
|
867
899
|
usage_cost_pico: provider.costFor(usage), // §provider-surface-costfor
|
|
868
900
|
finish_reason: finishReason,
|
|
869
901
|
model,
|
|
902
|
+
// #252 — opaque provider→client metadata passthrough (e.g. balancePico the
|
|
903
|
+
// provider normalized). Stored verbatim, unenforced; the service never reads a field.
|
|
904
|
+
meta: JSON.stringify(response.meta ?? {}),
|
|
870
905
|
});
|
|
871
906
|
// Dispatch model ops starting at nextActionIndex (continues the
|
|
872
907
|
// turn's running counter after any pre-model writes).
|
|
@@ -910,6 +945,27 @@ class Engine {
|
|
|
910
945
|
dropped: droppedCount,
|
|
911
946
|
});
|
|
912
947
|
}
|
|
948
|
+
// §telemetry — parse errors as LOG ITEMS: a failed-to-parse emission records an actionless
|
|
949
|
+
// `error` row (status 400, no target, snippet = the foldable body) at the turn's next free
|
|
950
|
+
// sequence (after the dispatched ops). The model folds/kills/recalls it like any log entry,
|
|
951
|
+
// and the errors section derives a pointer (status + coordinate) from log≥400 — one surface.
|
|
952
|
+
let errSeq = nextActionIndex + opsToDispatch.length;
|
|
953
|
+
for (const { message, line, column, source } of parseErrors ?? []) {
|
|
954
|
+
const snippet = this.#extractSnippet(packetAssistant.content, line, 2);
|
|
955
|
+
await this.#db.engine_insert_log_entry.get({
|
|
956
|
+
run_id: runId, loop_id: loopId, turn_id: turnId, sequence: errSeq++,
|
|
957
|
+
origin: "model", source: "grammar", op: "error", suffix: "", signal: null,
|
|
958
|
+
scheme: null, username: null, password: null, hostname: null, port: null,
|
|
959
|
+
pathname: null, params: null, fragment: null, lineMarker: null,
|
|
960
|
+
tx: "", mimetype_tx: "text/plain",
|
|
961
|
+
// `message`/`snippet` render as the foldable body (not a pointer `error` field), so the
|
|
962
|
+
// derived errors-section pointer stays minimal (status + coordinate), the bloated parser
|
|
963
|
+
// message lives in the log row, reclaimable on FOLD.
|
|
964
|
+
rx: JSON.stringify({ message, position: { type: "content-offset", line, column }, snippet, parserSource: source }),
|
|
965
|
+
mimetype_rx: "application/json",
|
|
966
|
+
status_rx: 400, tokens: 0, state: "resolved", outcome: null, attrs: "{}",
|
|
967
|
+
});
|
|
968
|
+
}
|
|
913
969
|
// Zero ops is NOT an error to report — the model knows it emitted
|
|
914
970
|
// nothing. Strike accounting (engine-internal) treats it as a
|
|
915
971
|
// struck turn; the model just sees an empty packet next turn.
|
|
@@ -1032,15 +1088,23 @@ class Engine {
|
|
|
1032
1088
|
// peer sections (unbundled). The budget section carries its {{tokensFree}}
|
|
1033
1089
|
// placeholders here; they resolve below once the assembled total is known.
|
|
1034
1090
|
const inject = await readPacketInject(); // #240 — operator section, per-turn, fail-hard on a broken path
|
|
1091
|
+
const sessionRoot = (await this.#db.envelope_get_session.get({ id: sessionId }))?.project_root ?? null;
|
|
1092
|
+
const systemPolicy = await readSystemPolicy(); // ~/.plurnk/AGENTS.md (or PLURNK_POLICY)
|
|
1093
|
+
const projectPolicy = await readProjectPolicy(sessionRoot); // <projectRoot>/AGENTS.md (or PLURNK_PROJECT)
|
|
1035
1094
|
const defaults = [
|
|
1036
1095
|
{ name: "definition", slot: "system", header: null, content: system_definition, tokens: 0 },
|
|
1037
1096
|
{ name: "tools", slot: "system", header: null, content: tools.join("\n"), tokens: 0 }, // titleless — the examples flow on from plurnk.md (definition) directly above
|
|
1038
1097
|
{ name: "schemes", slot: "system", header: "Plurnk System Schemes", content: this.#schemes.teach(), tokens: 0 },
|
|
1039
1098
|
...(inject !== null ? [{ name: "inject", slot: "system", header: "Plurnk Operator Notes", content: inject, tokens: 0 }] : []),
|
|
1040
|
-
|
|
1099
|
+
// policy: the client's privileged rules — ~/.plurnk/AGENTS.md (system) then <root>/AGENTS.md (project) — below grammar/tools/schemes, above budget-the-law. AGENTS is POLICY here, never a curatable READable entry. Empty content ⇒ section omitted.
|
|
1100
|
+
{ name: "system-policy", slot: "system", header: "Plurnk System Policy", content: systemPolicy ?? "", tokens: 0 },
|
|
1101
|
+
{ name: "project-policy", slot: "system", header: "Project Policy", content: projectPolicy ?? "", tokens: 0 },
|
|
1102
|
+
// budget at the BOTTOM of the SYSTEM slot — budget is LAW (the ceiling is a hard constraint the model must obey), so it belongs in the lean privileged zone, not as user-slot status.
|
|
1103
|
+
{ name: "budget", slot: "system", header: "Plurnk System Budget", content: budgetReadout, tokens: 0 },
|
|
1041
1104
|
{ name: "prompt", slot: "user", header: "Plurnk System User Prompt", content: prompt, tokens: 0 },
|
|
1042
|
-
{ name: "budget", slot: "user", header: "Plurnk System Budget", content: budgetReadout, tokens: 0 },
|
|
1043
1105
|
{ name: "errors", slot: "user", header: "Plurnk System Errors", content: PacketWire.renderErrors(telemetryErrors), tokens: 0 },
|
|
1106
|
+
// log in the USER slot, low: short-term memory (data, not rules) at the action point, so the model consults its history instead of skimming it. git follows it as workspace status — not law, so it stays in user, never system.
|
|
1107
|
+
{ name: "log", slot: "user", header: "Plurnk System Log", content: PacketWire.renderLog(log, countTokens), tokens: 0 },
|
|
1044
1108
|
{ name: "git", slot: "user", header: "Plurnk System Git Status", content: PacketWire.renderGit(gitStatus), tokens: 0 },
|
|
1045
1109
|
{ name: "requirements", slot: "user", header: "Plurnk System Requirements", content: baseRequirements, tokens: 0 },
|
|
1046
1110
|
];
|
|
@@ -1894,8 +1958,9 @@ class Engine {
|
|
|
1894
1958
|
const schemeName = this.#schemeNameOf(path);
|
|
1895
1959
|
if (schemeName === null)
|
|
1896
1960
|
return { status: 400, error: "KILL target must be a URL path with a scheme" };
|
|
1897
|
-
|
|
1898
|
-
|
|
1961
|
+
// KILL on log:/// erases the log row(s) — the model's DB-storage curation lever
|
|
1962
|
+
// (plurnk.md:36, :98), routed to Log.kill below via the killable.kill path. The old
|
|
1963
|
+
// "append-only" 405 forbade what the grammar requires; FOLD only collapses the render.
|
|
1899
1964
|
// Process-KILL: any scheme whose handler exposes kill() aborts a live stream — the
|
|
1900
1965
|
// exec handler, registered as "exec" + under every runtime tag (sh/node), so a tag-
|
|
1901
1966
|
// addressed stream (sh:///l/t/s) routes here, not to deleteEntry. §exec
|