@vincemakes/kiso-runtime 0.43.0 → 0.45.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/dist/context-anchor.d.ts +1 -1
- package/dist/context-anchor.js +1 -18
- package/dist/estimate-tokens.d.ts +23 -0
- package/dist/estimate-tokens.js +85 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.js +5 -4
- package/dist/request-budget.js +1 -1
- package/dist/run.d.ts +24 -2
- package/dist/run.js +71 -6
- package/dist/session.d.ts +0 -15
- package/dist/session.js +1 -28
- package/dist/summarize.d.ts +3 -2
- package/dist/summarize.js +5 -3
- package/dist/trace/manifest.js +1 -1
- package/package.json +6 -6
- package/dist/task-assessment.d.ts +0 -93
- package/dist/task-assessment.js +0 -165
package/dist/context-anchor.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* differently), or one of the STALE_AFTER events after it, each of which
|
|
16
16
|
* changes what the next request carries.
|
|
17
17
|
*/
|
|
18
|
-
import {
|
|
18
|
+
import type { Event, Usage } from "@vincemakes/kiso-core";
|
|
19
19
|
/**
|
|
20
20
|
* `usageTotal` turns one usage event into the tokens that request put in
|
|
21
21
|
* the context: its canonical prompt (fresh + cache read + cache write — the
|
package/dist/context-anchor.js
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* 0.40.0 (the owner's session) — what the context holds, anchored on the
|
|
3
|
-
* last BILLED request.
|
|
4
|
-
*
|
|
5
|
-
* The estimate alone read a Chinese-heavy context as ~470k while the
|
|
6
|
-
* provider billed 732,448 input tokens on a 1M window: the ctx row said
|
|
7
|
-
* "~53% left" and the microcompact trigger (window/2) never fired. The
|
|
8
|
-
* provider's own count of the last request is the truth; only what the log
|
|
9
|
-
* appended after it is estimated.
|
|
10
|
-
*
|
|
11
|
-
* The anchor is refused — the caller falls back to the estimate — when no
|
|
12
|
-
* bill describes the context any more: no usage yet, a usage the provider
|
|
13
|
-
* did not report (`known: false`), a usage at or before `floorSeq` (the
|
|
14
|
-
* session switched models since, and a different tokenizer counts
|
|
15
|
-
* differently), or one of the STALE_AFTER events after it, each of which
|
|
16
|
-
* changes what the next request carries.
|
|
17
|
-
*/
|
|
18
|
-
import { estimateTokens } from "@vincemakes/kiso-core";
|
|
1
|
+
import { estimateTokens } from "./estimate-tokens.js";
|
|
19
2
|
const STALE_AFTER = new Set([
|
|
20
3
|
"compacted",
|
|
21
4
|
"microcompacted",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The token estimator: runtime context-accounting policy.
|
|
3
|
+
*
|
|
4
|
+
* A stable, monotone chars/4 proxy for the size of a message list. The
|
|
5
|
+
* runtime's context policy, request budget, summary layer, context anchor
|
|
6
|
+
* and request trace read it; the kernel never did.
|
|
7
|
+
*
|
|
8
|
+
* ADR-0043 Amendment 13: moved here unchanged from the core package
|
|
9
|
+
* (`kernel/compaction.ts`). Since ADR-0044 moved compaction into the
|
|
10
|
+
* microcompact boundary, that file held only this estimator, and nothing
|
|
11
|
+
* in core called it — so it left the counted kernel instead of the cap
|
|
12
|
+
* moving.
|
|
13
|
+
*/
|
|
14
|
+
import type { Message } from "@vincemakes/kiso-core";
|
|
15
|
+
/**
|
|
16
|
+
* Rough token estimate (chars/4 + structural overhead). Calibration-free on
|
|
17
|
+
* purpose: context economy only needs a stable MONOTONE proxy, not an exact
|
|
18
|
+
* count — the threshold absorbs the error (the predecessor's ADR-0007). The proxy's
|
|
19
|
+
* three-word contract, and the pins that hold it, are
|
|
20
|
+
* packages/core/tests/sc1b-estimator.test.ts (it stays with the
|
|
21
|
+
* kernel-loop cases it also pins).
|
|
22
|
+
*/
|
|
23
|
+
export declare function estimateTokens(messages: readonly Message[]): number;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The token estimator: runtime context-accounting policy.
|
|
3
|
+
*
|
|
4
|
+
* A stable, monotone chars/4 proxy for the size of a message list. The
|
|
5
|
+
* runtime's context policy, request budget, summary layer, context anchor
|
|
6
|
+
* and request trace read it; the kernel never did.
|
|
7
|
+
*
|
|
8
|
+
* ADR-0043 Amendment 13: moved here unchanged from the core package
|
|
9
|
+
* (`kernel/compaction.ts`). Since ADR-0044 moved compaction into the
|
|
10
|
+
* microcompact boundary, that file held only this estimator, and nothing
|
|
11
|
+
* in core called it — so it left the counted kernel instead of the cap
|
|
12
|
+
* moving.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* What a non-text block (an image) contributes. There is no character count
|
|
16
|
+
* to proxy, and this module refuses to guess a provider's image
|
|
17
|
+
* tokenization; the figure exists so a block is never worth ZERO, which is
|
|
18
|
+
* what MONOTONE requires. Deliberately small: under-stating an image is a
|
|
19
|
+
* threshold that fires slightly late, over-stating it is one that fires on
|
|
20
|
+
* a conversation that was never large.
|
|
21
|
+
*/
|
|
22
|
+
const NON_TEXT_BLOCK_TOKENS = 8;
|
|
23
|
+
/**
|
|
24
|
+
* chars/4 over a `string | ContentBlock[]` content field.
|
|
25
|
+
*
|
|
26
|
+
* SC-1b ①: the array arm must be summed BLOCK BY BLOCK. Reading `.length`
|
|
27
|
+
* off it yields the block COUNT — a five-block 50 KB message scored ~2
|
|
28
|
+
* tokens, and the live microcompact threshold believed it.
|
|
29
|
+
*/
|
|
30
|
+
function contentTokens(content) {
|
|
31
|
+
if (typeof content === "string")
|
|
32
|
+
return textTokens(content);
|
|
33
|
+
let tokens = 0;
|
|
34
|
+
for (const block of content) {
|
|
35
|
+
tokens += block.type === "text" ? textTokens(block.text) : NON_TEXT_BLOCK_TOKENS;
|
|
36
|
+
}
|
|
37
|
+
return tokens;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 0.40.0 (the owner's session): chars/4, except that a CJK character counts
|
|
41
|
+
* as one token — which is about what a tokenizer spends on one. Plain
|
|
42
|
+
* chars/4 read a Chinese-heavy 730k context as ~470k, so the thresholds
|
|
43
|
+
* never fired and the ctx row lied. Text without CJK scores exactly what
|
|
44
|
+
* it scored before. A counting loop, not a regex match: the text can be
|
|
45
|
+
* megabytes, and a match array would hold one entry per character.
|
|
46
|
+
*/
|
|
47
|
+
function textTokens(text) {
|
|
48
|
+
let cjk = 0;
|
|
49
|
+
for (let i = 0; i < text.length; i++) {
|
|
50
|
+
const c = text.charCodeAt(i);
|
|
51
|
+
// kana, CJK ext A, unified ideographs, Hangul syllables,
|
|
52
|
+
// compatibility ideographs, full-width forms
|
|
53
|
+
if ((c >= 0x3040 && c <= 0x30ff) || (c >= 0x3400 && c <= 0x4dbf) || (c >= 0x4e00 && c <= 0x9fff) || (c >= 0xac00 && c <= 0xd7af) || (c >= 0xf900 && c <= 0xfaff) || (c >= 0xff00 && c <= 0xffef))
|
|
54
|
+
cjk++;
|
|
55
|
+
}
|
|
56
|
+
return cjk + Math.ceil((text.length - cjk) / 4);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Rough token estimate (chars/4 + structural overhead). Calibration-free on
|
|
60
|
+
* purpose: context economy only needs a stable MONOTONE proxy, not an exact
|
|
61
|
+
* count — the threshold absorbs the error (the predecessor's ADR-0007). The proxy's
|
|
62
|
+
* three-word contract, and the pins that hold it, are
|
|
63
|
+
* packages/core/tests/sc1b-estimator.test.ts (it stays with the
|
|
64
|
+
* kernel-loop cases it also pins).
|
|
65
|
+
*/
|
|
66
|
+
export function estimateTokens(messages) {
|
|
67
|
+
let total = 0;
|
|
68
|
+
for (const msg of messages) {
|
|
69
|
+
if (msg.role === "user") {
|
|
70
|
+
total += contentTokens(msg.content);
|
|
71
|
+
}
|
|
72
|
+
else if (msg.role === "assistant") {
|
|
73
|
+
for (const block of msg.blocks) {
|
|
74
|
+
total +=
|
|
75
|
+
block.type === "text"
|
|
76
|
+
? textTokens(block.text)
|
|
77
|
+
: textTokens(JSON.stringify(block.input)) + 20;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
total += contentTokens(msg.content) + 10;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return total;
|
|
85
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -24,15 +24,14 @@ export { AgentSession, AgentSession as Session } from "./session.js";
|
|
|
24
24
|
export type { SideQueryOptions } from "./session.js";
|
|
25
25
|
export { PoisonedSessionError, ResumeBlockedError } from "./session.js";
|
|
26
26
|
export type { ApprovalRequest, CompactInfo, ContextPolicy, SessionConfig, SummarizeResult } from "./session.js";
|
|
27
|
-
export { Run } from "./run.js";
|
|
27
|
+
export { Run, RunClosedError } from "./run.js";
|
|
28
28
|
export { SessionStore, StaleWriterError, StoreCorruptionError } from "./store.js";
|
|
29
29
|
export type { Event, SessionMeta, StoreRecord } from "./store.js";
|
|
30
30
|
export { disposeExtensions, loadExtensions, loadProjectExtensions } from "./extensions.js";
|
|
31
31
|
export type { KisoExtension } from "./extensions.js";
|
|
32
32
|
export { executionForCallId, executionLedger } from "./ledger.js";
|
|
33
33
|
export type { ExecutionRecord, ExecutionStatus } from "./ledger.js";
|
|
34
|
-
export { assessTasks } from "./task-assessment.js";
|
|
35
|
-
export type { EvidenceVerdict, TaskAssessment, TaskClaim } from "./task-assessment.js";
|
|
36
34
|
export { kisoHome, projectArtifacts, recordTrust, trustFor } from "./trust.js";
|
|
37
35
|
export type { ProjectArtifact, ProjectArtifacts, TrustDecision, TrustRecord } from "./trust.js";
|
|
38
36
|
export { canonicalizeUsage } from "./usage/canonical.js";
|
|
37
|
+
export { estimateTokens } from "./estimate-tokens.js";
|
package/dist/index.js
CHANGED
|
@@ -24,16 +24,13 @@ export { AgentRuntime, AgentRuntime as Agent, createAgent } from "./agent.js";
|
|
|
24
24
|
export { AgentSession, AgentSession as Session } from "./session.js";
|
|
25
25
|
export { PoisonedSessionError, ResumeBlockedError } from "./session.js";
|
|
26
26
|
// run
|
|
27
|
-
export { Run } from "./run.js";
|
|
27
|
+
export { Run, RunClosedError } from "./run.js";
|
|
28
28
|
// store
|
|
29
29
|
export { SessionStore, StaleWriterError, StoreCorruptionError } from "./store.js";
|
|
30
30
|
// extensions
|
|
31
31
|
export { disposeExtensions, loadExtensions, loadProjectExtensions } from "./extensions.js";
|
|
32
32
|
// ledger
|
|
33
33
|
export { executionForCallId, executionLedger } from "./ledger.js";
|
|
34
|
-
// task assessment (TV-1A) — the pure projection separating the model's
|
|
35
|
-
// CLAIM from VERIFIED under Verified ⟹ evidenceSeq > lastMutationSeq
|
|
36
|
-
export { assessTasks } from "./task-assessment.js";
|
|
37
34
|
// trust
|
|
38
35
|
export { kisoHome, projectArtifacts, recordTrust, trustFor } from "./trust.js";
|
|
39
36
|
// usage — the canonical accounting schema (E2/1.3.0, R4b-1 ruling:
|
|
@@ -45,3 +42,7 @@ export { kisoHome, projectArtifacts, recordTrust, trustFor } from "./trust.js";
|
|
|
45
42
|
// provider observation; canonicalizeUsage derives at the accounting
|
|
46
43
|
// boundary (R4 Case B — the frozen usage union does not move).
|
|
47
44
|
export { canonicalizeUsage } from "./usage/canonical.js";
|
|
45
|
+
// ADR-0043 Amendment 13: the token estimator is runtime context-accounting
|
|
46
|
+
// policy, not a kernel primitive — it moved here unchanged from the core
|
|
47
|
+
// package, which never called it.
|
|
48
|
+
export { estimateTokens } from "./estimate-tokens.js";
|
package/dist/request-budget.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* This is a display and design entry. The auto-compact policy keeps its
|
|
18
18
|
* own number (`estimateCtxRatio` in the CLI) — moving it is A1b's.
|
|
19
19
|
*/
|
|
20
|
-
import { estimateTokens } from "
|
|
20
|
+
import { estimateTokens } from "./estimate-tokens.js";
|
|
21
21
|
const chars = (s) => Math.ceil(s.length / 4);
|
|
22
22
|
export function requestBudget(parts, window) {
|
|
23
23
|
const system = parts.systemPrompt === undefined ? 0 : chars(parts.systemPrompt);
|
package/dist/run.d.ts
CHANGED
|
@@ -7,8 +7,19 @@ import { type AbortSignalLike, type Adapter, type ContentBlock, type Event } fro
|
|
|
7
7
|
import type { SessionStore } from "./store.js";
|
|
8
8
|
import { type AgentSession, type SessionConfig } from "./session.js";
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* ADR-0057: `run.steer()` after the run decided to end — its ingress is
|
|
11
|
+
* sealed. The host starts a new run with the input instead.
|
|
12
|
+
*/
|
|
13
|
+
export declare class RunClosedError extends Error {
|
|
14
|
+
readonly runId: string;
|
|
15
|
+
constructor(runId: string);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A single foreground execution, from its input to one terminal (ADR-0057:
|
|
19
|
+
* it may admit further input at Safe Admission boundaries).
|
|
20
|
+
* Async-iterable, so `for await (const ev of session.run(x))` is the
|
|
21
|
+
* natural shape; the handle also carries the runId, the abort and the
|
|
22
|
+
* steer.
|
|
12
23
|
*/
|
|
13
24
|
export declare class Run implements AsyncIterable<Event> {
|
|
14
25
|
#private;
|
|
@@ -16,5 +27,16 @@ export declare class Run implements AsyncIterable<Event> {
|
|
|
16
27
|
constructor(store: SessionStore, adapter: Adapter, config: SessionConfig, session: AgentSession, input: string | readonly ContentBlock[] | undefined, externalSignal: AbortSignalLike | undefined, resume: boolean, source?: import("@vincemakes/kiso-core").MessageSource, via?: import("@vincemakes/kiso-core").UserInputVia);
|
|
17
28
|
/** Cancel the run: propagates to the adapter (SDK) and future executions. */
|
|
18
29
|
abort(): void;
|
|
30
|
+
/**
|
|
31
|
+
* ADR-0057 — hand this run a person's input. It is admitted at the next
|
|
32
|
+
* Safe Admission boundary (every started effect settled, no approval
|
|
33
|
+
* open) as a user_input the model sees on its next request. Throws
|
|
34
|
+
* {@link RunClosedError} once the run has decided to end.
|
|
35
|
+
*/
|
|
36
|
+
steer(content: string | readonly ContentBlock[]): void;
|
|
37
|
+
/** ADR-0057 — take back the input that has not been admitted yet. */
|
|
38
|
+
retract(): readonly (string | readonly ContentBlock[])[];
|
|
39
|
+
/** ADR-0057 — input that arrived but was not admitted when the run ended. */
|
|
40
|
+
unadmitted(): readonly (string | readonly ContentBlock[])[];
|
|
19
41
|
[Symbol.asyncIterator](): AsyncIterator<Event>;
|
|
20
42
|
}
|
package/dist/run.js
CHANGED
|
@@ -15,8 +15,23 @@ import { RequestTracer, traceGuard } from "./trace/guard.js";
|
|
|
15
15
|
import { runtimeVersion } from "./trace/writer.js";
|
|
16
16
|
import { ResumeBlockedError } from "./session.js";
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* ADR-0057: `run.steer()` after the run decided to end — its ingress is
|
|
19
|
+
* sealed. The host starts a new run with the input instead.
|
|
20
|
+
*/
|
|
21
|
+
export class RunClosedError extends Error {
|
|
22
|
+
runId;
|
|
23
|
+
constructor(runId) {
|
|
24
|
+
super(`run ${runId} has ended — start a new run with this input`);
|
|
25
|
+
this.name = "RunClosedError";
|
|
26
|
+
this.runId = runId;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A single foreground execution, from its input to one terminal (ADR-0057:
|
|
31
|
+
* it may admit further input at Safe Admission boundaries).
|
|
32
|
+
* Async-iterable, so `for await (const ev of session.run(x))` is the
|
|
33
|
+
* natural shape; the handle also carries the runId, the abort and the
|
|
34
|
+
* steer.
|
|
20
35
|
*/
|
|
21
36
|
export class Run {
|
|
22
37
|
runId;
|
|
@@ -36,9 +51,15 @@ export class Run {
|
|
|
36
51
|
#externalSignal;
|
|
37
52
|
#decisionIds = [];
|
|
38
53
|
#started = false;
|
|
54
|
+
// ADR-0057 — the ephemeral ingress: a person's input in arrival order,
|
|
55
|
+
// held in memory until an admission site takes it. The log owns what was
|
|
56
|
+
// admitted; nothing here is durable (a crash before admission loses it).
|
|
57
|
+
#pending = [];
|
|
58
|
+
#unadmitted = [];
|
|
59
|
+
#sealed = false;
|
|
39
60
|
constructor(store, adapter, config, session, input, externalSignal, resume,
|
|
40
|
-
// TV-1B: durable PROVENANCE for the input (e.g.
|
|
41
|
-
//
|
|
61
|
+
// TV-1B: durable PROVENANCE for the input (e.g. source:"system"
|
|
62
|
+
// for a line the product wrote) — who produced the line, never a
|
|
42
63
|
// provider-role escalation. Absent = plain user input.
|
|
43
64
|
source,
|
|
44
65
|
// 0.40.0: how a person composed the turn (a skill + the typed line) —
|
|
@@ -59,6 +80,41 @@ export class Run {
|
|
|
59
80
|
abort() {
|
|
60
81
|
this.#abort.abort();
|
|
61
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* ADR-0057 — hand this run a person's input. It is admitted at the next
|
|
85
|
+
* Safe Admission boundary (every started effect settled, no approval
|
|
86
|
+
* open) as a user_input the model sees on its next request. Throws
|
|
87
|
+
* {@link RunClosedError} once the run has decided to end.
|
|
88
|
+
*/
|
|
89
|
+
steer(content) {
|
|
90
|
+
if (this.#sealed)
|
|
91
|
+
throw new RunClosedError(this.runId);
|
|
92
|
+
this.#pending.push(content);
|
|
93
|
+
}
|
|
94
|
+
/** ADR-0057 — take back the input that has not been admitted yet. */
|
|
95
|
+
retract() {
|
|
96
|
+
return this.#pending.splice(0);
|
|
97
|
+
}
|
|
98
|
+
/** ADR-0057 — input that arrived but was not admitted when the run ended. */
|
|
99
|
+
unadmitted() {
|
|
100
|
+
return [...this.#unadmitted];
|
|
101
|
+
}
|
|
102
|
+
/** The kernel's `admit`: what is pending becomes ONE human input — a
|
|
103
|
+
* single line as itself, several as text blocks in arrival order. */
|
|
104
|
+
async #admit(mode) {
|
|
105
|
+
if (mode !== "seal" && this.#pending.length > 0) {
|
|
106
|
+
const lines = this.#pending.splice(0);
|
|
107
|
+
const content = lines.length === 1 ? lines[0] : lines.flatMap((l) => (typeof l === "string" ? [{ type: "text", text: l }] : l));
|
|
108
|
+
return [{ kind: "human", content, source: "user" }];
|
|
109
|
+
}
|
|
110
|
+
if (mode !== "take")
|
|
111
|
+
this.#seal();
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
#seal() {
|
|
115
|
+
this.#sealed = true;
|
|
116
|
+
this.#unadmitted.push(...this.#pending.splice(0));
|
|
117
|
+
}
|
|
62
118
|
async *[Symbol.asyncIterator]() {
|
|
63
119
|
if (this.#started)
|
|
64
120
|
throw new Error("a run may only be consumed once");
|
|
@@ -204,6 +260,9 @@ export class Run {
|
|
|
204
260
|
// the human gave in the same instant as the abort is
|
|
205
261
|
// recorded, exactly once.
|
|
206
262
|
approvalVerdict: (decisionId) => this.#session.approvalVerdict(decisionId),
|
|
263
|
+
// ADR-0057: the ingress — the kernel admits at its three sites
|
|
264
|
+
// and seals at every terminal.
|
|
265
|
+
admit: (mode) => this.#admit(mode),
|
|
207
266
|
});
|
|
208
267
|
const self = this;
|
|
209
268
|
const runLoop = async function* () {
|
|
@@ -328,6 +387,9 @@ export class Run {
|
|
|
328
387
|
yield ev;
|
|
329
388
|
}
|
|
330
389
|
finally {
|
|
390
|
+
// ADR-0057: however the run ended — terminal, throw or abandoned —
|
|
391
|
+
// its ingress is closed; pending input is handed back.
|
|
392
|
+
this.#seal();
|
|
331
393
|
// round 5(P1-5): flush verdicts the consumer submitted before the
|
|
332
394
|
// generator was abandoned — an approve()/resolveUncertain() whose
|
|
333
395
|
// durable event the loop never got to persist must STILL land on
|
|
@@ -505,7 +567,10 @@ export class Run {
|
|
|
505
567
|
yield log.append({ type: "tool_result", callId: call.callId, invocationSeq: call.seq, content: refusal, isError: true, errorKind: "invalid_input" });
|
|
506
568
|
return;
|
|
507
569
|
}
|
|
508
|
-
|
|
570
|
+
// #20: the same lexical evidence the fresh path hands onPreTool — THIS
|
|
571
|
+
// invocation's deltas, anchored by its seq; never given to the chain.
|
|
572
|
+
const rawInput = rawInputOf(log.all, call.callId, call.seq);
|
|
573
|
+
const payload = { callId: call.callId, name: call.name, input: call.input ?? {}, ...(rawInput !== undefined ? { rawInput } : {}) };
|
|
509
574
|
const policyCall = { name: payload.name, input: payload.input };
|
|
510
575
|
let verdict;
|
|
511
576
|
try {
|
|
@@ -825,7 +890,7 @@ export class Run {
|
|
|
825
890
|
};
|
|
826
891
|
}
|
|
827
892
|
if (this.#config.hooks?.onPostTool) {
|
|
828
|
-
result = await this.#config.hooks.onPostTool({ callId, name, input }, result, { sessionId: this.#session.id });
|
|
893
|
+
result = await this.#config.hooks.onPostTool({ callId, name, input, ...(rawInput !== undefined ? { rawInput } : {}) }, result, { sessionId: this.#session.id });
|
|
829
894
|
}
|
|
830
895
|
}
|
|
831
896
|
// ruling #12 correction one: the honest note rides the recovered failure too —
|
package/dist/session.d.ts
CHANGED
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { EventLog, type AbortSignalLike, type Adapter, type Event, type KisoExtension, type Message, type PermissionDecision, type Tool, type ToolSpec, type EventInput } from "@vincemakes/kiso-core";
|
|
31
31
|
import { type OverflowMeasure } from "./overflow-belt.js";
|
|
32
|
-
import { type TaskAssessment } from "./task-assessment.js";
|
|
33
32
|
import { type ContinuationScope } from "@vincemakes/kiso-core";
|
|
34
33
|
import { type ReasoningSetting } from "./provider/metadata.js";
|
|
35
34
|
import { type SessionStore, type StoreRecord } from "./store.js";
|
|
@@ -375,20 +374,6 @@ export declare class AgentSession {
|
|
|
375
374
|
approve(decisionId: string, allow: boolean, reason?: string): Promise<void>;
|
|
376
375
|
/** Executions that started but never reported a result (crash window). */
|
|
377
376
|
uncertainExecutions(): import("./ledger.js").ExecutionRecord[];
|
|
378
|
-
/**
|
|
379
|
-
* TV-1A — assess the task claims and their evidence freshness over THIS
|
|
380
|
-
* session's durable log. The non-mutating set comes from the live tools'
|
|
381
|
-
* own `effects.precommitSafe` certificates (one direction of truth,
|
|
382
|
-
* never a second declaration) — the read-only+free+local contract, the
|
|
383
|
-
* only certificate that proves the world untouched. `concurrency:
|
|
384
|
-
* "shared"` is a SCHEDULING promise and never feeds this set (TV-1C —
|
|
385
|
-
* slow_touch is shared and writes). The evidence policy defaults to
|
|
386
|
-
* {"shell"} — the convention the task extension's own "make the LAST
|
|
387
|
-
* item a verification step" guidance produces.
|
|
388
|
-
*/
|
|
389
|
-
assessTasks(opts?: {
|
|
390
|
-
readonly evidenceTools?: ReadonlySet<string>;
|
|
391
|
-
}): TaskAssessment;
|
|
392
377
|
/**
|
|
393
378
|
* The human's verdict on an interrupted execution, keyed by EXECUTION ID
|
|
394
379
|
* (B group): "rerun" (the human says the side effect did NOT happen — the
|
package/dist/session.js
CHANGED
|
@@ -31,11 +31,6 @@ import { EventLog, projectMessages, SUMMARY_FRAMING, } from "@vincemakes/kiso-co
|
|
|
31
31
|
import { executionLedger } from "./ledger.js";
|
|
32
32
|
import { overflowBelt } from "./overflow-belt.js";
|
|
33
33
|
import { windowLearner } from "./window-learner.js";
|
|
34
|
-
import { assessTasks } from "./task-assessment.js";
|
|
35
|
-
/** TV-1A — the session-level evidence policy: the PURE projection defaults
|
|
36
|
-
* to ∅ (never inventing evidence); the session names the one built-in
|
|
37
|
-
* verification surface. Override per call for custom evidence tools. */
|
|
38
|
-
const DEFAULT_EVIDENCE_TOOLS = new Set(["shell"]);
|
|
39
34
|
import { denialResult } from "@vincemakes/kiso-core";
|
|
40
35
|
import { buildProfile, readProfile, writeProfile, writeSummary } from "./profile.js";
|
|
41
36
|
import { summarizeEvents } from "./session-summary.js";
|
|
@@ -45,7 +40,7 @@ import { canonicalizeUsageForModel } from "./usage/canonical.js";
|
|
|
45
40
|
import { contextAnchor } from "./context-anchor.js";
|
|
46
41
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
47
42
|
import { join } from "node:path";
|
|
48
|
-
import { estimateTokens } from "
|
|
43
|
+
import { estimateTokens } from "./estimate-tokens.js";
|
|
49
44
|
import { StaleWriterError } from "./store.js";
|
|
50
45
|
import { composeHooks, composeSystemPrompt, microcompactFor, runBasePrompt } from "./compose.js";
|
|
51
46
|
import { checkpointBoundarySeq } from "./checkpoint.js";
|
|
@@ -1064,28 +1059,6 @@ export class AgentSession {
|
|
|
1064
1059
|
uncertainExecutions() {
|
|
1065
1060
|
return [...executionLedger(this.log.all).values()].filter((r) => r.status === "uncertain");
|
|
1066
1061
|
}
|
|
1067
|
-
/**
|
|
1068
|
-
* TV-1A — assess the task claims and their evidence freshness over THIS
|
|
1069
|
-
* session's durable log. The non-mutating set comes from the live tools'
|
|
1070
|
-
* own `effects.precommitSafe` certificates (one direction of truth,
|
|
1071
|
-
* never a second declaration) — the read-only+free+local contract, the
|
|
1072
|
-
* only certificate that proves the world untouched. `concurrency:
|
|
1073
|
-
* "shared"` is a SCHEDULING promise and never feeds this set (TV-1C —
|
|
1074
|
-
* slow_touch is shared and writes). The evidence policy defaults to
|
|
1075
|
-
* {"shell"} — the convention the task extension's own "make the LAST
|
|
1076
|
-
* item a verification step" guidance produces.
|
|
1077
|
-
*/
|
|
1078
|
-
assessTasks(opts) {
|
|
1079
|
-
const nonMutatingTools = new Set();
|
|
1080
|
-
for (const tool of this.#config.registry.list()) {
|
|
1081
|
-
if (tool.effects?.precommitSafe === true)
|
|
1082
|
-
nonMutatingTools.add(tool.name);
|
|
1083
|
-
}
|
|
1084
|
-
return assessTasks(this.log.all, {
|
|
1085
|
-
nonMutatingTools,
|
|
1086
|
-
evidenceTools: opts?.evidenceTools ?? DEFAULT_EVIDENCE_TOOLS,
|
|
1087
|
-
});
|
|
1088
|
-
}
|
|
1089
1062
|
/**
|
|
1090
1063
|
* The human's verdict on an interrupted execution, keyed by EXECUTION ID
|
|
1091
1064
|
* (B group): "rerun" (the human says the side effect did NOT happen — the
|
package/dist/summarize.d.ts
CHANGED
|
@@ -270,8 +270,9 @@ export declare function estimateEventTokens(ev: Event): number;
|
|
|
270
270
|
* rounds exist (nothing worth covering yet).
|
|
271
271
|
*
|
|
272
272
|
* ⑥ (task round): a tool result tagged do-not-compact is DURABLE work
|
|
273
|
-
* memory (
|
|
274
|
-
*
|
|
273
|
+
* memory (a tool declares it so; the retired task extension's list was
|
|
274
|
+
* the first) — the summary must never cover its round, or the model
|
|
275
|
+
* loses it. When the base boundary would
|
|
275
276
|
* cover such a result, the boundary pulls back to just before the round
|
|
276
277
|
* containing the LATEST one (still a turn boundary). A protected round
|
|
277
278
|
* as the FIRST uncovered round leaves nothing before it to cover →
|
package/dist/summarize.js
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* unchanged ("nothing happened"). Only the generated `summarized` event
|
|
14
14
|
* lands on disk; the original events stay there forever.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
16
|
+
import { DO_NOT_COMPACT, DEFAULT_MAX_RETRIES, RETRY_AFTER_MAX_MS, retryDelayMs } from "@vincemakes/kiso-core";
|
|
17
|
+
import { estimateTokens } from "./estimate-tokens.js";
|
|
17
18
|
/** K (ADR-0044): the recent ROUNDS kept intact by /compact — a constant,
|
|
18
19
|
* not a knob. The covered range ends just before the K-th most recent
|
|
19
20
|
* round, so the model still reasons over the recent conversation. */
|
|
@@ -501,8 +502,9 @@ export function estimateEventTokens(ev) {
|
|
|
501
502
|
* rounds exist (nothing worth covering yet).
|
|
502
503
|
*
|
|
503
504
|
* ⑥ (task round): a tool result tagged do-not-compact is DURABLE work
|
|
504
|
-
* memory (
|
|
505
|
-
*
|
|
505
|
+
* memory (a tool declares it so; the retired task extension's list was
|
|
506
|
+
* the first) — the summary must never cover its round, or the model
|
|
507
|
+
* loses it. When the base boundary would
|
|
506
508
|
* cover such a result, the boundary pulls back to just before the round
|
|
507
509
|
* containing the LATEST one (still a turn boundary). A protected round
|
|
508
510
|
* as the FIRST uncovered round leaves nothing before it to cover →
|
package/dist/trace/manifest.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* surprise), every turn range degrades to null — honest thin pointers
|
|
21
21
|
* rather than wrong ones.
|
|
22
22
|
*/
|
|
23
|
-
import { estimateTokens } from "
|
|
23
|
+
import { estimateTokens } from "../estimate-tokens.js";
|
|
24
24
|
import { canonicalJson, hashSystemPrompt, hashToolSpecs, sha256Hex } from "./hash.js";
|
|
25
25
|
/** The log's visible user boundaries, in log order: every user_input
|
|
26
26
|
* whose replacement (if any) is not a veto. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"description": "kiso runtime \u2014 durable multi-turn agent sessions: AgentDefinition, AgentRuntime, AgentSession, Run, append-only JSONL store.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"test": "vitest run"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@vincemakes/kiso-core": "0.
|
|
28
|
+
"@vincemakes/kiso-core": "0.45.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@vincemakes/kiso-provider-anthropic": "0.
|
|
32
|
-
"@vincemakes/kiso-provider-openai": "0.
|
|
33
|
-
"@vincemakes/kiso-provider-openai-responses": "0.
|
|
31
|
+
"@vincemakes/kiso-provider-anthropic": "0.45.0",
|
|
32
|
+
"@vincemakes/kiso-provider-openai": "0.45.0",
|
|
33
|
+
"@vincemakes/kiso-provider-openai-responses": "0.45.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"@vincemakes/kiso-provider-anthropic": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@vincemakes/kiso-evals": "0.
|
|
47
|
+
"@vincemakes/kiso-evals": "0.45.0",
|
|
48
48
|
"@types/node": "^26.1.2",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vitest": "^3.0.0"
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TV-1A — TaskAssessment as a PURE PROJECTION + Evidence Freshness.
|
|
3
|
-
*
|
|
4
|
-
* The task checklist the model maintains through task_set is a SELF-REPORT:
|
|
5
|
-
* an item is "done" because the model said so. This projection mechanically
|
|
6
|
-
* separates that CLAIM from VERIFIED, under one frozen sentence:
|
|
7
|
-
*
|
|
8
|
-
* Verified ⟹ evidenceSeq > lastRelevantMutationSeq.
|
|
9
|
-
*
|
|
10
|
-
* No new event kind, no core diff — the projection reads the existing
|
|
11
|
-
* durable vocabulary (`deriveRecoveryPlan` is the shape precedent). The
|
|
12
|
-
* classifiers are maximally conservative; the exemptions are exactly
|
|
13
|
-
* three (TV-1C — no wider taxonomy exists or is wanted):
|
|
14
|
-
*
|
|
15
|
-
* - MUTATION marker: `tool_execution_started` (intent-to-effect) of any
|
|
16
|
-
* name NOT in `nonMutatingTools`. Started — not the receipt — so failed
|
|
17
|
-
* and crash-window executions invalidate too, and pre-EC-1 overlap eras
|
|
18
|
-
* are covered by the same rule. Absence of a certificate is a mutation.
|
|
19
|
-
* The exemption set is fed by `effects.precommitSafe` — the frozen
|
|
20
|
-
* read-only+free+local contract, the only certificate that PROVES the
|
|
21
|
-
* world untouched. `concurrency: "shared"` proves overlap-safety and
|
|
22
|
-
* nothing else (the TV-1C finding: slow_touch is shared AND writes) —
|
|
23
|
-
* it never feeds this set.
|
|
24
|
-
* ONE proven exception (TV-1B, grounded in WR-1A): an execution whose
|
|
25
|
-
* terminal receipt is failed(errorKind:"precondition") never counts —
|
|
26
|
-
* that kind's frozen contract is "work refused BEFORE it starts".
|
|
27
|
-
* - EVIDENCE: only receipts (`tool_execution_succeeded`) of names in
|
|
28
|
-
* `evidenceTools`. Default ∅ — the projection never invents evidence.
|
|
29
|
-
* What TV-1A's verdict asserts is POSITIONAL: the arc's last
|
|
30
|
-
* intent-to-effect was a successful evidence-class run — an "evidence",
|
|
31
|
-
* never a "proof" (semantic knowledge is the TV-1B driver's).
|
|
32
|
-
* - task_set itself never invalidates: recording the claim after the check
|
|
33
|
-
* is the natural arc (tests → mark done), and the claim-recording act
|
|
34
|
-
* mutates nothing the evidence observed.
|
|
35
|
-
* - VOIDED RANGES (TV-1C): a `model_output_abandoned` marker voids
|
|
36
|
-
* (voidFromSeq, seq] — the durable vocabulary's own "never happened",
|
|
37
|
-
* the same ranges the kernel projection skips. Every consumer here
|
|
38
|
-
* skips them uniformly: a voided echo is not a claim, a voided receipt
|
|
39
|
-
* is not evidence, a voided start is not a mutation. task_set waits
|
|
40
|
-
* for turn commit today (TT-1), so the scheduler already keeps claims
|
|
41
|
-
* out of voided turns — the projection encodes the admissibility rule
|
|
42
|
-
* itself instead of relying on that forever.
|
|
43
|
-
*
|
|
44
|
-
* `verified` means FRESH EVIDENCE UNDER THE CONFIGURED POLICY — never
|
|
45
|
-
* "objectively proven correct". Goal Truth is external (PE-1's
|
|
46
|
-
* evaluator); this projection only ever ranks the model's own claim
|
|
47
|
-
* against the trajectory's own receipts.
|
|
48
|
-
*/
|
|
49
|
-
import type { Event } from "@vincemakes/kiso-core";
|
|
50
|
-
/** One item of the model's plan, exactly as last claimed. */
|
|
51
|
-
export interface TaskClaim {
|
|
52
|
-
readonly text: string;
|
|
53
|
-
readonly status: "pending" | "active" | "done";
|
|
54
|
-
}
|
|
55
|
-
/** The freshness verdict. `stale` and `unreadable` NAME their cause. */
|
|
56
|
-
export type EvidenceVerdict = {
|
|
57
|
-
readonly kind: "verified";
|
|
58
|
-
readonly evidenceSeq: number;
|
|
59
|
-
} | {
|
|
60
|
-
readonly kind: "stale";
|
|
61
|
-
readonly evidenceSeq: number;
|
|
62
|
-
readonly invalidatedBySeq: number;
|
|
63
|
-
} | {
|
|
64
|
-
readonly kind: "none";
|
|
65
|
-
} | {
|
|
66
|
-
readonly kind: "unreadable";
|
|
67
|
-
readonly atSeq: number;
|
|
68
|
-
readonly reason: string;
|
|
69
|
-
};
|
|
70
|
-
export interface TaskAssessment {
|
|
71
|
-
/** The LAST successful task_set echo, parsed — the model's claims. */
|
|
72
|
-
readonly claims: readonly TaskClaim[];
|
|
73
|
-
/** True only for a non-empty plan whose every item is claimed done. */
|
|
74
|
-
readonly allClaimedDone: boolean;
|
|
75
|
-
readonly lastTaskSetSeq: number | null;
|
|
76
|
-
/** Seq of the last mutation-class `tool_execution_started`, if any. */
|
|
77
|
-
readonly lastMutationSeq: number | null;
|
|
78
|
-
readonly evidence: EvidenceVerdict;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Assess the task claims and their evidence freshness over a durable
|
|
82
|
-
* trajectory. Pure: same events, same options, same assessment.
|
|
83
|
-
*/
|
|
84
|
-
export declare function assessTasks(events: readonly Event[], opts?: {
|
|
85
|
-
/** Names whose executions PROVABLY never mutate the observable
|
|
86
|
-
* world (from `effects.precommitSafe` certificates — read-only +
|
|
87
|
-
* free + local). `concurrency: "shared"` is NOT such a proof
|
|
88
|
-
* (TV-1C). Default ∅ — everything mutates. */
|
|
89
|
-
readonly nonMutatingTools?: ReadonlySet<string>;
|
|
90
|
-
/** Names whose successful receipts count as evidence. Default ∅ —
|
|
91
|
-
* nothing does. */
|
|
92
|
-
readonly evidenceTools?: ReadonlySet<string>;
|
|
93
|
-
}): TaskAssessment;
|
package/dist/task-assessment.js
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TV-1A — TaskAssessment as a PURE PROJECTION + Evidence Freshness.
|
|
3
|
-
*
|
|
4
|
-
* The task checklist the model maintains through task_set is a SELF-REPORT:
|
|
5
|
-
* an item is "done" because the model said so. This projection mechanically
|
|
6
|
-
* separates that CLAIM from VERIFIED, under one frozen sentence:
|
|
7
|
-
*
|
|
8
|
-
* Verified ⟹ evidenceSeq > lastRelevantMutationSeq.
|
|
9
|
-
*
|
|
10
|
-
* No new event kind, no core diff — the projection reads the existing
|
|
11
|
-
* durable vocabulary (`deriveRecoveryPlan` is the shape precedent). The
|
|
12
|
-
* classifiers are maximally conservative; the exemptions are exactly
|
|
13
|
-
* three (TV-1C — no wider taxonomy exists or is wanted):
|
|
14
|
-
*
|
|
15
|
-
* - MUTATION marker: `tool_execution_started` (intent-to-effect) of any
|
|
16
|
-
* name NOT in `nonMutatingTools`. Started — not the receipt — so failed
|
|
17
|
-
* and crash-window executions invalidate too, and pre-EC-1 overlap eras
|
|
18
|
-
* are covered by the same rule. Absence of a certificate is a mutation.
|
|
19
|
-
* The exemption set is fed by `effects.precommitSafe` — the frozen
|
|
20
|
-
* read-only+free+local contract, the only certificate that PROVES the
|
|
21
|
-
* world untouched. `concurrency: "shared"` proves overlap-safety and
|
|
22
|
-
* nothing else (the TV-1C finding: slow_touch is shared AND writes) —
|
|
23
|
-
* it never feeds this set.
|
|
24
|
-
* ONE proven exception (TV-1B, grounded in WR-1A): an execution whose
|
|
25
|
-
* terminal receipt is failed(errorKind:"precondition") never counts —
|
|
26
|
-
* that kind's frozen contract is "work refused BEFORE it starts".
|
|
27
|
-
* - EVIDENCE: only receipts (`tool_execution_succeeded`) of names in
|
|
28
|
-
* `evidenceTools`. Default ∅ — the projection never invents evidence.
|
|
29
|
-
* What TV-1A's verdict asserts is POSITIONAL: the arc's last
|
|
30
|
-
* intent-to-effect was a successful evidence-class run — an "evidence",
|
|
31
|
-
* never a "proof" (semantic knowledge is the TV-1B driver's).
|
|
32
|
-
* - task_set itself never invalidates: recording the claim after the check
|
|
33
|
-
* is the natural arc (tests → mark done), and the claim-recording act
|
|
34
|
-
* mutates nothing the evidence observed.
|
|
35
|
-
* - VOIDED RANGES (TV-1C): a `model_output_abandoned` marker voids
|
|
36
|
-
* (voidFromSeq, seq] — the durable vocabulary's own "never happened",
|
|
37
|
-
* the same ranges the kernel projection skips. Every consumer here
|
|
38
|
-
* skips them uniformly: a voided echo is not a claim, a voided receipt
|
|
39
|
-
* is not evidence, a voided start is not a mutation. task_set waits
|
|
40
|
-
* for turn commit today (TT-1), so the scheduler already keeps claims
|
|
41
|
-
* out of voided turns — the projection encodes the admissibility rule
|
|
42
|
-
* itself instead of relying on that forever.
|
|
43
|
-
*
|
|
44
|
-
* `verified` means FRESH EVIDENCE UNDER THE CONFIGURED POLICY — never
|
|
45
|
-
* "objectively proven correct". Goal Truth is external (PE-1's
|
|
46
|
-
* evaluator); this projection only ever ranks the model's own claim
|
|
47
|
-
* against the trajectory's own receipts.
|
|
48
|
-
*/
|
|
49
|
-
const TASK_TOOL = "task_set";
|
|
50
|
-
const STATUSES = new Set(["pending", "active", "done"]);
|
|
51
|
-
const COUNT_LINE = /^\[task\] (\d+) items? — (\d+) pending, (\d+) active, (\d+) done$/;
|
|
52
|
-
const ITEM_LINE = /^\[(\w+)\] (.*)$/;
|
|
53
|
-
/** Parse the canonical task_set echo (the frozen content contract the
|
|
54
|
-
* checklist cell also reads). Returns the claims, or the reason it
|
|
55
|
-
* cannot — never a guess. */
|
|
56
|
-
function parseEcho(content) {
|
|
57
|
-
const lines = content.split("\n");
|
|
58
|
-
const head = COUNT_LINE.exec(lines[0] ?? "");
|
|
59
|
-
if (head === null)
|
|
60
|
-
return { reason: "line 1 is not the [task] count line" };
|
|
61
|
-
const total = Number(head[1]);
|
|
62
|
-
const declared = { pending: Number(head[2]), active: Number(head[3]), done: Number(head[4]) };
|
|
63
|
-
const claims = [];
|
|
64
|
-
for (let i = 1; i < lines.length; i += 1) {
|
|
65
|
-
const m = ITEM_LINE.exec(lines[i]);
|
|
66
|
-
if (m === null || !STATUSES.has(m[1]))
|
|
67
|
-
return { reason: `line ${i + 1} is not a [pending|active|done] item line` };
|
|
68
|
-
claims.push({ text: m[2], status: m[1] });
|
|
69
|
-
}
|
|
70
|
-
const counts = { pending: 0, active: 0, done: 0 };
|
|
71
|
-
for (const c of claims)
|
|
72
|
-
counts[c.status] += 1;
|
|
73
|
-
if (claims.length !== total || counts.pending !== declared.pending || counts.active !== declared.active || counts.done !== declared.done) {
|
|
74
|
-
return { reason: "the count line disagrees with the item lines" };
|
|
75
|
-
}
|
|
76
|
-
return { claims };
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Assess the task claims and their evidence freshness over a durable
|
|
80
|
-
* trajectory. Pure: same events, same options, same assessment.
|
|
81
|
-
*/
|
|
82
|
-
export function assessTasks(events, opts) {
|
|
83
|
-
const nonMutating = opts?.nonMutatingTools ?? new Set();
|
|
84
|
-
const evidenceNames = opts?.evidenceTools ?? new Set();
|
|
85
|
-
// TV-1C: PASS 0 — the voided ranges. (voidFromSeq, seq] of every
|
|
86
|
-
// `model_output_abandoned` marker is "never happened" for this whole
|
|
87
|
-
// projection, exactly as the kernel's own context projection treats
|
|
88
|
-
// it. Markers are rare; a linear range check is honest and enough.
|
|
89
|
-
const voidRanges = [];
|
|
90
|
-
for (const ev of events) {
|
|
91
|
-
if (ev.type === "model_output_abandoned")
|
|
92
|
-
voidRanges.push({ from: ev.voidFromSeq, to: ev.seq });
|
|
93
|
-
}
|
|
94
|
-
const voided = (seq) => voidRanges.some((r) => seq > r.from && seq <= r.to);
|
|
95
|
-
// TV-1B: PASS 1 — executions whose terminal receipt is a
|
|
96
|
-
// PRECONDITION failure are PROVEN no-mutation (WR-1A froze that
|
|
97
|
-
// contract: work refused before it starts). Everything else stays
|
|
98
|
-
// conservative: no receipt (crash window), fatal/transient/
|
|
99
|
-
// invalid_input, success, and legacy receipts with no errorKind.
|
|
100
|
-
// Two passes, no temporal rollback state — same events, same verdict.
|
|
101
|
-
const provenNoMutation = new Set();
|
|
102
|
-
for (const ev of events) {
|
|
103
|
-
if (voided(ev.seq))
|
|
104
|
-
continue;
|
|
105
|
-
if (ev.type === "tool_execution_failed" && ev.errorKind === "precondition") {
|
|
106
|
-
provenNoMutation.add(ev.executionId);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
const nameByExecution = new Map();
|
|
110
|
-
let claims = [];
|
|
111
|
-
let lastTaskSetSeq = null;
|
|
112
|
-
let unreadable = null;
|
|
113
|
-
let lastMutationSeq = null;
|
|
114
|
-
let lastEvidenceSeq = null;
|
|
115
|
-
let staleBySeq = null;
|
|
116
|
-
for (const ev of events) {
|
|
117
|
-
if (voided(ev.seq))
|
|
118
|
-
continue;
|
|
119
|
-
if (ev.type === "tool_execution_started") {
|
|
120
|
-
nameByExecution.set(ev.executionId, ev.name);
|
|
121
|
-
if (ev.name !== TASK_TOOL && !nonMutating.has(ev.name) && !provenNoMutation.has(ev.executionId)) {
|
|
122
|
-
lastMutationSeq = ev.seq;
|
|
123
|
-
if (lastEvidenceSeq !== null && staleBySeq === null)
|
|
124
|
-
staleBySeq = ev.seq;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
else if (ev.type === "tool_execution_succeeded") {
|
|
128
|
-
const name = nameByExecution.get(ev.executionId);
|
|
129
|
-
if (name === TASK_TOOL) {
|
|
130
|
-
const parsed = parseEcho(ev.result.content);
|
|
131
|
-
if ("reason" in parsed) {
|
|
132
|
-
unreadable = { atSeq: ev.seq, reason: parsed.reason };
|
|
133
|
-
claims = [];
|
|
134
|
-
lastTaskSetSeq = ev.seq;
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
unreadable = null;
|
|
138
|
-
claims = parsed.claims;
|
|
139
|
-
lastTaskSetSeq = ev.seq;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
else if (name !== undefined && evidenceNames.has(name)) {
|
|
143
|
-
// a fresh evidence receipt supersedes both the older evidence
|
|
144
|
-
// AND any staleness its own start incurred.
|
|
145
|
-
lastEvidenceSeq = ev.seq;
|
|
146
|
-
staleBySeq = null;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
const allClaimedDone = claims.length > 0 && claims.every((c) => c.status === "done");
|
|
151
|
-
let evidence;
|
|
152
|
-
if (unreadable !== null) {
|
|
153
|
-
evidence = { kind: "unreadable", atSeq: unreadable.atSeq, reason: unreadable.reason };
|
|
154
|
-
}
|
|
155
|
-
else if (lastEvidenceSeq === null) {
|
|
156
|
-
evidence = { kind: "none" };
|
|
157
|
-
}
|
|
158
|
-
else if (staleBySeq !== null) {
|
|
159
|
-
evidence = { kind: "stale", evidenceSeq: lastEvidenceSeq, invalidatedBySeq: staleBySeq };
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
evidence = { kind: "verified", evidenceSeq: lastEvidenceSeq };
|
|
163
|
-
}
|
|
164
|
-
return { claims, allClaimedDone, lastTaskSetSeq, lastMutationSeq, evidence };
|
|
165
|
-
}
|