pi-fold 1.0.3 → 2.0.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/README.md +34 -8
- package/extensions/active-context.ts +255 -2331
- package/extensions/evidence.js +0 -10
- package/extensions/index.js +10 -30
- package/extensions/json.ts +3 -29
- package/extensions/lib/canonical.ts +0 -8
- package/extensions/lib/curation.ts +1 -258
- package/extensions/lib/folding.ts +33 -406
- package/extensions/lib/instrumentation.ts +3 -124
- package/extensions/lib/measurement.ts +0 -71
- package/extensions/lib/persistence.ts +216 -197
- package/extensions/lib/policy.ts +29 -558
- package/extensions/lib/rollback.ts +0 -53
- package/extensions/lib/scheduling.ts +36 -367
- package/extensions/lib/selection.ts +206 -312
- package/extensions/lib/tool-surface.ts +5 -18
- package/extensions/lib/transcript.ts +32 -108
- package/package.json +1 -1
- package/extensions/lib/surfacing.ts +0 -604
- package/extensions/summarizer.js +0 -291
package/extensions/evidence.js
CHANGED
|
@@ -562,16 +562,6 @@ function mcpServerForTool(toolName, fallback) {
|
|
|
562
562
|
: fallback;
|
|
563
563
|
}
|
|
564
564
|
|
|
565
|
-
/**
|
|
566
|
-
* MCP tools are recognized by their name, and by nothing else.
|
|
567
|
-
*
|
|
568
|
-
* This was a host-supplied predicate whose default returned false for everything, so a
|
|
569
|
-
* conventionally named MCP tool was never classified unless a host wrote the predicate:
|
|
570
|
-
* a documented feature whose shipped state guaranteed it never ran. `mcp__server__tool`
|
|
571
|
-
* is the convention every MCP host follows, so it is the rule. A name outside the
|
|
572
|
-
* convention is deliberately not classified as MCP, and its oversized results fall to
|
|
573
|
-
* the ordinary tool-result path.
|
|
574
|
-
*/
|
|
575
565
|
function conventionalMcpTool(toolName) {
|
|
576
566
|
return mcpServerForTool(toolName, null) !== null;
|
|
577
567
|
}
|
package/extensions/index.js
CHANGED
|
@@ -1,35 +1,20 @@
|
|
|
1
1
|
import { registerActiveContext } from "./active-context.ts";
|
|
2
2
|
import { registerEvidenceIngestion } from "./evidence.js";
|
|
3
|
-
import { createSummarizeContextSpan } from "./summarizer.js";
|
|
4
3
|
|
|
5
|
-
/**
|
|
6
|
-
* THE public surface: five options, and every name outside them is refused.
|
|
7
|
-
*
|
|
8
|
-
* Unknown keys used to spread through silently, which made every deletion a no-op rather
|
|
9
|
-
* than an error and handed a deployment the opposite of what it asked for. A caller that
|
|
10
|
-
* passes a name believes it bought behavior, so a name this package does not sell is a
|
|
11
|
-
* throw with the replacement named in it.
|
|
12
|
-
*/
|
|
13
4
|
const PUBLIC_OPTIONS = Object.freeze([
|
|
14
|
-
"thresholds", "
|
|
5
|
+
"thresholds", "providerInputBudget", "blacklistAutoFoldTools", "guidance",
|
|
15
6
|
]);
|
|
16
7
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
const GENERATOR_DELETED = "the model brief generator is deleted (2026-08-14): across 15 sealed " +
|
|
9
|
+
"generator runs, 75 percent of its 1,186 upgraded briefs were never consulted or never became " +
|
|
10
|
+
"visible, and the no-generator build scored the same for less. Every fold carries its " +
|
|
11
|
+
"deterministic brief; sessions already holding model briefs keep them on load";
|
|
12
|
+
|
|
22
13
|
const INVERTED_AUTO_FOLD_LIST = "renamed blacklistAutoFoldTools, and the sense is INVERTED: " +
|
|
23
14
|
"every completed tool batch folds unmarked, and the list names the exceptions whose " +
|
|
24
15
|
"results must stay raw. An allow-list moved across verbatim would bar exactly the tools " +
|
|
25
16
|
"it meant to permit";
|
|
26
17
|
|
|
27
|
-
/**
|
|
28
|
-
* Options that left the surface, each answered by name. Deployment identity is hardwired:
|
|
29
|
-
* this is a branded package with one deployment, not a framework a host rebrands, and a
|
|
30
|
-
* consumer that moved `entryTypePrefix` stranded its own durable folds under a namespace
|
|
31
|
-
* nothing would read back.
|
|
32
|
-
*/
|
|
33
18
|
const REFUSED_OPTIONS = Object.freeze({
|
|
34
19
|
toolName: "the deployment identity is hardwired to pi-fold: the tool is pi_fold_context",
|
|
35
20
|
toolLabel: "the deployment identity is hardwired to pi-fold",
|
|
@@ -52,11 +37,11 @@ const REFUSED_OPTIONS = Object.freeze({
|
|
|
52
37
|
providerTotalWindow: "renamed providerInputBudget, and it is ALREADY NET: pass the tokens " +
|
|
53
38
|
"the deployment may actually fill, not the total window the runtime then subtracts a " +
|
|
54
39
|
"guessed output reservation from",
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
summarizer: GENERATOR_DELETED,
|
|
41
|
+
summarizeContextSpan: GENERATOR_DELETED,
|
|
57
42
|
});
|
|
58
43
|
|
|
59
|
-
export function registerPiFold(pi, options = {}
|
|
44
|
+
export function registerPiFold(pi, options = {}) {
|
|
60
45
|
for (const name of Object.keys(options)) {
|
|
61
46
|
if (Object.hasOwn(REFUSED_OPTIONS, name)) {
|
|
62
47
|
throw new Error(`${name} is no longer an option: ${REFUSED_OPTIONS[name]}`);
|
|
@@ -66,13 +51,8 @@ export function registerPiFold(pi, options = {}, loadHostModule) {
|
|
|
66
51
|
`${PUBLIC_OPTIONS.join(", ")}`);
|
|
67
52
|
}
|
|
68
53
|
}
|
|
69
|
-
const { summarizer = "session", ...activeContextOptions } = options;
|
|
70
|
-
const summarizeContextSpan = createSummarizeContextSpan(summarizer, loadHostModule);
|
|
71
|
-
// Evidence first, then the runtime. Ingestion is unconditional: the artifacts are the
|
|
72
|
-
// exact-recovery anchors an oversized tool result folds against, so a deployment that
|
|
73
|
-
// turned them off kept the folds and lost what makes them lossless.
|
|
74
54
|
registerEvidenceIngestion(pi);
|
|
75
|
-
return registerActiveContext(pi,
|
|
55
|
+
return registerActiveContext(pi, options);
|
|
76
56
|
}
|
|
77
57
|
|
|
78
58
|
export default function piFold(pi, options) {
|
package/extensions/json.ts
CHANGED
|
@@ -8,7 +8,6 @@ export interface EvidenceRef {
|
|
|
8
8
|
sha256: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
// Capture trust-boundary intrinsics before mutable prototypes can influence validation.
|
|
12
11
|
const ArrayConstructor = Array;
|
|
13
12
|
const arrayIsArray = Array.isArray;
|
|
14
13
|
const arrayPrototype = Array.prototype;
|
|
@@ -95,7 +94,6 @@ function safeJson(value: unknown, ancestors: unknown[]): string | undefined {
|
|
|
95
94
|
}
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
/** Preserve JSON property order without invoking getters, toJSON, coercion, iterators, or mutable prototypes. */
|
|
99
97
|
export function stableStringify(value: unknown): string {
|
|
100
98
|
const serialized = safeJson(value, new ArrayConstructor<unknown>());
|
|
101
99
|
if (typeof serialized !== "string") throw new Error("Value is not JSON-serializable");
|
|
@@ -122,7 +120,6 @@ export function ownDataProperty(value: unknown, key: PropertyKey): unknown {
|
|
|
122
120
|
return descriptor && "value" in descriptor ? descriptor.value : undefined;
|
|
123
121
|
}
|
|
124
122
|
|
|
125
|
-
/** Copy a dense own-data array without invoking inherited accessors, iterators, or methods. */
|
|
126
123
|
export function denseOwnArrayValues(value: unknown): unknown[] | null {
|
|
127
124
|
if (!arrayIsArray(value) || isProxy(value) || getPrototypeOf(value) !== arrayPrototype) return null;
|
|
128
125
|
const lengthDescriptor = getOwnPropertyDescriptor(value, "length");
|
|
@@ -202,7 +199,9 @@ export function objectRefKey(ref: EvidenceRef): string {
|
|
|
202
199
|
}
|
|
203
200
|
|
|
204
201
|
export function sameObjectIdentity(left: EvidenceRef, right: EvidenceRef): boolean {
|
|
205
|
-
return
|
|
202
|
+
return left.sessionId === right.sessionId &&
|
|
203
|
+
left.entryId === right.entryId &&
|
|
204
|
+
left.role === right.role;
|
|
206
205
|
}
|
|
207
206
|
|
|
208
207
|
export function evidenceValue(message: unknown): unknown {
|
|
@@ -216,31 +215,6 @@ export function evidenceValue(message: unknown): unknown {
|
|
|
216
215
|
};
|
|
217
216
|
}
|
|
218
217
|
|
|
219
|
-
/**
|
|
220
|
-
* Evidence digests, kept against the message OBJECT.
|
|
221
|
-
*
|
|
222
|
-
* A message is canonicalized and hashed whole to produce its digest, and the messages
|
|
223
|
-
* this runs on are transcript entries: tool results in the tens of kilobytes. Nothing
|
|
224
|
-
* cached the result, so every reading that wanted to confirm a ref rehashed the entire
|
|
225
|
-
* message from scratch. `exactMapped` does exactly that on every lookup, and the
|
|
226
|
-
* selection, surfacing, ordering and accounting passes call it once per ref, per fold,
|
|
227
|
-
* several times a turn. Replayed against the sealed sol-20260812 rep-2 session at its
|
|
228
|
-
* 85 percent point, that single recomputation was 76 percent of all CPU, and it grows
|
|
229
|
-
* with the transcript because the transcript is what it rehashes.
|
|
230
|
-
*
|
|
231
|
-
* A WeakMap on the object is the same shape `MAPPED_BY_KEY` and `BRANCH_POSITIONS`
|
|
232
|
-
* already use one line over, and the same rule gate 113 settled for entry evidence: the
|
|
233
|
-
* session is append-only, so keep the derivation against the object and let a REPLACED
|
|
234
|
-
* object miss rather than serve a stale answer. Entries are never edited in place; they
|
|
235
|
-
* are appended, and a changed message is a different object, which misses and rehashes.
|
|
236
|
-
*
|
|
237
|
-
* What this no longer detects, stated plainly: a message mutated IN PLACE after it was
|
|
238
|
-
* first hashed now keeps its original digest. That case was already half invisible,
|
|
239
|
-
* because the ref a snapshot carries was itself computed from that same object at
|
|
240
|
-
* mapping time, so a mutation that beat the digest would have to be caught by this
|
|
241
|
-
* recomputation alone. No path in this package edits a persisted message, and the
|
|
242
|
-
* runtime clones before it stores.
|
|
243
|
-
*/
|
|
244
218
|
const EVIDENCE_DIGESTS = new WeakMap<object, string>();
|
|
245
219
|
|
|
246
220
|
export function evidenceSha256(message: unknown): string {
|
|
@@ -18,16 +18,9 @@ export function bytes(value: unknown): number {
|
|
|
18
18
|
export function boundedUtf8(text: string, maximumBytes: number): string {
|
|
19
19
|
const buffer = Buffer.from(text, "utf8");
|
|
20
20
|
if (buffer.length <= maximumBytes) return text;
|
|
21
|
-
// A multi-byte sequence cut by the bound decodes to one trailing replacement
|
|
22
|
-
// character; dropping it keeps the slice an exact prefix of the source.
|
|
23
21
|
return buffer.subarray(0, maximumBytes).toString("utf8").replace(/\uFFFD$/, "");
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
/**
|
|
27
|
-
* The suffix of `text` beginning at a byte offset. A multi-byte sequence cut by the
|
|
28
|
-
* offset decodes to one leading replacement character; dropping it keeps the slice an
|
|
29
|
-
* exact suffix of the source, which is what makes a paged peek losslessly rejoinable.
|
|
30
|
-
*/
|
|
31
24
|
export function utf8Slice(text: string, offsetBytes: number): string {
|
|
32
25
|
if (offsetBytes <= 0) return text;
|
|
33
26
|
const buffer = Buffer.from(text, "utf8");
|
|
@@ -75,7 +68,6 @@ export function contentText(message: unknown): string {
|
|
|
75
68
|
return text.join("\n");
|
|
76
69
|
}
|
|
77
70
|
|
|
78
|
-
/** Pi's exact one-entry projection, kept local so this service has no second transcript owner. */
|
|
79
71
|
export function sessionEntryMessages(entry: Record<string, unknown>): unknown[] {
|
|
80
72
|
if (entry.type === "message" && entry.message && typeof entry.message === "object") {
|
|
81
73
|
const message = entry.message as Record<string, unknown>;
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { objectRefKey } from "../json.ts";
|
|
2
|
-
import {
|
|
3
|
-
boundedUtf8,
|
|
4
|
-
bytes,
|
|
5
|
-
messageRole,
|
|
6
|
-
} from "./canonical.ts";
|
|
7
1
|
import { boundReceiptText } from "./measurement.ts";
|
|
8
2
|
import {
|
|
9
3
|
contextBrand,
|
|
@@ -11,140 +5,8 @@ import {
|
|
|
11
5
|
CONTEXT_RECEIPT_BLOCK_BYTES,
|
|
12
6
|
DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN,
|
|
13
7
|
MAX_CONTEXT_RECEIPTS,
|
|
14
|
-
MAX_LAST_CALL_TEXT_BYTES,
|
|
15
|
-
MAX_SURFACING_LINE_BYTES,
|
|
16
|
-
MAX_THRESHOLD_NOTICE_TEXT_BYTES,
|
|
17
|
-
} from "./policy.ts";
|
|
18
|
-
import type {
|
|
19
|
-
ActiveContextSnapshot,
|
|
20
|
-
ActiveContextState,
|
|
21
8
|
} from "./policy.ts";
|
|
22
9
|
|
|
23
|
-
/**
|
|
24
|
-
* Guided curation.
|
|
25
|
-
*
|
|
26
|
-
* The ladder's commit triggers are correct and silent. Silent is the problem: the
|
|
27
|
-
* agent learns that its evidence became placeholders by reading a placeholder, which
|
|
28
|
-
* is the one moment it can no longer do anything about it. So a commit is ANNOUNCED
|
|
29
|
-
* before it happens, early enough that reacting is still cheap, and the announcement
|
|
30
|
-
* carries what is about to happen, what it costs, and the actions that change it.
|
|
31
|
-
*
|
|
32
|
-
* Everything here is deterministic: transcript ordinals, measured tokens and byte
|
|
33
|
-
* counts only. No wall clock and no randomness.
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
export interface StaleToolMass {
|
|
37
|
-
/** Serialized bytes of tool results outside the fresh tail and outside every fold. */
|
|
38
|
-
bytes: number;
|
|
39
|
-
/** How many tool results that mass is spread over. */
|
|
40
|
-
results: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Tool-result mass a commit could actually reach: outside the fresh tail, outside any
|
|
45
|
-
* protection, and not already owned by a fold. Occupancy alone would announce commits
|
|
46
|
-
* that have nothing to fold; this is the second signal that says the announcement is
|
|
47
|
-
* about to be worth something.
|
|
48
|
-
*/
|
|
49
|
-
export function staleToolMass(
|
|
50
|
-
snapshot: ActiveContextSnapshot,
|
|
51
|
-
state: ActiveContextState,
|
|
52
|
-
): StaleToolMass {
|
|
53
|
-
const owned = new Set(state.folds.flatMap((fold) =>
|
|
54
|
-
fold.parts.flatMap((part) => part.kind === "raw" ? [objectRefKey(part.ref)] : [])));
|
|
55
|
-
let total = 0;
|
|
56
|
-
let results = 0;
|
|
57
|
-
for (const item of snapshot.mapped) {
|
|
58
|
-
if (!item.ref || messageRole(item.message) !== "toolResult") continue;
|
|
59
|
-
if (snapshot.toolProtectedIndices.has(item.index)) continue;
|
|
60
|
-
if (owned.has(objectRefKey(item.ref))) continue;
|
|
61
|
-
total += bytes(item.message);
|
|
62
|
-
results += 1;
|
|
63
|
-
}
|
|
64
|
-
return { bytes: total, results };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface CurationSignals {
|
|
68
|
-
/** Measured provider tokens as a share of the truthful serving budget; null unmeasured. */
|
|
69
|
-
occupancy: number | null;
|
|
70
|
-
/** The trigger line this occupancy is read against: thresholds.maxTarget. */
|
|
71
|
-
maxTarget: number;
|
|
72
|
-
occupancyTokens: number | null;
|
|
73
|
-
/** THE serving budget: one resolved value, the same one the fence and estimator use. */
|
|
74
|
-
budgetTokens: number;
|
|
75
|
-
window: number;
|
|
76
|
-
/** Stale tool mass as a share of the serving budget, in the session's own tokens. */
|
|
77
|
-
staleToolShare: number;
|
|
78
|
-
staleToolTokens: number;
|
|
79
|
-
staleToolResults: number;
|
|
80
|
-
/** Marks a commit could apply right now. */
|
|
81
|
-
eligibleFolds: number;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Both signals are measured against ONE resolved serving budget, handed in by the
|
|
86
|
-
* caller that owns the capacity accounting. Nothing here re-derives a window from a
|
|
87
|
-
* descriptor: rep 15's trigger and gate ran the whole run against a 272,000-token
|
|
88
|
-
* per-request descriptor while the fence used the truthful 383,616, because the budget
|
|
89
|
-
* was computed twice from two different places.
|
|
90
|
-
*/
|
|
91
|
-
export function curationSignals(input: {
|
|
92
|
-
snapshot: ActiveContextSnapshot;
|
|
93
|
-
state: ActiveContextState;
|
|
94
|
-
usedTokens: number | null;
|
|
95
|
-
budgetTokens: number;
|
|
96
|
-
/** The resolved serving WINDOW the budget was taken from, reported for audit. */
|
|
97
|
-
window: number;
|
|
98
|
-
/** The session's own measured serialized chars per token; never a fixed constant. */
|
|
99
|
-
charsPerToken: number;
|
|
100
|
-
eligibleFolds: number;
|
|
101
|
-
}): CurationSignals {
|
|
102
|
-
const mass = staleToolMass(input.snapshot, input.state);
|
|
103
|
-
const charsPerToken = Number.isFinite(input.charsPerToken) && input.charsPerToken > 0
|
|
104
|
-
? input.charsPerToken
|
|
105
|
-
: 4;
|
|
106
|
-
const staleToolTokens = Math.ceil(mass.bytes / charsPerToken);
|
|
107
|
-
return {
|
|
108
|
-
maxTarget: input.snapshot.thresholds.maxTarget,
|
|
109
|
-
occupancy: input.usedTokens === null || input.budgetTokens <= 0
|
|
110
|
-
? null
|
|
111
|
-
: input.usedTokens / input.budgetTokens,
|
|
112
|
-
occupancyTokens: input.usedTokens,
|
|
113
|
-
budgetTokens: input.budgetTokens,
|
|
114
|
-
window: input.window,
|
|
115
|
-
staleToolShare: input.budgetTokens > 0 ? staleToolTokens / input.budgetTokens : 0,
|
|
116
|
-
staleToolTokens,
|
|
117
|
-
staleToolResults: mass.results,
|
|
118
|
-
eligibleFolds: input.eligibleFolds,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* ONE signal. Occupancy of the serving budget reaches maxTarget, and a commit is due.
|
|
124
|
-
*
|
|
125
|
-
* The second signal is gone with the announcement it guarded. It existed because the
|
|
126
|
-
* trigger ANNOUNCED a commit before running one, and announcing a commit that has
|
|
127
|
-
* nothing stale to fold is a false statement in the window; the announcement was
|
|
128
|
-
* deleted when its cache cost was measured, and the AND-condition outlived its reason.
|
|
129
|
-
* A commit with nothing eligible now simply applies nothing and says so, which the
|
|
130
|
-
* reclaim floor and the eligibility accounting already report. Stale mass is still
|
|
131
|
-
* measured and still reported; it is no longer a condition on the trigger.
|
|
132
|
-
*/
|
|
133
|
-
export function curationTriggerFires(signals: CurationSignals): boolean {
|
|
134
|
-
return signals.occupancy !== null &&
|
|
135
|
-
Number.isFinite(signals.occupancy) &&
|
|
136
|
-
signals.occupancy >= signals.maxTarget;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* The awareness block a mark call answers with.
|
|
141
|
-
*
|
|
142
|
-
* The projection is byte-frozen between fold events, so this tool result is the ONLY
|
|
143
|
-
* cache-free place left to tell the agent where it stands. It carries three things and
|
|
144
|
-
* stops: what is now held, what is still on the table as an aggregate with a bounded
|
|
145
|
-
* head of the largest names, and the one percentage worth steering by. It is hard
|
|
146
|
-
* bounded on the receipt block's principle: awareness that becomes bloat is bloat.
|
|
147
|
-
*/
|
|
148
10
|
export function markAwarenessText(input: {
|
|
149
11
|
held: ReadonlyArray<{ id: string; kind: string; tokens: number }>;
|
|
150
12
|
remainder: { spans: number; tokens: number; share: number; candidates: ReadonlyArray<{ id: string; tokens: number }> };
|
|
@@ -168,16 +30,6 @@ export function markAwarenessText(input: {
|
|
|
168
30
|
].join("\n"), CONTEXT_MARK_RESPONSE_BYTES, `[${brand} marks] Held; the remainder is unavailable this pass.`);
|
|
169
31
|
}
|
|
170
32
|
|
|
171
|
-
/**
|
|
172
|
-
* The rider: the one post-commit carrier, composed at a fold commit and delivered
|
|
173
|
-
* INSIDE the rewrite that commit already paid for, beside the receipt. Its wording
|
|
174
|
-
* follows the curation rule (report what is happening, name the actions), its numbers
|
|
175
|
-
* are decision inputs only: pending agent marks with what they free, up to three
|
|
176
|
-
* completed-unit anchors, and the pinned share against its cap. Raw occupancy,
|
|
177
|
-
* distance-to-commit and unmarked-share percentages stay OUT: they are pressure
|
|
178
|
-
* readouts, not decisions, and the pre-commit last call is where they are both
|
|
179
|
-
* accurate and free.
|
|
180
|
-
*/
|
|
181
33
|
export function contextRiderText(input: {
|
|
182
34
|
toolName: string;
|
|
183
35
|
brandNoun?: string;
|
|
@@ -185,12 +37,9 @@ export function contextRiderText(input: {
|
|
|
185
37
|
eligibleMarks: number;
|
|
186
38
|
freedTokens: number;
|
|
187
39
|
eligibleFreedTokens: number;
|
|
188
|
-
/** Up to three completed-unit entry ids the agent could mark next. */
|
|
189
40
|
anchors: string[];
|
|
190
41
|
pinnedShare: number;
|
|
191
42
|
maxPinnedShare: number;
|
|
192
|
-
/** The surfacing slate, already bounded and already logged; null on a silent pass. */
|
|
193
|
-
suggestion?: string | null;
|
|
194
43
|
}): string {
|
|
195
44
|
const brand = contextBrand(input.brandNoun ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN);
|
|
196
45
|
const anchors = input.anchors.length
|
|
@@ -198,10 +47,7 @@ export function contextRiderText(input: {
|
|
|
198
47
|
: "none";
|
|
199
48
|
const pinnedPercent = Math.round(input.pinnedShare * 100);
|
|
200
49
|
const capPercent = Math.round(input.maxPinnedShare * 100);
|
|
201
|
-
|
|
202
|
-
// carrier's overhead is its bound plus the line's, and neither can eat the other: a
|
|
203
|
-
// long rider must never truncate the one sentence a suggestion consists of.
|
|
204
|
-
return joinSurfacing(boundReceiptText(
|
|
50
|
+
return boundReceiptText(
|
|
205
51
|
[
|
|
206
52
|
`[${brand} notice] A fold commit just landed; the next one will batch every pending mark ` +
|
|
207
53
|
"into one rewrite, and marks are free until then.",
|
|
@@ -219,93 +65,9 @@ export function contextRiderText(input: {
|
|
|
219
65
|
].join("\n"),
|
|
220
66
|
2_048,
|
|
221
67
|
`[${brand} notice] Post-commit curation details are unavailable this pass.`,
|
|
222
|
-
), input.suggestion);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/** One carrier, one optional slate line, appended last and bounded on its own. */
|
|
226
|
-
export function joinSurfacing(text: string, suggestion?: string | null): string {
|
|
227
|
-
if (!suggestion) return text;
|
|
228
|
-
return `${text}\n${boundedUtf8(suggestion, MAX_SURFACING_LINE_BYTES)}`;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/** The ruled last-call sentence (Shane, 2026-08-09 13:23), verbatim. */
|
|
232
|
-
export const LAST_CALL_WORDING =
|
|
233
|
-
"Fold commit triggered, please add or edit marks, pin or unpin context based on foreseeable relevance.";
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* The pre-commit last-call.
|
|
237
|
-
*
|
|
238
|
-
* It rides the commit boundary: the band-top trigger has fired, the rewrite is one
|
|
239
|
-
* round away, so this is the single moment telemetry is both accurate and free. It
|
|
240
|
-
* carries the ruled wording, the three numbers the ruling names (occupancy against the
|
|
241
|
-
* commit line, unmarked foldable mass the ladder can reach, pending marks), the verbs,
|
|
242
|
-
* and the one-round law. Never a question: continuing the task is the stated default,
|
|
243
|
-
* and the commit proceeds regardless.
|
|
244
|
-
*/
|
|
245
|
-
export function lastCallText(input: {
|
|
246
|
-
signals: CurationSignals;
|
|
247
|
-
unmarked: { spans: number; tokens: number };
|
|
248
|
-
pendingMarks: number;
|
|
249
|
-
/** Peek copies this exposure marked for reclaim, so the pin that vetoes one is timely. */
|
|
250
|
-
peekReclaims?: number;
|
|
251
|
-
/** The surfacing slate, already bounded and already logged; null on a silent pass. */
|
|
252
|
-
suggestion?: string | null;
|
|
253
|
-
toolName: string;
|
|
254
|
-
brandNoun?: string;
|
|
255
|
-
}): string {
|
|
256
|
-
const brand = contextBrand(input.brandNoun ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN);
|
|
257
|
-
const { signals } = input;
|
|
258
|
-
const occupancy = signals.occupancy === null ? "unmeasured" : `${Math.round(signals.occupancy * 100)}%`;
|
|
259
|
-
const peekReclaims = input.peekReclaims ?? 0;
|
|
260
|
-
return joinSurfacing(boundReceiptText([
|
|
261
|
-
`[${brand} last call] ${LAST_CALL_WORDING}`,
|
|
262
|
-
`Occupancy is ${occupancy} of the ${signals.budgetTokens}-token serving budget, at or past the ` +
|
|
263
|
-
`${Math.round(signals.maxTarget * 100)}% commit line. Unmarked foldable mass: ` +
|
|
264
|
-
`${input.unmarked.spans} span(s), about ${input.unmarked.tokens} tokens. ` +
|
|
265
|
-
`Pending marks: ${input.pendingMarks}.`,
|
|
266
|
-
// Stated only when there is one to veto: a standing sentence about zero peek copies
|
|
267
|
-
// is bytes every exposure pays for and no round ever acts on.
|
|
268
|
-
peekReclaims > 0
|
|
269
|
-
? `Peek copies reclaimed by this commit: ${peekReclaims}. Each duplicates a fold you can peek ` +
|
|
270
|
-
"again at any time, and its placeholder names that fold, so nothing becomes unreachable. " +
|
|
271
|
-
"Pin one to keep the copy raw."
|
|
272
|
-
: "",
|
|
273
|
-
`Marks: ${input.toolName} ` +
|
|
274
|
-
"{\"action\":\"fold\",\"marks\":[{\"ids\":[\"<start>\",\"<end>\"],\"brief\":\"<factual brief>\"}]} " +
|
|
275
|
-
"adds or widens several in one call. Pins: {\"action\":\"protect\",\"ids\":[\"<entry-id>\"]} holds " +
|
|
276
|
-
"entries raw through every fold, and {\"action\":\"unprotect\"} releases them.",
|
|
277
|
-
"This is one round: the commit proceeds on the pass after your next response with whatever marks " +
|
|
278
|
-
"exist. Continuing the task is the default; nothing here needs a reply.",
|
|
279
|
-
].filter(Boolean).join("\n"), MAX_LAST_CALL_TEXT_BYTES,
|
|
280
|
-
`[${brand} last call] ${LAST_CALL_WORDING}`), input.suggestion);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* One occupancy waypoint, stated once. Informatory and quiet: below the commit line
|
|
285
|
-
* nothing folds automatically, so the only useful sentence is where the window stands
|
|
286
|
-
* and that marks and pins made now shape the eventual commit.
|
|
287
|
-
*/
|
|
288
|
-
export function thresholdNoticeText(input: {
|
|
289
|
-
share: number;
|
|
290
|
-
occupancyTokens: number;
|
|
291
|
-
budgetTokens: number;
|
|
292
|
-
maxTarget: number;
|
|
293
|
-
toolName: string;
|
|
294
|
-
brandNoun?: string;
|
|
295
|
-
}): string {
|
|
296
|
-
const brand = contextBrand(input.brandNoun ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN);
|
|
297
|
-
return boundReceiptText(
|
|
298
|
-
`[${brand} notice] Context occupancy crossed ${Math.round(input.share * 100)}% of the ` +
|
|
299
|
-
`${input.budgetTokens}-token serving budget (${input.occupancyTokens} tokens). Nothing folds before ` +
|
|
300
|
-
`the fold commit at ${Math.round(input.maxTarget * 100)}%; marks and pins made now shape it: ` +
|
|
301
|
-
`${input.toolName} {"action":"fold","marks":[{"ids":["<start>","<end>"],"brief":"<factual brief>"}]} ` +
|
|
302
|
-
'or {"action":"protect","ids":["<entry-id>"]}.',
|
|
303
|
-
MAX_THRESHOLD_NOTICE_TEXT_BYTES,
|
|
304
|
-
`[${brand} notice] Context occupancy crossed ${Math.round(input.share * 100)}% of the serving budget.`,
|
|
305
68
|
);
|
|
306
69
|
}
|
|
307
70
|
|
|
308
|
-
/** One automatic context action, reported back as status rather than as advice. */
|
|
309
71
|
export interface ContextReceipt {
|
|
310
72
|
kind: string;
|
|
311
73
|
ordinal: number;
|
|
@@ -313,20 +75,14 @@ export interface ContextReceipt {
|
|
|
313
75
|
foldsCommitted: number;
|
|
314
76
|
foldsCreated: number;
|
|
315
77
|
freedTokens: number;
|
|
316
|
-
/** Measured occupancy either side of the action, in tokens. The impact, concretely. */
|
|
317
78
|
occupancyBefore: number | null;
|
|
318
79
|
occupancyAfter: number | null;
|
|
319
|
-
/** What the action actually folded, split the way an agent thinks about it. */
|
|
320
80
|
spansFolded: number;
|
|
321
81
|
toolResultsFolded: number;
|
|
322
|
-
/** Bite-sized splitting, when an oversized span became sequential folds. */
|
|
323
82
|
splitFolds: number;
|
|
324
83
|
splitFromChars: number;
|
|
325
|
-
/** Short stale spans a commit absorbed into their later neighbour fold. */
|
|
326
84
|
absorbedWedges: number;
|
|
327
|
-
/** Overflow recovery, when this action rebuilt a request the provider would reject. */
|
|
328
85
|
recovered: boolean;
|
|
329
|
-
/** Mass the agent's pins held out of this commit's reach, in bytes. */
|
|
330
86
|
protectedBytes: number;
|
|
331
87
|
note: string | null;
|
|
332
88
|
}
|
|
@@ -352,7 +108,6 @@ export function contextReceipt(input: Partial<ContextReceipt> & { kind: string;
|
|
|
352
108
|
};
|
|
353
109
|
}
|
|
354
110
|
|
|
355
|
-
/** Newest last, oldest evicted: a receipt block that can never grow into its own problem. */
|
|
356
111
|
export function withReceipt(
|
|
357
112
|
receipts: readonly ContextReceipt[],
|
|
358
113
|
receipt: ContextReceipt,
|
|
@@ -394,11 +149,6 @@ export function receiptLine(receipt: ContextReceipt): string {
|
|
|
394
149
|
return parts.filter(Boolean).join(" ");
|
|
395
150
|
}
|
|
396
151
|
|
|
397
|
-
/**
|
|
398
|
-
* The receipt block. Informatory, never exhortative: it reports what the runtime did,
|
|
399
|
-
* what it cost, and the verbs that correct it. Hard-bounded, because a report about
|
|
400
|
-
* bloat that becomes bloat has argued against itself.
|
|
401
|
-
*/
|
|
402
152
|
export function receiptBlockText(input: {
|
|
403
153
|
receipts: readonly ContextReceipt[];
|
|
404
154
|
toolName: string;
|
|
@@ -413,11 +163,6 @@ export function receiptBlockText(input: {
|
|
|
413
163
|
"{\"action\":\"rebrief\",\"id\":\"<fold-id>\",\"brief\":\"<factual brief>\"} replaces a brief that " +
|
|
414
164
|
"does not describe what you needed; {\"action\":\"reboundary\",\"id\":\"<fold-id>\"} returns a " +
|
|
415
165
|
"mis-cut fold to raw so you can fold the span you meant.";
|
|
416
|
-
// The verbs are the only part of this block an agent can act on, and they sit last, so
|
|
417
|
-
// a plain tail truncation drops them precisely when the report runs long -- which is
|
|
418
|
-
// when a commit did the most and correcting it matters most. Reserve them, and spend
|
|
419
|
-
// what remains on receipt lines newest-first: an older action that no longer fits is
|
|
420
|
-
// the right thing to lose, and its structured record is still in the ring.
|
|
421
166
|
const fixed = Buffer.byteLength(header, "utf8") + Buffer.byteLength(verbs, "utf8") + 1;
|
|
422
167
|
if (fixed > CONTEXT_RECEIPT_BLOCK_BYTES) {
|
|
423
168
|
return `[${brand} actions] Recent automatic context actions are unavailable this pass.`;
|
|
@@ -427,8 +172,6 @@ export function receiptBlockText(input: {
|
|
|
427
172
|
for (let index = input.receipts.length - 1; index >= 0; index -= 1) {
|
|
428
173
|
const line = receiptLine(input.receipts[index]);
|
|
429
174
|
if (Buffer.byteLength(line, "utf8") + 1 > remaining) {
|
|
430
|
-
// Not even the newest line fits whole: carry as much of it as the reservation
|
|
431
|
-
// leaves rather than reporting an action with no detail at all.
|
|
432
175
|
if (!kept.length && remaining > 1) kept.push(boundReceiptText(line, remaining - 1, ""));
|
|
433
176
|
break;
|
|
434
177
|
}
|