@plurnk/plurnk-service 1.0.5 → 1.0.7
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 +66 -48
- package/INSTALL.md +3 -3
- package/README.md +4 -4
- package/SPEC.md +212 -196
- package/dist/Paths.js +1 -1
- package/dist/Paths.js.map +1 -1
- package/dist/core/ChannelWrite.d.ts +18 -18
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +17 -17
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +11 -11
- package/dist/core/Dispatcher.d.ts +15 -11
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +124 -125
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/Engine.d.ts +20 -23
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +210 -185
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +106 -109
- package/dist/core/PacketBuilder.d.ts +9 -9
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +199 -151
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ProposalLifecycle.d.ts +8 -8
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +10 -10
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts +1 -0
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +9 -3
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +4 -1
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +18 -9
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/TelemetryChannel.d.ts +5 -5
- package/dist/core/TelemetryChannel.d.ts.map +1 -1
- package/dist/core/TelemetryChannel.js +9 -9
- package/dist/core/TelemetryChannel.js.map +1 -1
- package/dist/core/caps/CapsResolve.js +2 -2
- package/dist/core/caps/CapsResolve.js.map +1 -1
- package/dist/core/caps/DbNotifyCaps.js +2 -2
- package/dist/core/caps/DbNotifyCaps.js.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.js +7 -7
- package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts +2 -2
- package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js +4 -4
- package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
- package/dist/core/fork.d.ts +1 -1
- package/dist/core/fork.d.ts.map +1 -1
- package/dist/core/fork.js +31 -26
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +41 -32
- package/dist/core/git-env.d.ts.map +1 -1
- package/dist/core/git-env.js +23 -7
- package/dist/core/git-env.js.map +1 -1
- package/dist/core/git-iso.d.ts +9 -0
- package/dist/core/git-iso.d.ts.map +1 -0
- package/dist/core/git-iso.js +227 -0
- package/dist/core/git-iso.js.map +1 -0
- package/dist/core/git-membership.d.ts +2 -2
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +70 -50
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/git-state.d.ts +1 -1
- package/dist/core/git-state.d.ts.map +1 -1
- package/dist/core/git-state.js +21 -11
- package/dist/core/git-state.js.map +1 -1
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +81 -22
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +2 -2
- package/dist/core/plurnk-uri.d.ts.map +1 -1
- package/dist/core/plurnk-uri.js +19 -12
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/run-ops.sql +26 -26
- package/dist/core/scheme-types.d.ts +6 -6
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/search-gate.js +0 -0
- package/dist/core/search-gate.js.map +1 -1
- package/dist/core/token-ruler.js +2 -2
- package/dist/core/token-ruler.js.map +1 -1
- package/dist/core/worker-cap.d.ts +8 -0
- package/dist/core/worker-cap.d.ts.map +1 -0
- package/dist/core/worker-cap.js +20 -0
- package/dist/core/worker-cap.js.map +1 -0
- package/dist/core/{session-settings.d.ts → workspace-settings.d.ts} +5 -5
- package/dist/core/workspace-settings.d.ts.map +1 -0
- package/dist/core/{session-settings.js → workspace-settings.js} +13 -13
- package/dist/core/workspace-settings.js.map +1 -0
- package/dist/digest/Digest.d.ts +3 -3
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +83 -73
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/digest.sql +18 -18
- package/dist/schemes/Exec.d.ts +9 -2
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +124 -68
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.js +3 -3
- package/dist/schemes/ExecOutputScheme.js.map +1 -1
- package/dist/schemes/File.js +23 -23
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Known.js +5 -5
- package/dist/schemes/Known.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +63 -14
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Log.sql +49 -8
- package/dist/schemes/Plurnk.js +5 -5
- package/dist/schemes/Plurnk.js.map +1 -1
- package/dist/schemes/Skill.js +5 -5
- package/dist/schemes/Skill.js.map +1 -1
- package/dist/schemes/Unknown.js +5 -5
- package/dist/schemes/Unknown.js.map +1 -1
- package/dist/schemes/{Run.d.ts → Worker.d.ts} +2 -2
- package/dist/schemes/Worker.d.ts.map +1 -0
- package/dist/schemes/{Run.js → Worker.js} +67 -67
- package/dist/schemes/Worker.js.map +1 -0
- package/dist/schemes/_entry-crud.js +8 -8
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +29 -29
- package/dist/schemes/_entry-find.d.ts +2 -1
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +31 -19
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-find.sql +9 -9
- package/dist/schemes/_entry-graph.d.ts +2 -2
- package/dist/schemes/_entry-graph.d.ts.map +1 -1
- package/dist/schemes/_entry-graph.js +18 -18
- package/dist/schemes/_entry-graph.js.map +1 -1
- package/dist/schemes/_entry-graph.sql +8 -8
- 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 +72 -28
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts +4 -4
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +25 -25
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-ops.sql +9 -9
- package/dist/schemes/_entry-semantic.d.ts +2 -1
- package/dist/schemes/_entry-semantic.d.ts.map +1 -1
- package/dist/schemes/_entry-semantic.js +23 -4
- package/dist/schemes/_entry-semantic.js.map +1 -1
- package/dist/schemes/_entry-semantic.sql +18 -4
- package/dist/schemes/_entry-send.d.ts +1 -1
- package/dist/schemes/_entry-send.d.ts.map +1 -1
- package/dist/schemes/_entry-send.js +6 -6
- package/dist/schemes/_entry-send.js.map +1 -1
- package/dist/schemes/exec-abort.js +1 -1
- package/dist/schemes/exec-abort.js.map +1 -1
- package/dist/server/Daemon.d.ts +48 -48
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +295 -292
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/client-input.js +20 -20
- package/dist/server/client-input.js.map +1 -1
- package/dist/server/dispatch-as-plurnk.d.ts +1 -1
- package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
- package/dist/server/dispatch-as-plurnk.js +7 -7
- package/dist/server/dispatch-as-plurnk.js.map +1 -1
- package/dist/server/drain.sql +27 -27
- package/dist/server/envelope.d.ts +20 -20
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +86 -86
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/envelope.sql +39 -39
- package/dist/server/logEntry.d.ts +1 -1
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +1 -1
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/loopDocs.d.ts +1 -1
- package/dist/server/loopDocs.d.ts.map +1 -1
- package/dist/server/loopDocs.js +6 -6
- package/dist/server/loopDocs.js.map +1 -1
- package/dist/server/seam-entry-read.sql +2 -2
- package/dist/server/seam-log-read.sql +3 -3
- package/dist/server/seam-loop.sql +3 -3
- package/dist/server/seam-proposal-list.sql +4 -4
- package/dist/server/yolo.js +2 -2
- package/dist/server/yolo.js.map +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +22 -7
- package/dist/service.js.map +1 -1
- package/migrations/0000-00-00.01_schema.sql +82 -70
- package/package.json +18 -15
- package/dist/core/run-cap.d.ts +0 -8
- package/dist/core/run-cap.d.ts.map +0 -1
- package/dist/core/run-cap.js +0 -20
- package/dist/core/run-cap.js.map +0 -1
- package/dist/core/search-prefetch.d.ts +0 -17
- package/dist/core/search-prefetch.d.ts.map +0 -1
- package/dist/core/search-prefetch.js +0 -164
- package/dist/core/search-prefetch.js.map +0 -1
- package/dist/core/session-settings.d.ts.map +0 -1
- package/dist/core/session-settings.js.map +0 -1
- package/dist/schemes/Run.d.ts.map +0 -1
- package/dist/schemes/Run.js.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
var _a;
|
|
2
2
|
import { renderAddress, promptLoopPrefix } from "./plurnk-uri.js";
|
|
3
3
|
import { rulerCount } from "./token-ruler.js";
|
|
4
|
-
import {
|
|
4
|
+
import { docsExcludeSet } from "./teaching.js";
|
|
5
5
|
import { Policy } from "@plurnk/plurnk-execs";
|
|
6
|
-
import
|
|
6
|
+
import WorkspaceSettings from "./workspace-settings.js";
|
|
7
7
|
import { DEFAULT_LOOP_FLAGS } from "./scheme-types.js";
|
|
8
8
|
import { readPacketInject, readSystemPolicy, readProjectPolicy } from "./packet-inject.js";
|
|
9
9
|
import { readFile } from "node:fs/promises";
|
|
@@ -20,6 +20,7 @@ import ProviderInstantiate from "./ProviderInstantiate.js";
|
|
|
20
20
|
const TOKENS_FREE_PLACEHOLDER = "{{tokensFree}}";
|
|
21
21
|
const TOKEN_USAGE_PLACEHOLDER = "{{tokenUsage}}";
|
|
22
22
|
const TOKEN_PERCENT_PLACEHOLDER = "{{tokenPercent}}";
|
|
23
|
+
const SYSTEM_CTX_PLACEHOLDER = "{{systemCtx}}"; // #440 — treemap's non-turn overhead = total − Σturns, known only post-assembly
|
|
23
24
|
// §tokenomics-window-partition — the four partition numbers. REQUIRED (fail-hard, the
|
|
24
25
|
// providers-env convention): the ceiling is DERIVED from these, never set directly
|
|
25
26
|
// (PLURNK_BUDGET_CEILING is retired — a settable ceiling let policy contradict physics).
|
|
@@ -46,12 +47,9 @@ class PacketBuilder {
|
|
|
46
47
|
this.#schemes = schemes;
|
|
47
48
|
this.#telemetry = telemetry;
|
|
48
49
|
this.#executors = executors;
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
// resolve fresh at call time.
|
|
53
|
-
const bootAlias = resolveActiveAlias(process.env)?.alias ?? "";
|
|
54
|
-
this.#partitions.set(bootAlias, this.#resolvePartition(bootAlias));
|
|
50
|
+
// #507 — the envelope rides the provider; construction only runs the retired-knob shed
|
|
51
|
+
// so a stale operator .env fails at BOOT, not first use.
|
|
52
|
+
this.#safetyFor(resolveActiveAlias(process.env)?.alias ?? "");
|
|
55
53
|
}
|
|
56
54
|
// #352 — the generation envelope is PER-ALIAS: gemma keeps its measured llama-server policy
|
|
57
55
|
// envelope (n_predict honored to the context wall — the cap MUST bound it, providers#10);
|
|
@@ -59,66 +57,52 @@ class PacketBuilder {
|
|
|
59
57
|
// scopeEnvToAlias resolves PLURNK_SERVICE_*_<alias> over the bare fallback with providers' own
|
|
60
58
|
// battle-tested suffix parser. Cached per alias; the boot-global case falls back to the active
|
|
61
59
|
// alias when a provider carries no side-table entry (a test Mock).
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!key.startsWith(knob + "_"))
|
|
83
|
-
continue;
|
|
84
|
-
if (_a.#KNOBS.includes(key))
|
|
85
|
-
continue;
|
|
86
|
-
if (key.slice(knob.length + 1).toLowerCase() === folded && (process.env[key] ?? "").length > 0)
|
|
87
|
-
return true;
|
|
60
|
+
// #507 (owner-ruled full migration): the generation envelope is PROVIDER-owned — the window
|
|
61
|
+
// and both reserves ride the Provider surface (contextWindow/reasoningReserve/completionReserve,
|
|
62
|
+
// ingested or PLURNK_PROVIDERS_*-pinned in the provider tier). Core keeps ONE knob: SAFETY,
|
|
63
|
+
// the ruler's own packing margin — a service fact, not a model fact.
|
|
64
|
+
static #KNOBS = ["PLURNK_SERVICE_SAFETY"];
|
|
65
|
+
#shedChecked = false;
|
|
66
|
+
#safetyFor(alias) {
|
|
67
|
+
// #507 hard shed (the #472 pattern): the three misprefixed partition knobs moved to the
|
|
68
|
+
// provider tier; a stale operator .env must never silently lose its envelope to the move.
|
|
69
|
+
if (!this.#shedChecked) {
|
|
70
|
+
this.#shedChecked = true;
|
|
71
|
+
const MOVED = {
|
|
72
|
+
CTX: "PLURNK_PROVIDERS_CONTEXT_WINDOW", CONTEXT_WINDOW: "PLURNK_PROVIDERS_CONTEXT_WINDOW",
|
|
73
|
+
REASONING: "PLURNK_PROVIDERS_REASONING_RESERVE",
|
|
74
|
+
ASSISTANT: "PLURNK_PROVIDERS_COMPLETION_RESERVE", COMPLETION: "PLURNK_PROVIDERS_COMPLETION_RESERVE",
|
|
75
|
+
};
|
|
76
|
+
for (const k of Object.keys(process.env)) {
|
|
77
|
+
const m = /^PLURNK_SERVICE_(CTX|CONTEXT_WINDOW|REASONING|ASSISTANT|COMPLETION)(_.*)?$/.exec(k);
|
|
78
|
+
if (m !== null)
|
|
79
|
+
throw new Error(`${k} is retired (#507): the envelope is provider-owned — the knob is ${MOVED[m[1]]}${m[2] ?? ""}.`);
|
|
88
80
|
}
|
|
89
81
|
}
|
|
90
|
-
|
|
82
|
+
const view = scopeEnvToAlias(process.env, alias, _a.#KNOBS);
|
|
83
|
+
return readPartitionIntFrom(view, "PLURNK_SERVICE_SAFETY", 0);
|
|
84
|
+
}
|
|
85
|
+
// #421 — §tokenomics-window-unpollable-deliberate: provider.contextWindow null (env/probe/catalog
|
|
86
|
+
// all missed, provider-tier pins included) is genuinely-unknown — nobody chose an envelope. The
|
|
87
|
+
// budget/ceiling short-circuit to NO-CAP rather than substitute a stand-in the operator never chose.
|
|
88
|
+
#isUnboundedWindow(provider) {
|
|
89
|
+
return provider.contextWindow === null;
|
|
91
90
|
}
|
|
92
91
|
#partitionFor(provider) {
|
|
93
92
|
const alias = ProviderInstantiate.aliasOf(provider) ?? resolveActiveAlias(process.env)?.alias ?? "";
|
|
94
|
-
|
|
95
|
-
// only honest when the CTX is the operator's DELIBERATE policy for THIS alias. An unpollable
|
|
96
|
-
// window (provider.contextSize null — the provider's machine signal after env/probe/catalog
|
|
97
|
-
// all miss) riding BARE partition numbers is nobody's policy: numbers tuned for some other
|
|
98
|
-
// model silently applied, surfacing later as truncation/overflow telemetry (#352's failure
|
|
99
|
-
// mode recurring for every unpollable model). Fail loud at first use, naming both remedies.
|
|
100
|
-
if (provider.contextSize === null && !_a.#hasAliasKnob(alias)) {
|
|
101
|
-
// BEFORE the cache lookup — the constructor pre-primes the boot alias's partition, and a
|
|
102
|
-
// cache hit must never bypass this refusal (the check keys on the PROVIDER's null window,
|
|
103
|
-
// which the cached partition knows nothing about).
|
|
104
|
-
throw new Error(`window unpollable for alias '${alias || "<active>"}' and its partition rides bare numbers — nobody chose this envelope for this model. Either set PLURNK_PROVIDERS_CONTEXT_SIZE_${alias || "<alias>"} (so the window is known) or set the per-alias partition (PLURNK_SERVICE_{CTX,REASONING,ASSISTANT,SAFETY}_${alias || "<alias>"}) so the numbers are deliberate.`);
|
|
105
|
-
}
|
|
106
|
-
const hit = this.#partitions.get(alias);
|
|
107
|
-
if (hit !== undefined)
|
|
108
|
-
return hit;
|
|
109
|
-
const part = this.#resolvePartition(alias);
|
|
110
|
-
this.#partitions.set(alias, part);
|
|
111
|
-
return part;
|
|
93
|
+
return { reasoning: provider.reasoningReserve ?? null, completion: provider.completionReserve ?? null, safety: this.#safetyFor(alias) };
|
|
112
94
|
}
|
|
113
|
-
// The generation envelope — REASONING +
|
|
95
|
+
// The generation envelope — REASONING + COMPLETION, one undifferentiated pool, passed on
|
|
114
96
|
// every generate({maxTokens}): no decode is unbounded (§tokenomics-window-partition). Per
|
|
115
97
|
// alias (#352): gemma's measured envelope; a cloud alias's generous default the backend clamps.
|
|
116
|
-
|
|
117
|
-
const { reasoning,
|
|
118
|
-
|
|
98
|
+
maxTokensFor(provider) {
|
|
99
|
+
const { reasoning, completion } = this.#partitionFor(provider);
|
|
100
|
+
if (reasoning === null || completion === null)
|
|
101
|
+
return null; // #421 — unknown envelope, no cap; the backend clamps
|
|
102
|
+
return reasoning + completion;
|
|
119
103
|
}
|
|
120
104
|
// §tokenomics-window-partition — the prompt ceiling
|
|
121
|
-
// is DERIVED, never set: effectiveWindow = min(
|
|
105
|
+
// is DERIVED, never set: effectiveWindow = min(CONTEXT_WINDOW, provider window; CONTEXT_WINDOW alone when the
|
|
122
106
|
// provider reports none) minus the reserves, divided by the loop's observed real/measured
|
|
123
107
|
// token ratio (usage.prompt is ground truth; a heuristic ruler shipped a 65k-real packet into
|
|
124
108
|
// a 49k window, #311). A fractional ceiling also budgeted the prompt against the window and
|
|
@@ -129,9 +113,12 @@ class PacketBuilder {
|
|
|
129
113
|
// space (usage.prompt, the numerator, is real; the calibration ratio maps measured→real and
|
|
130
114
|
// has no business here). The raw n_ctx overstates usable room by the reserve total.
|
|
131
115
|
promptBudgetFor(provider) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
116
|
+
if (this.#isUnboundedWindow(provider) || provider.contextWindow === null)
|
|
117
|
+
return null; // #421 — no cap: an unknown window has no denominator
|
|
118
|
+
const { reasoning, completion, safety } = this.#partitionFor(provider);
|
|
119
|
+
if (reasoning === null || completion === null)
|
|
120
|
+
return null; // #421 — unknown reserves: no denominator either
|
|
121
|
+
return Math.max(0, provider.contextWindow - reasoning - completion - safety);
|
|
135
122
|
}
|
|
136
123
|
// §tokenomics-agnostic-ruler — the ceiling is the real window partition (window − reserves),
|
|
137
124
|
// NO calibration ratio: the model-facing measure is the chars/2 ruler (an over-count for
|
|
@@ -139,12 +126,17 @@ class PacketBuilder {
|
|
|
139
126
|
// bias — the model curates against less room than it has and never overflows for typical
|
|
140
127
|
// content; the exact provider count guards the pathological tail at the materialization gate.
|
|
141
128
|
ceilingFor(provider) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
129
|
+
if (this.#isUnboundedWindow(provider) || provider.contextWindow === null)
|
|
130
|
+
return null; // #421 — no cap: the gauge headline is omitted
|
|
131
|
+
const { reasoning, completion, safety } = this.#partitionFor(provider);
|
|
132
|
+
if (reasoning === null || completion === null)
|
|
133
|
+
return null; // #421 — unknown reserves, no ceiling
|
|
134
|
+
const promptBudget = provider.contextWindow - reasoning - completion - safety;
|
|
145
135
|
if (promptBudget <= 0) {
|
|
146
136
|
const alias = ProviderInstantiate.aliasOf(provider) ?? resolveActiveAlias(process.env)?.alias ?? "";
|
|
147
|
-
|
|
137
|
+
// #507 — post-migration this contradiction has ONE cause: pinned absolute reserves
|
|
138
|
+
// exceeding the window the provider detected (percent reserves derive and cannot contradict).
|
|
139
|
+
throw new Error(`window partition contradiction for alias '${alias}': window ${provider.contextWindow} <= reserves ${reasoning}+${completion}+${safety}. Pinned PLURNK_PROVIDERS_{REASONING,COMPLETION}_RESERVE absolutes exceed the detected window — repin them under it, or use percent reserves, which derive from the window.`);
|
|
148
140
|
}
|
|
149
141
|
return promptBudget;
|
|
150
142
|
}
|
|
@@ -152,7 +144,7 @@ class PacketBuilder {
|
|
|
152
144
|
// and §user) BEFORE the provider call. The same packet object is then
|
|
153
145
|
// completed with assistant + assistantRaw after the model responds, so
|
|
154
146
|
// the stored packet and the wire payload share one source of truth.
|
|
155
|
-
async buildRequestPacket({ initialMessages, requirements,
|
|
147
|
+
async buildRequestPacket({ initialMessages, requirements, workspaceId, workerId, loopId, currentTurnSeq, provider, gitStatus, telemetryErrors: presetTelemetry, }) {
|
|
156
148
|
const byRole = (role) => initialMessages.filter((m) => m.role === role).map((m) => m.content).join("\n\n");
|
|
157
149
|
// plurnk.md (grammar/dialects) ONLY — the definition is the hot-path grammar.
|
|
158
150
|
// The scheme catalogue is its own `schemes` section below tools (§schemes-directory),
|
|
@@ -165,7 +157,7 @@ class PacketBuilder {
|
|
|
165
157
|
// the runLoop caller's messages.user for tests that bypass the
|
|
166
158
|
// foist mechanism entirely.
|
|
167
159
|
const loopSeqRow = await this.#db.engine_loop_sequence.get({ loop_id: loopId });
|
|
168
|
-
const promptRows = (await this.#db.drain_get_all_prompt_bodies_for_loop.all({ pattern: `${promptLoopPrefix(
|
|
160
|
+
const promptRows = (await this.#db.drain_get_all_prompt_bodies_for_loop.all({ pattern: `${promptLoopPrefix(workerId, loopSeqRow?.sequence ?? loopId)}%` }))
|
|
169
161
|
.filter((r) => typeof r.content === "string" && r.content.length > 0);
|
|
170
162
|
// §prompt-auto-read (owner): the section is a PATHS list (the errors shape — no bodies);
|
|
171
163
|
// each prompt's content reaches the model through its foisted auto-READ in the log, and
|
|
@@ -184,14 +176,14 @@ class PacketBuilder {
|
|
|
184
176
|
// leads requirements.md, so a third copy here was pure duplication in the model's packet. PLAN
|
|
185
177
|
// is mandated unconditionally by plurnk.md §Imperatives (grammar 0.70 requires every turn to
|
|
186
178
|
// lead with PLAN), so the service injects no separate plan directive either.
|
|
187
|
-
const log = await this.#buildLog(
|
|
179
|
+
const log = await this.#buildLog(workerId);
|
|
188
180
|
const telemetryErrors = presetTelemetry ?? await this.buildTelemetryErrors(loopId, currentTurnSeq);
|
|
189
181
|
const countTokens = rulerCount; // §tokenomics-agnostic-ruler — the ONE model-facing ruler (chars/2), not the provider
|
|
190
|
-
// #367 — the capability sheet must reflect the LOOP MODE, not just
|
|
182
|
+
// #367 — the capability sheet must reflect the LOOP MODE, not just workspace-enablement: an
|
|
191
183
|
// ask-mode loop advertises only what its dispatch gate (resolveForLoop) will accept, so the
|
|
192
184
|
// model is never taught a tag it'll then be 403'd on (the taught→emitted→rejected→508 spiral).
|
|
193
185
|
const activeSchemes = this.#schemes.resolveForLoop(await this.#loadLoopFlags(loopId));
|
|
194
|
-
const tools = this.#collectTools(await this.#
|
|
186
|
+
const tools = this.#collectTools(await this.#workspaceEnabled(workspaceId), await WorkspaceSettings.questionsEnabled(this.#db, workspaceId), activeSchemes);
|
|
195
187
|
// Budget readout (SPEC.md §tokenomics). Two-pass: render the budget from
|
|
196
188
|
// the structured log's subtotals with a {{tokensFree}} placeholder, build
|
|
197
189
|
// the section list, measure the assembled total, resolve free, substitute.
|
|
@@ -200,7 +192,8 @@ class PacketBuilder {
|
|
|
200
192
|
// PLURNK_BUDGET_CEILING (null when no window is reported → headline
|
|
201
193
|
// omitted, section lines still shown). §tokenomics-render-weight-budget
|
|
202
194
|
const ceiling = this.ceilingFor(provider);
|
|
203
|
-
const
|
|
195
|
+
const logBudget = PacketWire.measureLogBudget(log, countTokens);
|
|
196
|
+
const budgetReadout = this.#renderBudget(logBudget, ceiling);
|
|
204
197
|
// The default packet: an ordered list of addressable sections (§packet-assembly).
|
|
205
198
|
// `slot` is a TRUST boundary (and the prompt-cache boundary): system holds only
|
|
206
199
|
// framework-authored, non-injectable sections — the static head (definition/tools/
|
|
@@ -209,24 +202,24 @@ class PacketBuilder {
|
|
|
209
202
|
// the requirements footer. The budget section carries its {{tokensFree}} placeholders
|
|
210
203
|
// here; they resolve below once the assembled total is known.
|
|
211
204
|
const inject = await readPacketInject(); // #240 — operator section, per-turn, fail-hard on a broken path
|
|
212
|
-
const
|
|
205
|
+
const workspaceRoot = (await this.#db.envelope_get_workspace.get({ id: workspaceId }))?.project_root ?? null;
|
|
213
206
|
const systemPolicy = await readSystemPolicy(); // ~/.plurnk/AGENTS.md (or PLURNK_SERVICE_POLICY)
|
|
214
|
-
const projectPolicy = await readProjectPolicy(
|
|
207
|
+
const projectPolicy = await readProjectPolicy(workspaceRoot); // <projectRoot>/AGENTS.md (or PLURNK_SERVICE_PROJECT)
|
|
215
208
|
// Child-orientation (§child-orientation): the live things THIS run holds — open streams +
|
|
216
|
-
// unconcluded child
|
|
209
|
+
// unconcluded child workers — surfaced every turn as terse `* <status> <path>` pointers (same shape
|
|
217
210
|
// as errors) just above the errors section. Orienting STATE so the model never loses track of
|
|
218
211
|
// what it's holding (the premature-terminate trap), never advice on what to do. Empty → omitted.
|
|
219
|
-
const childStreams = (await this.#db.engine_child_streams_open.all({
|
|
212
|
+
const childStreams = (await this.#db.engine_child_streams_open.all({ worker_id: workerId }))
|
|
220
213
|
.map((s) => ({ status: "active", path: renderAddress(s.scheme, s.pathname) }));
|
|
221
|
-
const
|
|
222
|
-
.map((r) => ({ status: r.status, path: `
|
|
214
|
+
const childWorkers = (await this.#db.engine_child_workers_live.all({ worker_id: workerId }))
|
|
215
|
+
.map((r) => ({ status: r.status, path: `worker://${r.name}` }));
|
|
223
216
|
const defaults = [
|
|
224
217
|
{ name: "definition", slot: "system", header: null, content: system_definition, tokens: 0 },
|
|
225
|
-
{ name: "tools", slot: "system", header: null, content: tools
|
|
226
|
-
{ name: "schemes", slot: "system", header: "
|
|
227
|
-
...(inject !== null ? [{ name: "inject", slot: "system", header: "
|
|
218
|
+
{ name: "tools", slot: "system", header: null, content: tools, tokens: 0 }, // titleless — the fenced op catalog flows on from plurnk.md (definition) directly above
|
|
219
|
+
{ name: "schemes", slot: "system", header: "Schemes", content: this.#schemes.teach(), tokens: 0 },
|
|
220
|
+
...(inject !== null ? [{ name: "inject", slot: "system", header: "Operator Notes", content: inject, tokens: 0 }] : []),
|
|
228
221
|
// 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.
|
|
229
|
-
{ name: "system-policy", slot: "system", header: "
|
|
222
|
+
{ name: "system-policy", slot: "system", header: "Policy", content: systemPolicy ?? "", tokens: 0 },
|
|
230
223
|
{ name: "project-policy", slot: "system", header: "Project Policy", content: projectPolicy ?? "", tokens: 0 },
|
|
231
224
|
// The packet split is a TRUST boundary: system carries only framework-authored, non-injectable
|
|
232
225
|
// sections; anything that could carry attacker-reachable text (a READ result, exec output, the
|
|
@@ -235,19 +228,19 @@ class PacketBuilder {
|
|
|
235
228
|
// neither is an injection surface; both sit at the bottom of system, just above budget-the-law.
|
|
236
229
|
// child-orientation: what THIS run holds live — streams then runs — just above errors. Terse
|
|
237
230
|
// pointers (the path is the actionable address the model READs/OPENs/KILLs), never advice. §child-orientation
|
|
238
|
-
{ name: "child-streams", slot: "system", header: "
|
|
239
|
-
{ name: "child-
|
|
240
|
-
{ name: "errors", slot: "system", header: "
|
|
241
|
-
{ name: "git", slot: "system", header: "
|
|
231
|
+
{ name: "child-streams", slot: "system", header: "Child Streams", content: PacketWire.renderChildPointers(childStreams), tokens: 0 },
|
|
232
|
+
{ name: "child-workers", slot: "system", header: "Active Child Workers", content: PacketWire.renderChildPointers(childWorkers), tokens: 0 },
|
|
233
|
+
{ name: "errors", slot: "system", header: "Errors", content: PacketWire.renderErrors(telemetryErrors), tokens: 0 },
|
|
234
|
+
{ name: "git", slot: "system", header: "Git Status", content: PacketWire.renderGit(gitStatus), tokens: 0 },
|
|
242
235
|
// budget — LAW (a hard ceiling the model must obey).
|
|
243
|
-
{ name: "budget", slot: "system", header: "
|
|
236
|
+
{ name: "budget", slot: "system", header: "Budget", content: budgetReadout, tokens: 0 },
|
|
244
237
|
// §prompt-auto-read (owner): the prompts section is the system slot's very bottom —
|
|
245
238
|
// a paths-only list (the errors shape); bodies arrive via the foisted auto-READ.
|
|
246
|
-
{ name: "prompt", slot: "system", header: "
|
|
239
|
+
{ name: "prompt", slot: "system", header: "User Prompts", content: prompt, tokens: 0 },
|
|
247
240
|
// log in the user slot: injectable content (READ results, exec output, the model's own mirror) — data, never rules — kept at the action point so the model consults its history.
|
|
248
|
-
{ name: "log", slot: "user", header: "
|
|
241
|
+
{ name: "log", slot: "user", header: "Log", content: PacketWire.renderLog(log, countTokens), tokens: 0 },
|
|
249
242
|
// requirements renders LAST — the user-slot footer, the syntax contract closest to the model's turn (a recency carve-out for weak models).
|
|
250
|
-
{ name: "requirements", slot: "user", header: "
|
|
243
|
+
{ name: "requirements", slot: "user", header: "Recap", content: baseRequirements, tokens: 0 },
|
|
251
244
|
];
|
|
252
245
|
// Plugin packet control (§packet-assembly): trusted schemes rewrite the
|
|
253
246
|
// default list — add, remove, reorder — in-process, before measurement.
|
|
@@ -257,14 +250,18 @@ class PacketBuilder {
|
|
|
257
250
|
let total = countTokens(PacketWire.renderSlot(sections, "system")) + countTokens(PacketWire.renderSlot(sections, "user"));
|
|
258
251
|
{
|
|
259
252
|
const budgetSec = sections.find((s) => s.name === "budget"); // a plugin may have removed it
|
|
260
|
-
|
|
253
|
+
// A null ceiling (#421 — unbounded window) has no headline to calibrate: no truncation, no
|
|
254
|
+
// percent/free substitution. #renderBudget already omitted the headline, so nothing to do.
|
|
255
|
+
if (budgetSec && ceiling !== null) {
|
|
261
256
|
// Curation pressure gates on OCCUPANCY (§tokenomics-pressure-gates-on-occupancy, #308):
|
|
262
257
|
// the Turns/Heaviest tables are a standing FOLD-target list, and a high-headroom model
|
|
263
258
|
// reads them as a todo — burning turns on token hygiene at 3% of a 64k window. Under
|
|
264
259
|
// half-full, the headline's numbers stand alone (truncate at the first blank line) and
|
|
265
260
|
// the total RE-measures — the substituted figures must reconcile with what ships.
|
|
266
261
|
// A null ceiling can't calibrate, so the full readout stays.
|
|
267
|
-
|
|
262
|
+
// A mermaid budget (#440) self-scales to pressure, so it is never truncated — the calm
|
|
263
|
+
// low-usage view IS the point; only the tabular readout collapses under half-full.
|
|
264
|
+
if (!budgetSec.content.includes("```mermaid") && (total / ceiling) * 100 < 50) {
|
|
268
265
|
const cut = budgetSec.content.indexOf("\n\n");
|
|
269
266
|
if (cut !== -1) {
|
|
270
267
|
budgetSec.content = budgetSec.content.slice(0, cut);
|
|
@@ -273,12 +270,17 @@ class PacketBuilder {
|
|
|
273
270
|
}
|
|
274
271
|
const tokensFree = Math.max(0, ceiling - total); // free floors at 0 on overshoot — §tokenomics-over-budget-floor
|
|
275
272
|
const percent = (total / ceiling) * 100; // usage as % of the ceiling — §tokenomics-context-percent
|
|
273
|
+
const sumTurns = logBudget.byTurn.reduce((s, t) => s + t.tokens, 0); // #440 — treemap non-turn box = total − Σturns
|
|
274
|
+
const systemCtx = Math.max(0, total - sumTurns);
|
|
275
|
+
// replaceAll: a mermaid budget recurs free/used across the headline + treemap + pie, so a
|
|
276
|
+
// single .replace would leave the diagrams carrying literal {{…}} placeholders.
|
|
276
277
|
budgetSec.content = budgetSec.content
|
|
277
|
-
.
|
|
278
|
+
.replaceAll(TOKEN_USAGE_PLACEHOLDER, String(total))
|
|
278
279
|
// Any nonzero usage under 1% is "<1" — Math.round alone claimed "1%" from 0.51%,
|
|
279
280
|
// overstating a near-empty window.
|
|
280
|
-
.
|
|
281
|
-
.
|
|
281
|
+
.replaceAll(TOKEN_PERCENT_PLACEHOLDER, total > 0 && percent < 1 ? "<1" : String(Math.round(percent)))
|
|
282
|
+
.replaceAll(TOKENS_FREE_PLACEHOLDER, String(tokensFree))
|
|
283
|
+
.replaceAll(SYSTEM_CTX_PLACEHOLDER, String(systemCtx));
|
|
282
284
|
}
|
|
283
285
|
}
|
|
284
286
|
// Pass 2: per-section render-weight + the assembled packet total (post
|
|
@@ -294,7 +296,23 @@ class PacketBuilder {
|
|
|
294
296
|
// placeholder here — buildRequestPacket substitutes it after measuring the packet.
|
|
295
297
|
#renderBudget(log, ceiling) {
|
|
296
298
|
const lines = [];
|
|
297
|
-
|
|
299
|
+
// #421 — no ceiling (unbounded window): omit the headline entirely; the section lines below
|
|
300
|
+
// stay so the model keeps its FOLD-target surface, just with no percent it can't compute.
|
|
301
|
+
if (ceiling !== null)
|
|
302
|
+
lines.push(`Token Ceiling ${ceiling} · Token Usage ${TOKEN_USAGE_PLACEHOLDER} (${TOKEN_PERCENT_PLACEHOLDER}%) · Tokens Free ${TOKENS_FREE_PLACEHOLDER}`);
|
|
303
|
+
// #440 {§budget-mermaid} — the enriched visual Budget (default on). With a ceiling to scale
|
|
304
|
+
// against, the treemap REPLACES the Turns table (per-turn composition) + a pie gauge; the
|
|
305
|
+
// heaviest-items list stays a table (#450). Self-scaled to pressure (calm→urgent), never <50%-truncated.
|
|
306
|
+
// Set PLURNK_SERVICE_BUDGET_MERMAID=off to A/B against the tabular baseline (#440's before/after).
|
|
307
|
+
if (process.env.PLURNK_SERVICE_BUDGET_MERMAID !== "off" && ceiling !== null && log.entries > 0) {
|
|
308
|
+
if (lines.length > 0)
|
|
309
|
+
lines.push("");
|
|
310
|
+
lines.push(_a.#renderBudgetMermaid(log, ceiling));
|
|
311
|
+
// #450 — the heaviest items stay a plain ranked list (a ranking isn't a composition, no
|
|
312
|
+
// treemap; two mermaid diagrams are enough visual examples) — the same table as the tabular budget.
|
|
313
|
+
lines.push(..._a.#heaviestItemsLines(log.largest));
|
|
314
|
+
return lines.join("\n");
|
|
315
|
+
}
|
|
298
316
|
if (log.entries > 0) {
|
|
299
317
|
if (lines.length > 0)
|
|
300
318
|
lines.push("");
|
|
@@ -306,23 +324,54 @@ class PacketBuilder {
|
|
|
306
324
|
for (const t of log.byTurn)
|
|
307
325
|
lines.push(`| ${t.turn} | ${t.tokens} |`);
|
|
308
326
|
}
|
|
309
|
-
|
|
310
|
-
// (§tokenomics {§tokenomics-largest-entries}). "items", not "entries": the readout
|
|
311
|
-
// lists log:/// rows (log items), distinct from catalog entries (plurnk.md: "EDIT
|
|
312
|
-
// is only for entries. Do not attempt to edit log items.").
|
|
313
|
-
if (log.largest.length > 0) {
|
|
314
|
-
lines.push("", "Heaviest items (FOLD targets — folding reclaims their tokens):", "| item | tokens |", "|---|--:|");
|
|
315
|
-
for (const e of log.largest)
|
|
316
|
-
lines.push(`| ${e.path} | ${e.tokens} |`);
|
|
317
|
-
}
|
|
327
|
+
lines.push(..._a.#heaviestItemsLines(log.largest));
|
|
318
328
|
}
|
|
319
329
|
return lines.join("\n");
|
|
320
330
|
}
|
|
321
|
-
//
|
|
322
|
-
//
|
|
331
|
+
// The heaviest individual log items — the FOLD targets behind the weight, a ranked LIST in both the
|
|
332
|
+
// mermaid and tabular budgets (#450: a ranking isn't a composition, so it's never a chart). "items",
|
|
333
|
+
// not "entries": log:/// rows, distinct from catalog entries (plurnk.md: "EDIT is only for entries").
|
|
334
|
+
// {§tokenomics-largest-entries}
|
|
335
|
+
static #heaviestItemsLines(largest) {
|
|
336
|
+
if (largest.length === 0)
|
|
337
|
+
return [];
|
|
338
|
+
return ["", "Heaviest items (FOLD targets — folding reclaims their tokens):", "| item | tokens |", "|---|--:|",
|
|
339
|
+
...largest.map((e) => `| ${e.path} | ${e.tokens} |`)];
|
|
340
|
+
}
|
|
341
|
+
// #440 {§budget-mermaid} — the Budget as two budget-scaled mermaid diagrams (validated to render on
|
|
342
|
+
// GitHub; syntax: plurnk-plurnkdown/demo/budget-mermaid.md). Both scaled to the CEILING, so salience
|
|
343
|
+
// tracks pressure: `free` dominates at low usage (calm), turn boxes fill as it climbs (urgent).
|
|
344
|
+
// free/used/system+context are placeholders — the post-assembly total resolves them. (#450 cut the xychart.)
|
|
345
|
+
static #renderBudgetMermaid(log, ceiling) {
|
|
346
|
+
// Turn composition → treemap: turn boxes + system+context + free compose the whole ceiling —
|
|
347
|
+
// the per-turn FOLD surface (which turns are heavy, labeled `turn L/T`) the headline can't give.
|
|
348
|
+
const treemap = [
|
|
349
|
+
"```mermaid",
|
|
350
|
+
"treemap-beta",
|
|
351
|
+
`"Budget — ceiling ${ceiling}"`,
|
|
352
|
+
` "free": ${TOKENS_FREE_PLACEHOLDER}`,
|
|
353
|
+
` "system + context": ${SYSTEM_CTX_PLACEHOLDER}`,
|
|
354
|
+
...log.byTurn.map((t) => ` "turn ${t.turn}": ${t.tokens}`),
|
|
355
|
+
"```",
|
|
356
|
+
].join("\n");
|
|
357
|
+
// Gauge → pie: used vs free, budget-scaled (used + free = ceiling); also a visual exemplar for
|
|
358
|
+
// the model's own user-facing SENDs. (#450 cut the heaviest-items xychart — its bare-coordinate
|
|
359
|
+
// labels a floor model can't decode, and the treemap already surfaces per-turn heaviness.)
|
|
360
|
+
const pie = [
|
|
361
|
+
"```mermaid",
|
|
362
|
+
"pie showData",
|
|
363
|
+
` title Budget — used vs free (ceiling ${ceiling})`,
|
|
364
|
+
` "used" : ${TOKEN_USAGE_PLACEHOLDER}`,
|
|
365
|
+
` "free" : ${TOKENS_FREE_PLACEHOLDER}`,
|
|
366
|
+
"```",
|
|
367
|
+
].join("\n");
|
|
368
|
+
return [treemap, pie].join("\n\n");
|
|
369
|
+
}
|
|
370
|
+
// #328 — the per-workspace client execs policy narrows what the packet ADVERTISES, matching what
|
|
371
|
+
// dispatch refuses: a workspace-disabled tag is absent from the capability sheet and the doc set,
|
|
323
372
|
// never taught-then-refused. No policy (execs unset) → everything boot-registered shows.
|
|
324
|
-
async #
|
|
325
|
-
const { execs } = await
|
|
373
|
+
async #workspaceEnabled(workspaceId) {
|
|
374
|
+
const { execs } = await WorkspaceSettings.read(this.#db, workspaceId);
|
|
326
375
|
if (execs === null)
|
|
327
376
|
return () => true;
|
|
328
377
|
return (tag) => Policy.isEnabled(tag, execs);
|
|
@@ -344,58 +393,55 @@ class PacketBuilder {
|
|
|
344
393
|
return DEFAULT_LOOP_FLAGS;
|
|
345
394
|
}
|
|
346
395
|
}
|
|
347
|
-
#collectTools(
|
|
348
|
-
|
|
396
|
+
#collectTools(workspaceEnabled, questionsOn = false, activeSchemes) {
|
|
397
|
+
// §PACKET Tools (#441) — the capability sheet's OP examples ride a `plurnk` fence, matching the
|
|
398
|
+
// Schemes catalog (one packet, one shape for op-example sheets). Prose notices (EXEC-disabled)
|
|
399
|
+
// stay prose beside the fence — a prose line isn't an op for the op-fence gate to validate.
|
|
400
|
+
const ops = [];
|
|
401
|
+
const notices = [];
|
|
349
402
|
// §send-300-choices — the one-liner rides ONLY where questions are enabled (allowed +
|
|
350
403
|
// client-requested); the fuller questions.md doc injects through docEntries the same way.
|
|
351
404
|
if (questionsOn)
|
|
352
|
-
|
|
353
|
-
// Each available runtime tag contributes its self-documenting example —
|
|
354
|
-
// the example carries syntax + purpose, so there's no prose line. Tags
|
|
355
|
-
// with no example (sh/node, covered by the core prompt) contribute
|
|
356
|
-
// nothing; available-only, so the model never sees an unusable tag. `* `
|
|
357
|
-
// bullets + bare op forms match the packet's list/op rendering (no `- `,
|
|
358
|
-
// no backticks — see packet-wire.ts).
|
|
405
|
+
ops.push("<<SEND[300]:Deploy where?;staging;production:SEND");
|
|
359
406
|
const executors = this.#executors();
|
|
360
407
|
if (executors !== undefined) {
|
|
361
408
|
const excluded = docsExcludeSet();
|
|
362
409
|
const runtimes = executors.availableRuntimes();
|
|
363
|
-
// execs#24
|
|
364
|
-
//
|
|
365
|
-
//
|
|
366
|
-
// be8a77c. The sheet's lines are EXEC-usage examples, so the key is the 'exec' scheme
|
|
367
|
-
// (the op face, excludedInAsk). When it's inactive, say so POSITIVELY: plurnk.md still
|
|
368
|
-
// teaches EXEC as language, and silent absence measurably invites confabulated runtimes
|
|
369
|
-
// (the client's 5-probe: 500×3). Core speaks the line — only core knows the gate closed.
|
|
410
|
+
// execs#24 / #367: the sheet's lines are EXEC-usage examples, keyed on the 'exec' scheme
|
|
411
|
+
// (the op face, excludedInAsk). When inactive, say so POSITIVELY (a prose notice): plurnk.md
|
|
412
|
+
// still teaches EXEC as language, and silent absence measurably invites confabulated runtimes.
|
|
370
413
|
const execActive = activeSchemes === undefined || activeSchemes.has("exec");
|
|
371
414
|
if (runtimes.length > 0 && !execActive) {
|
|
372
|
-
|
|
373
|
-
return tools;
|
|
415
|
+
notices.push("EXEC operations are disabled for this loop — do not run commands; answer or advise directly");
|
|
374
416
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
417
|
+
else {
|
|
418
|
+
for (const tag of runtimes) {
|
|
419
|
+
if (excluded.has(tag))
|
|
420
|
+
continue; // #240 — PLURNK_SERVICE_DOCS_EXCLUDE drops the oneliner + the doc
|
|
421
|
+
if (!workspaceEnabled(tag))
|
|
422
|
+
continue; // #328 — workspace-disabled tags aren't advertised
|
|
423
|
+
const entry = executors.entry(tag);
|
|
424
|
+
// #240 — the example IS the oneliner (a bare op, fenced below); the fuller doc
|
|
425
|
+
// materializes at plurnk://docs/<tag>.md. No example → no line.
|
|
426
|
+
if (entry?.example)
|
|
427
|
+
ops.push(entry.example);
|
|
428
|
+
}
|
|
386
429
|
}
|
|
387
430
|
}
|
|
388
|
-
|
|
431
|
+
const parts = [...notices];
|
|
432
|
+
if (ops.length > 0)
|
|
433
|
+
parts.push(`\`\`\`plurnk\n${ops.join("\n")}\n\`\`\``);
|
|
434
|
+
return parts.join("\n\n");
|
|
389
435
|
}
|
|
390
436
|
// #note12 — the daughter-provided reference docs (schemes' + execs' `documentation`),
|
|
391
437
|
// materialized at plurnk:///docs/<name>.md by loop_run (like operator docs) so the
|
|
392
438
|
// catalogue's doc-links READ and the manifest carries each doc's token cost.
|
|
393
|
-
async docEntries(
|
|
439
|
+
async docEntries(workspaceId) {
|
|
394
440
|
const out = this.#schemes.docs(); // scheme docs already drop PLURNK_SERVICE_DOCS_EXCLUDE names
|
|
395
441
|
// §send-300-choices — the conditional teaching: questions.md (from the docs corpus)
|
|
396
|
-
// materializes ONLY for enabled
|
|
397
|
-
// plugin docs below. An un-enabled
|
|
398
|
-
if (await
|
|
442
|
+
// materializes ONLY for enabled workspaces — the same conditional-doc mechanism as the EXEC
|
|
443
|
+
// plugin docs below. An un-enabled workspace is never taught the op it can't use.
|
|
444
|
+
if (await WorkspaceSettings.questionsEnabled(this.#db, workspaceId)) {
|
|
399
445
|
try {
|
|
400
446
|
const q = await readFile(resolvePath(Paths.schemeDocs, "questions.md"), "utf8");
|
|
401
447
|
if (q.length > 0)
|
|
@@ -406,11 +452,11 @@ class PacketBuilder {
|
|
|
406
452
|
const executors = this.#executors();
|
|
407
453
|
if (executors !== undefined) {
|
|
408
454
|
const excluded = docsExcludeSet();
|
|
409
|
-
const
|
|
455
|
+
const workspaceEnabled = await this.#workspaceEnabled(workspaceId); // #328 — no doc for a disabled tag
|
|
410
456
|
for (const tag of executors.availableRuntimes()) {
|
|
411
457
|
if (excluded.has(tag))
|
|
412
458
|
continue; // #240 — exec docs honor the same exclude
|
|
413
|
-
if (!
|
|
459
|
+
if (!workspaceEnabled(tag))
|
|
414
460
|
continue;
|
|
415
461
|
const doc = executors.entry(tag)?.documentation;
|
|
416
462
|
if (doc !== undefined && doc.length > 0)
|
|
@@ -425,10 +471,12 @@ class PacketBuilder {
|
|
|
425
471
|
// strike, rebuild, re-measure. Folds (never deletes). The strike it raises and
|
|
426
472
|
// the hard-stop it can signal are returned to runLoop, which owns abandonment.
|
|
427
473
|
// §grinder-overflow-only — fires only on actual overflow, never speculatively
|
|
428
|
-
async enforceBudget({ packet, provider,
|
|
474
|
+
async enforceBudget({ packet, provider, workerId, loopId, turnId, mintSequence, rebuild }) {
|
|
429
475
|
const ceiling = this.ceilingFor(provider);
|
|
430
476
|
const measure = (p) => p.tokens;
|
|
431
|
-
|
|
477
|
+
// #421 — a null ceiling is an unbounded window: always fit, never fold or strike (the backend
|
|
478
|
+
// clamps; this mirrors Engine's physicallySendable, which treats a null contextWindow as sendable).
|
|
479
|
+
if (ceiling === null || measure(packet) <= ceiling)
|
|
432
480
|
return { packet, fit: true, struck: false };
|
|
433
481
|
// ONE rule, every turn — turn 1 and turn 101 alike (§grinder-layer1-rollback): fold the
|
|
434
482
|
// NEWEST turn boundary's still-open rows (the prior turn's emissions + this turn's
|
|
@@ -442,7 +490,7 @@ class PacketBuilder {
|
|
|
442
490
|
// turn late. The row is grinder-exempt, so it stacks into a visible recurrence trail. It
|
|
443
491
|
// sits at the turn's reserved running sequence (mintSequence) so it never collides with the
|
|
444
492
|
// post-generate dispatch rows. §telemetry-uniform-error-channel, §grinder-overflow-error-row
|
|
445
|
-
await this.#telemetry.mintEngineError("budget_overflow", {
|
|
493
|
+
await this.#telemetry.mintEngineError("budget_overflow", { workerId, loopId, turnId, sequence: mintSequence });
|
|
446
494
|
await this.#db.engine_grinder_fold_newest_turn.run({ loop_id: loopId, turn_id: turnId });
|
|
447
495
|
const current = await rebuild();
|
|
448
496
|
return { packet: current, fit: measure(current) <= ceiling, struck: true };
|
|
@@ -492,16 +540,16 @@ class PacketBuilder {
|
|
|
492
540
|
// Snapshot is taken at packet build (pre-dispatch this turn), so it
|
|
493
541
|
// reflects "what has happened before this turn." Each row carries a
|
|
494
542
|
// log:///<loop_seq>/<turn_seq>/<sequence> coordinate the model can READ.
|
|
495
|
-
async #buildLog(
|
|
496
|
-
// SPEC §packet-terms: runs own log entries — log is the
|
|
497
|
-
// not the loop's. Span all loops in the
|
|
543
|
+
async #buildLog(workerId) {
|
|
544
|
+
// SPEC §packet-terms: runs own log entries — log is the worker's history,
|
|
545
|
+
// not the loop's. Span all loops in the worker so the model sees
|
|
498
546
|
// earlier loops' work as conversational memory.
|
|
499
547
|
//
|
|
500
548
|
// User prompts are first-class log entries: runTurn writes a
|
|
501
549
|
// client-origin SEND[200] row at sequence=0 of each new
|
|
502
550
|
// turn-1. Prompts thus surface naturally in this query — no
|
|
503
551
|
// synthetic / shim layer.
|
|
504
|
-
const rows = await this.#db.engine_render_log.all({
|
|
552
|
+
const rows = await this.#db.engine_render_log.all({ worker_id: workerId });
|
|
505
553
|
return rows.map((r) => ({
|
|
506
554
|
coordinate: `${r.loop_seq}/${r.turn_seq}/${r.sequence}`,
|
|
507
555
|
origin: r.origin,
|