@alisio/core 0.8.0 → 0.10.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.
Files changed (58) hide show
  1. package/dist/analysis/data/engine-client.d.ts +2 -0
  2. package/dist/analysis/data/engine-client.js +11 -4
  3. package/dist/application.d.ts +11 -1
  4. package/dist/application.js +46 -0
  5. package/dist/changelog/data.js +42 -0
  6. package/dist/commands/builtins.js +6 -0
  7. package/dist/commands/catalog.d.ts +4 -2
  8. package/dist/commands/catalog.js +23 -1
  9. package/dist/config.d.ts +31 -0
  10. package/dist/config.js +31 -1
  11. package/dist/context/elide.d.ts +50 -0
  12. package/dist/context/elide.js +222 -0
  13. package/dist/context/format.d.ts +21 -0
  14. package/dist/context/format.js +81 -0
  15. package/dist/context/governance.d.ts +58 -0
  16. package/dist/context/governance.js +96 -0
  17. package/dist/context/invariants.d.ts +88 -0
  18. package/dist/context/invariants.js +141 -0
  19. package/dist/context/inventory.d.ts +22 -0
  20. package/dist/context/inventory.js +178 -0
  21. package/dist/context/report.d.ts +35 -0
  22. package/dist/context/report.js +120 -0
  23. package/dist/context/sources.d.ts +20 -0
  24. package/dist/context/sources.js +41 -0
  25. package/dist/context/types.d.ts +43 -0
  26. package/dist/context/types.js +5 -0
  27. package/dist/context/usage.d.ts +96 -0
  28. package/dist/context/usage.js +264 -0
  29. package/dist/context/util.d.ts +22 -0
  30. package/dist/context/util.js +36 -0
  31. package/dist/core/compaction.d.ts +7 -2
  32. package/dist/core/compaction.js +9 -3
  33. package/dist/core/contracts.d.ts +8 -0
  34. package/dist/core/runner.d.ts +67 -0
  35. package/dist/core/runner.js +272 -10
  36. package/dist/eval/compare.d.ts +1 -1
  37. package/dist/eval/compare.js +18 -1
  38. package/dist/eval/engine.js +31 -1
  39. package/dist/eval/report.js +44 -1
  40. package/dist/forge/javascript/hosts/process-host.d.ts +1 -1
  41. package/dist/forge/javascript/hosts/process-host.js +4 -1
  42. package/dist/forge/python/wasm-node.d.ts +1 -1
  43. package/dist/forge/python/wasm-node.js +4 -1
  44. package/dist/index.d.ts +8 -2
  45. package/dist/index.js +8 -2
  46. package/dist/plugins/host.d.ts +5 -0
  47. package/dist/plugins/host.js +15 -1
  48. package/dist/plugins/install.d.ts +50 -2
  49. package/dist/plugins/install.js +102 -12
  50. package/dist/plugins/local-install.d.ts +33 -0
  51. package/dist/plugins/local-install.js +121 -0
  52. package/dist/plugins/npm-runner.d.ts +30 -0
  53. package/dist/plugins/npm-runner.js +66 -0
  54. package/dist/plugins/tarball.d.ts +39 -0
  55. package/dist/plugins/tarball.js +277 -0
  56. package/dist/plugins/version-compat.d.ts +20 -0
  57. package/dist/plugins/version-compat.js +154 -0
  58. package/package.json +2 -2
@@ -0,0 +1,96 @@
1
+ import type { ContextSegmentKind, Message } from "@alisio/sdk";
2
+ import { type CallInfo } from "./util.ts";
3
+ /**
4
+ * Usage ledger of the Context Governor (spec 012 phase 4, section 3.4). It measures, per tool
5
+ * result, whether the agent later acted on the same file path. The numbers are advisory and
6
+ * heuristic: they never drive elision, they are never persisted, and they hold no content (only
7
+ * normalized paths in memory, never in a summary).
8
+ *
9
+ * Pure and deterministic: the summary depends only on the sequence of histories handed to `record`
10
+ * and on the workspace.
11
+ */
12
+ export interface UsageCounts {
13
+ observed: number;
14
+ used: number;
15
+ }
16
+ export interface UsageSummary {
17
+ /** Attributable results the model saw in full in a request older than the latest one. */
18
+ observed: number;
19
+ /** Of those, the ones a later call acted on (same path; re-reads excluded). */
20
+ used: number;
21
+ byKind: Partial<Record<ContextSegmentKind, UsageCounts>>;
22
+ /** Per tool name, keys sorted. */
23
+ byTool: Record<string, UsageCounts>;
24
+ /** Results with no path to match (bg_output, web results, writes, errors, shell output...). */
25
+ unattributed: number;
26
+ /** Attributable results seen only in the latest request: not decidable yet. */
27
+ pending: number;
28
+ /** Results the model only ever saw as an elision stub (`on` mode). */
29
+ stubOnly: number;
30
+ /** Calls that re-read a path whose earlier read is still in the ledger. */
31
+ rereads: number;
32
+ /** Entries dropped by the size cap. */
33
+ evicted: number;
34
+ }
35
+ export interface UsageLedgerOptions {
36
+ workspace: string;
37
+ /** Entries kept (default 4096); the oldest are evicted first. */
38
+ maxEntries?: number;
39
+ /** Keys kept per entry (default 32). */
40
+ maxKeysPerEntry?: number;
41
+ /** Paths a tool declares for an input (`ToolDefinition.paths`); may throw. */
42
+ pathsOf?: (tool: string, args: Record<string, unknown>) => string[] | undefined;
43
+ /** Whether a tool is side-effect free: only those results can be attributed by declared paths. */
44
+ isRead?: (tool: string) => boolean;
45
+ }
46
+ export declare const usedRatio: (c: UsageCounts) => number | undefined;
47
+ /** POSIX-normalized, workspace-relative when inside it, no `./`, no trailing `/`, case kept. */
48
+ export declare function normalizePath(path: string, workspace: string): string;
49
+ type PathsOf = UsageLedgerOptions["pathsOf"];
50
+ /**
51
+ * Keys that identify what a tool result is about, or undefined when it cannot be attributed.
52
+ * `read_file`: its path. `list_files`: the listed files. `search_text`: the paths of the ripgrep
53
+ * `match` lines (the `path` argument, often ".", is ignored). Other tools only through `isRead`
54
+ * plus their declared `paths` (done by the ledger).
55
+ */
56
+ export declare function attributionKeys(call: CallInfo, text: string, workspace: string, pathsOf?: PathsOf, maxKeys?: number): string[] | undefined;
57
+ /** What a later call names: its `path` argument, its declared paths and, for processes, the command. */
58
+ export declare function referencedPaths(call: CallInfo, workspace: string, pathsOf?: PathsOf): {
59
+ paths: string[];
60
+ command?: string;
61
+ };
62
+ export declare class UsageLedger {
63
+ private readonly workspace;
64
+ private readonly maxEntries;
65
+ private readonly maxKeys;
66
+ private readonly pathsOf;
67
+ private readonly isRead;
68
+ /** Insertion order is eviction order. */
69
+ private readonly entries;
70
+ /** key -> call ids of the entries that carry it. */
71
+ private readonly index;
72
+ private readonly retired;
73
+ private seenCalls;
74
+ private latest;
75
+ private reread;
76
+ private evictedCount;
77
+ constructor(options: UsageLedgerOptions);
78
+ /** Number of seams recorded; the runner numbers the next one `seams + 1`. */
79
+ get seams(): number;
80
+ /**
81
+ * Feeds the history of one model request. `request` must grow on every call; `stubbed` holds the
82
+ * call ids whose result the model got as an elision stub in this request.
83
+ */
84
+ record(request: number, history: Message[], stubbed: ReadonlySet<string>): void;
85
+ private add;
86
+ private drop;
87
+ private credit;
88
+ /**
89
+ * Whether a later call acted on the result of `callId`: `true` or `false` once the result is
90
+ * decidable, `undefined` when it cannot be (unknown, not attributable, only seen as a stub, or
91
+ * seen only in the latest request).
92
+ */
93
+ decided(callId: string): boolean | undefined;
94
+ summary(): UsageSummary;
95
+ }
96
+ export {};
@@ -0,0 +1,264 @@
1
+ import { posix } from "node:path";
2
+ import { describeCall, parseObject, resultText } from "./util.js";
3
+ export const usedRatio = (c) => c.observed ? c.used / c.observed : undefined;
4
+ const DEFAULT_MAX_ENTRIES = 4096;
5
+ const DEFAULT_MAX_KEYS = 32;
6
+ /** POSIX-normalized, workspace-relative when inside it, no `./`, no trailing `/`, case kept. */
7
+ export function normalizePath(path, workspace) {
8
+ let p = posix.normalize(path);
9
+ const ws = posix.normalize(workspace).replace(/\/+$/, "");
10
+ if (ws && p === ws)
11
+ return ".";
12
+ if (ws && p.startsWith(`${ws}/`))
13
+ p = p.slice(ws.length + 1);
14
+ if (p.length > 1 && p.endsWith("/"))
15
+ p = p.slice(0, -1);
16
+ return p;
17
+ }
18
+ function declaredPaths(call, pathsOf) {
19
+ if (!call.args || !pathsOf)
20
+ return [];
21
+ try {
22
+ return (pathsOf(call.name, call.args) ?? []).filter((p) => typeof p === "string");
23
+ }
24
+ catch {
25
+ return [];
26
+ }
27
+ }
28
+ const unique = (items, max) => [...new Set(items)].slice(0, max);
29
+ /**
30
+ * Keys that identify what a tool result is about, or undefined when it cannot be attributed.
31
+ * `read_file`: its path. `list_files`: the listed files. `search_text`: the paths of the ripgrep
32
+ * `match` lines (the `path` argument, often ".", is ignored). Other tools only through `isRead`
33
+ * plus their declared `paths` (done by the ledger).
34
+ */
35
+ export function attributionKeys(call, text, workspace, pathsOf, maxKeys = DEFAULT_MAX_KEYS) {
36
+ let found = [];
37
+ if (call.name === "read_file") {
38
+ if (typeof call.args?.path === "string")
39
+ found = [call.args.path];
40
+ }
41
+ else if (call.name === "list_files") {
42
+ const files = parseObject(text)?.files;
43
+ if (Array.isArray(files))
44
+ found = files.filter((f) => typeof f === "string");
45
+ }
46
+ else if (call.name === "search_text") {
47
+ const stdout = parseObject(text)?.stdout;
48
+ if (typeof stdout === "string") {
49
+ for (const line of stdout.split("\n")) {
50
+ if (!line.includes('"match"'))
51
+ continue;
52
+ const event = parseObject(line);
53
+ const data = event?.type === "match" ? event.data : undefined;
54
+ if (typeof data?.path?.text === "string")
55
+ found.push(data.path.text);
56
+ }
57
+ }
58
+ }
59
+ else {
60
+ found = declaredPaths(call, pathsOf);
61
+ }
62
+ const keys = unique(found.map((p) => normalizePath(p, workspace)), maxKeys);
63
+ return keys.length > 0 ? keys : undefined;
64
+ }
65
+ /** What a later call names: its `path` argument, its declared paths and, for processes, the command. */
66
+ export function referencedPaths(call, workspace, pathsOf) {
67
+ const raw = [...(typeof call.args?.path === "string" ? [call.args.path] : [])];
68
+ raw.push(...declaredPaths(call, pathsOf));
69
+ const paths = unique(raw.map((p) => normalizePath(p, workspace)), DEFAULT_MAX_KEYS);
70
+ if (call.name !== "run_process" && call.name !== "shell")
71
+ return { paths };
72
+ const args = call.args?.args;
73
+ const command = [
74
+ typeof call.args?.command === "string" ? call.args.command : "",
75
+ ...(Array.isArray(args) ? args.filter((a) => typeof a === "string") : []),
76
+ ]
77
+ .filter(Boolean)
78
+ .join(" ");
79
+ return { paths, ...(command ? { command } : {}) };
80
+ }
81
+ const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
82
+ /** Heuristic: the key as a whole word of the command, delimited by whitespace, quotes and `=:(),;`. */
83
+ const inCommand = (command, key) => command.includes(key) &&
84
+ new RegExp(`(^|[\\s"'=:(])${escapeRegExp(key)}($|[\\s"'):,;])`).test(command);
85
+ export class UsageLedger {
86
+ workspace;
87
+ maxEntries;
88
+ maxKeys;
89
+ pathsOf;
90
+ isRead;
91
+ /** Insertion order is eviction order. */
92
+ entries = new Map();
93
+ /** key -> call ids of the entries that carry it. */
94
+ index = new Map();
95
+ retired = new Set();
96
+ seenCalls = new Set();
97
+ latest = 0;
98
+ reread = 0;
99
+ evictedCount = 0;
100
+ constructor(options) {
101
+ this.workspace = options.workspace;
102
+ this.maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_MAX_ENTRIES);
103
+ this.maxKeys = Math.max(1, options.maxKeysPerEntry ?? DEFAULT_MAX_KEYS);
104
+ this.pathsOf = options.pathsOf;
105
+ this.isRead = options.isRead ?? (() => true);
106
+ }
107
+ /** Number of seams recorded; the runner numbers the next one `seams + 1`. */
108
+ get seams() {
109
+ return this.latest;
110
+ }
111
+ /**
112
+ * Feeds the history of one model request. `request` must grow on every call; `stubbed` holds the
113
+ * call ids whose result the model got as an elision stub in this request.
114
+ */
115
+ record(request, history, stubbed) {
116
+ this.latest = Math.max(this.latest, request);
117
+ const calls = new Map();
118
+ const fresh = [];
119
+ const results = [];
120
+ for (const m of history) {
121
+ if (m.role === "assistant") {
122
+ for (const c of m.calls) {
123
+ const info = describeCall(c.name, c.arguments);
124
+ calls.set(c.id, info);
125
+ if (!this.seenCalls.has(c.id))
126
+ fresh.push({ id: c.id, call: info });
127
+ }
128
+ }
129
+ else if (m.role === "tool")
130
+ results.push(m);
131
+ }
132
+ // (1) Credit the calls that are new at this seam against what was observed before it.
133
+ for (const { id, call } of fresh)
134
+ this.credit(id, call, request);
135
+ this.seenCalls = new Set(calls.keys());
136
+ // (2) Add the results not seen yet.
137
+ for (const m of results) {
138
+ if (this.entries.has(m.callId) || this.retired.has(m.callId))
139
+ continue;
140
+ const call = calls.get(m.callId);
141
+ const isError = m.result.isError === true;
142
+ let keys;
143
+ if (call && !isError && (this.isRead(call.name) || call.name === "read_file"))
144
+ keys = attributionKeys(call, resultText(m), this.workspace, this.pathsOf, this.maxKeys);
145
+ this.add({
146
+ callId: m.callId,
147
+ tool: call?.name ?? "tool",
148
+ observedAt: request,
149
+ stubOnly: stubbed.has(m.callId),
150
+ keys: keys ?? [],
151
+ used: false,
152
+ });
153
+ }
154
+ }
155
+ add(entry) {
156
+ this.entries.set(entry.callId, entry);
157
+ for (const key of entry.keys) {
158
+ const ids = this.index.get(key) ?? new Set();
159
+ ids.add(entry.callId);
160
+ this.index.set(key, ids);
161
+ }
162
+ while (this.entries.size > this.maxEntries) {
163
+ const oldest = this.entries.keys().next().value;
164
+ if (oldest === undefined)
165
+ break;
166
+ this.drop(oldest);
167
+ this.evictedCount++;
168
+ }
169
+ }
170
+ drop(callId) {
171
+ const entry = this.entries.get(callId);
172
+ if (!entry)
173
+ return;
174
+ this.entries.delete(callId);
175
+ for (const key of entry.keys) {
176
+ const ids = this.index.get(key);
177
+ ids?.delete(callId);
178
+ if (ids?.size === 0)
179
+ this.index.delete(key);
180
+ }
181
+ this.retired.add(callId);
182
+ if (this.retired.size > this.maxEntries) {
183
+ const first = this.retired.values().next().value;
184
+ if (first !== undefined)
185
+ this.retired.delete(first);
186
+ }
187
+ }
188
+ credit(callId, call, request) {
189
+ const { paths, command } = referencedPaths(call, this.workspace, this.pathsOf);
190
+ const hit = new Set();
191
+ for (const path of paths)
192
+ for (const id of this.index.get(path) ?? [])
193
+ hit.add(id);
194
+ if (command)
195
+ for (const [key, ids] of this.index) {
196
+ if (inCommand(command, key) || inCommand(command, `${this.workspace}/${key}`))
197
+ for (const id of ids)
198
+ hit.add(id);
199
+ }
200
+ let counted = false;
201
+ for (const id of hit) {
202
+ const entry = this.entries.get(id);
203
+ if (!entry || entry.observedAt >= request || entry.callId === callId || entry.stubOnly)
204
+ continue;
205
+ if (call.name === "read_file" && entry.tool === "read_file") {
206
+ // A re-read of the same path is the model checking again, not acting on it.
207
+ counted ||= true;
208
+ continue;
209
+ }
210
+ entry.used = true;
211
+ }
212
+ if (counted)
213
+ this.reread++;
214
+ }
215
+ /**
216
+ * Whether a later call acted on the result of `callId`: `true` or `false` once the result is
217
+ * decidable, `undefined` when it cannot be (unknown, not attributable, only seen as a stub, or
218
+ * seen only in the latest request).
219
+ */
220
+ decided(callId) {
221
+ const e = this.entries.get(callId);
222
+ if (!e || e.stubOnly || e.keys.length === 0 || e.observedAt >= this.latest)
223
+ return undefined;
224
+ return e.used;
225
+ }
226
+ summary() {
227
+ const byTool = new Map();
228
+ let observed = 0;
229
+ let used = 0;
230
+ let unattributed = 0;
231
+ let pending = 0;
232
+ let stubOnly = 0;
233
+ for (const e of this.entries.values()) {
234
+ if (e.stubOnly)
235
+ stubOnly++;
236
+ else if (e.keys.length === 0)
237
+ unattributed++;
238
+ else if (e.observedAt >= this.latest)
239
+ pending++;
240
+ else {
241
+ const counts = byTool.get(e.tool) ?? { observed: 0, used: 0 };
242
+ counts.observed++;
243
+ observed++;
244
+ if (e.used) {
245
+ counts.used++;
246
+ used++;
247
+ }
248
+ byTool.set(e.tool, counts);
249
+ }
250
+ }
251
+ const sorted = [...byTool].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
252
+ return {
253
+ observed,
254
+ used,
255
+ byKind: observed > 0 || pending > 0 ? { tool_result: { observed, used } } : {},
256
+ byTool: Object.fromEntries(sorted),
257
+ unattributed,
258
+ pending,
259
+ stubOnly,
260
+ rereads: this.reread,
261
+ evicted: this.evictedCount,
262
+ };
263
+ }
264
+ }
@@ -0,0 +1,22 @@
1
+ import type { Message } from "@alisio/sdk";
2
+ /** Tools whose `path` argument names the file the call reads or changes. */
3
+ export declare const PATH_TOOLS: Set<string>;
4
+ export declare const WRITE_TOOLS: Set<string>;
5
+ export declare const sha: (text: string) => string;
6
+ /** Stable 12-hex id of a segment: kind, call id or position, and a hash of its content. */
7
+ export declare const segmentId: (kind: string, key: string | number, content: string) => string;
8
+ export declare function parseObject(text: string): Record<string, unknown> | undefined;
9
+ /** Key-order independent serialization, so equal arguments compare equal. */
10
+ export declare function stableStringify(value: unknown): string;
11
+ /** Text parts of a tool result joined with newlines (what the model sees). */
12
+ export declare const resultText: (m: Extract<Message, {
13
+ role: "tool";
14
+ }>) => string;
15
+ export interface CallInfo {
16
+ name: string;
17
+ arguments: string;
18
+ /** Parsed JSON arguments when they form an object, else undefined. */
19
+ args?: Record<string, unknown>;
20
+ path?: string;
21
+ }
22
+ export declare function describeCall(name: string, argumentsText: string): CallInfo;
@@ -0,0 +1,36 @@
1
+ import { createHash } from "node:crypto";
2
+ /** Tools whose `path` argument names the file the call reads or changes. */
3
+ export const PATH_TOOLS = new Set(["read_file", "write_file", "edit_file"]);
4
+ export const WRITE_TOOLS = new Set(["write_file", "edit_file"]);
5
+ export const sha = (text) => createHash("sha256").update(text).digest("hex");
6
+ /** Stable 12-hex id of a segment: kind, call id or position, and a hash of its content. */
7
+ export const segmentId = (kind, key, content) => sha(`${kind}\0${key}\0${sha(content)}`).slice(0, 12);
8
+ export function parseObject(text) {
9
+ try {
10
+ const value = JSON.parse(text);
11
+ return value && typeof value === "object" && !Array.isArray(value)
12
+ ? value
13
+ : undefined;
14
+ }
15
+ catch {
16
+ return undefined;
17
+ }
18
+ }
19
+ /** Key-order independent serialization, so equal arguments compare equal. */
20
+ export function stableStringify(value) {
21
+ if (Array.isArray(value))
22
+ return `[${value.map(stableStringify).join(",")}]`;
23
+ if (value && typeof value === "object")
24
+ return `{${Object.entries(value)
25
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
26
+ .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)
27
+ .join(",")}}`;
28
+ return JSON.stringify(value) ?? "null";
29
+ }
30
+ /** Text parts of a tool result joined with newlines (what the model sees). */
31
+ export const resultText = (m) => m.result.content.map((p) => (p.type === "text" ? p.text : "")).join("\n");
32
+ export function describeCall(name, argumentsText) {
33
+ const args = parseObject(argumentsText);
34
+ const path = PATH_TOOLS.has(name) && typeof args?.path === "string" ? args.path : undefined;
35
+ return { name, arguments: argumentsText, ...(args ? { args } : {}), ...(path ? { path } : {}) };
36
+ }
@@ -103,8 +103,13 @@ export declare function reduceMessagesToBudget(messages: Message[], targetChars:
103
103
  */
104
104
  export declare function serializeForSummary(messages: Message[], maxItemChars?: number): string;
105
105
  export declare function renderCheckpoint(c: CompactionCheckpoint): string;
106
- /** Summarizer instructions: JSON checkpoint plus any extension-requested fields. */
107
- export declare function checkpointInstructions(extraInstructions?: string[], fields?: Record<string, string>): string;
106
+ /** Top-level field the governed summarizer fills with verbatim user quotes (spec 012, phase 3). */
107
+ export declare const INVARIANTS_FIELD = "alisio_invariants";
108
+ /**
109
+ * Summarizer instructions: JSON checkpoint plus any extension-requested fields. `governed` (the
110
+ * Context Governor in `on` mode) asks for one more field; without it the text is unchanged.
111
+ */
112
+ export declare function checkpointInstructions(extraInstructions?: string[], fields?: Record<string, string>, governed?: boolean): string;
108
113
  export interface CheckpointOutput {
109
114
  structured: boolean;
110
115
  checkpoint?: CompactionCheckpoint;
@@ -306,9 +306,15 @@ export function renderCheckpoint(c) {
306
306
  section("Relevant files", c.relevantFiles),
307
307
  ].join("\n\n");
308
308
  }
309
- /** Summarizer instructions: JSON checkpoint plus any extension-requested fields. */
310
- export function checkpointInstructions(extraInstructions = [], fields = {}) {
311
- const extra = Object.entries(fields)
309
+ /** Top-level field the governed summarizer fills with verbatim user quotes (spec 012, phase 3). */
310
+ export const INVARIANTS_FIELD = "alisio_invariants";
311
+ const INVARIANTS_ASK = `array of strings: verbatim quotes (exact substrings) of USER messages that state durable instructions or constraints; never paraphrase; [] if none`;
312
+ /**
313
+ * Summarizer instructions: JSON checkpoint plus any extension-requested fields. `governed` (the
314
+ * Context Governor in `on` mode) asks for one more field; without it the text is unchanged.
315
+ */
316
+ export function checkpointInstructions(extraInstructions = [], fields = {}, governed = false) {
317
+ const extra = Object.entries(governed ? { ...fields, [INVARIANTS_FIELD]: INVARIANTS_ASK } : fields)
312
318
  .map(([name, description]) => `,\n "${name}": ${description}`)
313
319
  .join("");
314
320
  return [
@@ -177,6 +177,14 @@ export interface SessionStore {
177
177
  eventsPage?(session: string, options?: PageOptions): EventPage;
178
178
  /** Marks queued/running runs whose owner process is not alive as interrupted. */
179
179
  interruptRuns?(): number;
180
+ /**
181
+ * Atomic read-modify-write of the session's `options` (`SQLiteStore` implements it). Without it
182
+ * the runner keeps what it would store there, for the context invariants, in memory.
183
+ */
184
+ mutateSessionOptions?<T>(id: string, change: (current: Record<string, unknown>) => {
185
+ patch?: Record<string, unknown>;
186
+ value: T;
187
+ }): T;
180
188
  }
181
189
  export interface ContextSource {
182
190
  /** System instructions; `sessionId` scopes lazily attached nested instructions. */
@@ -1,4 +1,7 @@
1
1
  import { type Attachment, type DatasetRef, type Message, type ModelProvider, type RunEvent, type ToolDefinition, type ToolResult } from "@alisio/sdk";
2
+ import { type ContextGovernorOptions, type GovernanceSnapshot } from "../context/governance.ts";
3
+ import { type Invariant, type InvariantSources } from "../context/invariants.ts";
4
+ import { UsageLedger, type UsageSummary } from "../context/usage.ts";
2
5
  import type { ProofRecorder } from "../proof/recorder.ts";
3
6
  import type { PathAccess } from "../runtime/access.ts";
4
7
  import type { ApprovalHandler, ArtifactPublisherFactory, CapabilityGate, ContextSource, DecisionsFactory, Policy, RunnerExtensions, SessionStore } from "./contracts.ts";
@@ -62,6 +65,17 @@ export interface RunnerOptions {
62
65
  * unknown (or absurdly large, see `MAX_TRUSTED_WINDOW`) the char budget falls back. */
63
66
  contextWindow?: (model: string) => number | undefined;
64
67
  compaction?: CompactionSettings;
68
+ /**
69
+ * Context Governor (spec 012). Unset or `mode: "off"` changes nothing. `shadow` computes what
70
+ * would be elided and keeps it in `governance()` while the request stays byte-identical; `on`
71
+ * sends the projected history. Compaction, limits and persistence always use the raw history.
72
+ */
73
+ contextGovernor?: ContextGovernorOptions;
74
+ /**
75
+ * Live sources of the `<invariants>` block (goal, plan, Proof criteria, grants). Only read when
76
+ * the governor is `shadow` or `on`; a missing source leaves its section out.
77
+ */
78
+ invariantSources?: InvariantSources;
65
79
  /** When set, write/process tools not allowed by the policy are offered and ask first. */
66
80
  approve?: ApprovalHandler;
67
81
  /**
@@ -194,6 +208,16 @@ export declare class AgentRunner {
194
208
  private reported;
195
209
  /** Conversation size in characters right after the last automatic compaction, per session. */
196
210
  private compactedChars;
211
+ /** What the Context Governor decided for the latest request of each session (shadow and on). */
212
+ private governed;
213
+ /** Usage ledger per session (shadow and on; process-local, LRU of `USAGE_LEDGER_SESSIONS`). */
214
+ private usage;
215
+ /** Effects the user approved for the whole session, per session (`on` mode; process-local). */
216
+ private sessionEffects;
217
+ /** Instruction quotes of a session whose store cannot keep session options (process-local). */
218
+ private invariantMemory;
219
+ /** Quotes promoted and refused by the compactions of this process, per session. */
220
+ private promotions;
197
221
  /** Where human waits are opened; the clocks of the active runs listen to it. */
198
222
  readonly humanWaits: HumanWaits;
199
223
  constructor(options: RunnerOptions);
@@ -262,6 +286,49 @@ export declare class AgentRunner {
262
286
  }>;
263
287
  /** The output-token budget for `model`: user value, else catalog maximum, else fallback. */
264
288
  private outputLimit;
289
+ /** Latest Context Governor decision for a session (core-internal; counts and ids only). */
290
+ governance(sessionId: string): GovernanceSnapshot | undefined;
291
+ /**
292
+ * Heuristic usage of the tool results this process showed the model in a session (shadow and on
293
+ * only; undefined otherwise). Lost on restart; advisory, it never drives elision.
294
+ */
295
+ contextUsage(sessionId: string): UsageSummary | undefined;
296
+ private usageLedger;
297
+ /**
298
+ * Forgets the user instructions kept for a session: the stored `contextInvariants` and every
299
+ * in-memory entry. Core-internal; there is no per-item removal before phase 5.
300
+ */
301
+ clearInvariants(sessionId: string): void;
302
+ /** The stored instruction list of a session, or undefined for old or never-compacted sessions. */
303
+ private invariantState;
304
+ /**
305
+ * The `<invariants>` block for the next request of a session: empty before the first compaction
306
+ * (a history that starts with a checkpoint, or a stored compaction count). Built fresh from the
307
+ * stored instructions and the live sources, never saved.
308
+ */
309
+ private invariantBlock;
310
+ /** The collected invariants, or undefined before the first compaction (see `invariantBlock`). */
311
+ private invariantList;
312
+ /**
313
+ * What the `ContextReport` of a session is built from (spec 012 phase 5): the governor settings,
314
+ * the active history, the invariants of the next request and the in-memory usage ledger. Read
315
+ * only. `off` and runners without a governor return no history, because `off` measures nothing.
316
+ */
317
+ contextInputs(sessionId: string): {
318
+ governor: ContextGovernorOptions;
319
+ messages: Message[];
320
+ invariants: {
321
+ items: Array<{
322
+ kind: Invariant["kind"];
323
+ text: string;
324
+ }>;
325
+ omitted: number;
326
+ };
327
+ ledger?: UsageLedger;
328
+ governance?: GovernanceSnapshot;
329
+ } | undefined;
330
+ /** Keeps the user's verbatim quotes the summarizer proposed (see `promoteInstructions`). */
331
+ private promoteInvariants;
265
332
  /** Change the model used by subsequent turns of a session; recorded in the store. */
266
333
  setModel(sessionId: string, model: string): void;
267
334
  /** Manually compact a session outside a run. */