@vincemakes/kiso-runtime 0.42.3 → 0.44.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/index.d.ts +0 -2
- package/dist/index.js +0 -3
- package/dist/recovery-plan.js +27 -12
- package/dist/run.js +79 -14
- package/dist/session.d.ts +0 -15
- package/dist/session.js +0 -27
- package/dist/summarize.d.ts +3 -2
- package/dist/summarize.js +3 -2
- package/package.json +6 -6
- package/dist/task-assessment.d.ts +0 -93
- package/dist/task-assessment.js +0 -165
package/dist/index.d.ts
CHANGED
|
@@ -31,8 +31,6 @@ export { disposeExtensions, loadExtensions, loadProjectExtensions } from "./exte
|
|
|
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";
|
package/dist/index.js
CHANGED
|
@@ -31,9 +31,6 @@ export { SessionStore, StaleWriterError, StoreCorruptionError } from "./store.js
|
|
|
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:
|
package/dist/recovery-plan.js
CHANGED
|
@@ -147,16 +147,25 @@ export function deriveRecoveryPlan(events, scope) {
|
|
|
147
147
|
// undecided → the pipeline must decide; decided-approved without an
|
|
148
148
|
// execution → execute the persisted call; decided-denied without a
|
|
149
149
|
// model-facing result → repair it from the denial.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
// 0430-F1: an event binds to an invocation by its OWN invocationSeq
|
|
151
|
+
// when both carry one (0.1.43+) — callId is a correlation key the model
|
|
152
|
+
// may re-use; an old log's event without one binds by callId after the
|
|
153
|
+
// call, as before.
|
|
154
|
+
const bound = (e, callId, after, invocationSeq) => e.invocationSeq !== undefined && invocationSeq !== undefined ? e.invocationSeq === invocationSeq : e.callId === callId && e.seq > after;
|
|
155
|
+
const hasRequest = (callId, after, invocationSeq) => events.some((e) => e.type === "permission_requested" && bound(e, callId, after, invocationSeq));
|
|
156
|
+
const hasExecution = (callId, after, invocationSeq) => events.some((e) => e.type === "tool_execution_started" && bound(e, callId, after, invocationSeq));
|
|
157
|
+
const hasResult = (callId, after, invocationSeq) => events.some((e) => e.type === "tool_result" && bound(e, callId, after, invocationSeq));
|
|
153
158
|
const decidedForCall = (callId, after) => {
|
|
154
159
|
for (const e of events) {
|
|
155
160
|
if (e.type !== "permission_decided")
|
|
156
161
|
continue;
|
|
157
162
|
// a durable POLICY verdict binds the call (E1); a human verdict
|
|
158
|
-
// binds its request, never the call (the requests pass owns it)
|
|
159
|
-
|
|
163
|
+
// binds its request, never the call (the requests pass owns it).
|
|
164
|
+
// 0430-F1: a decision that names its invocation binds EXACTLY that
|
|
165
|
+
// one (callId repeats); only an old log's decision binds by callId.
|
|
166
|
+
if (e.decidedBy === undefined)
|
|
167
|
+
continue;
|
|
168
|
+
if (e.invocationSeq !== undefined ? e.invocationSeq === after : e.callId === callId && e.seq > after)
|
|
160
169
|
return e;
|
|
161
170
|
}
|
|
162
171
|
return undefined;
|
|
@@ -179,18 +188,24 @@ export function deriveRecoveryPlan(events, scope) {
|
|
|
179
188
|
// a stored request owns the invocation (the requests pass below) —
|
|
180
189
|
// "only a durable permission_decided authorizes an effect"; Gap A
|
|
181
190
|
// must never re-decide over a stored request.
|
|
182
|
-
if (hasRequest(call.callId, call.seq))
|
|
191
|
+
if (hasRequest(call.callId, call.seq, call.seq))
|
|
183
192
|
continue;
|
|
184
|
-
if (hasResult(call.callId, call.seq))
|
|
193
|
+
if (hasResult(call.callId, call.seq, call.seq))
|
|
185
194
|
continue; // closed — nothing to fill
|
|
186
195
|
const decided = decidedForCall(call.callId, call.seq);
|
|
196
|
+
// 0430-F1: a started execution proves the invocation crossed the
|
|
197
|
+
// permission gate on the fresh path (which records no decision for
|
|
198
|
+
// a default or hook allow) — it is never re-decided; step 6 owns its
|
|
199
|
+
// receipt.
|
|
200
|
+
if (decided === undefined && hasExecution(call.callId, call.seq, call.seq))
|
|
201
|
+
continue;
|
|
187
202
|
if (decided === undefined)
|
|
188
203
|
return { kind: "DECIDE_PERMISSION", invocationSeq: call.seq };
|
|
189
204
|
if (decided.decision === "approved") {
|
|
190
|
-
if (!hasExecution(call.callId, call.seq))
|
|
205
|
+
if (!hasExecution(call.callId, call.seq, call.seq))
|
|
191
206
|
return { kind: "EXECUTE", invocationSeq: call.seq };
|
|
192
207
|
}
|
|
193
|
-
else if (!hasResult(call.callId, call.seq)) {
|
|
208
|
+
else if (!hasResult(call.callId, call.seq, call.seq)) {
|
|
194
209
|
return { kind: "REPAIR_RESULT", invocationSeq: call.seq };
|
|
195
210
|
}
|
|
196
211
|
}
|
|
@@ -213,10 +228,10 @@ export function deriveRecoveryPlan(events, scope) {
|
|
|
213
228
|
return { kind: "WAIT_PERMISSION", invocationSeq: invocationSeq ?? pending.seq };
|
|
214
229
|
}
|
|
215
230
|
if (decided.decision === "approved") {
|
|
216
|
-
if (!hasExecution(pending.callId, pending.seq))
|
|
231
|
+
if (!hasExecution(pending.callId, pending.seq, invocationSeq))
|
|
217
232
|
return { kind: "EXECUTE", invocationSeq: invocationSeq ?? pending.seq };
|
|
218
233
|
}
|
|
219
|
-
else if (!hasResult(pending.callId, pending.seq)) {
|
|
234
|
+
else if (!hasResult(pending.callId, pending.seq, invocationSeq)) {
|
|
220
235
|
return { kind: "REPAIR_RESULT", invocationSeq: invocationSeq ?? pending.seq };
|
|
221
236
|
}
|
|
222
237
|
}
|
|
@@ -226,7 +241,7 @@ export function deriveRecoveryPlan(events, scope) {
|
|
|
226
241
|
for (const ev of scope) {
|
|
227
242
|
if (ev.type !== "tool_execution_succeeded" && ev.type !== "tool_execution_failed")
|
|
228
243
|
continue;
|
|
229
|
-
if (!hasResult(ev.callId, ev.seq) && !events.some((e) => e.type === "tool_result" && e.executionId === ev.executionId)) {
|
|
244
|
+
if (!hasResult(ev.callId, ev.seq, ev.invocationSeq) && !events.some((e) => e.type === "tool_result" && e.executionId === ev.executionId)) {
|
|
230
245
|
return { kind: "REPAIR_RESULT", executionId: ev.executionId };
|
|
231
246
|
}
|
|
232
247
|
}
|
package/dist/run.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* persistence, the loop drive, the durable recovery state machine), moved
|
|
4
4
|
* verbatim from session.ts.
|
|
5
5
|
*/
|
|
6
|
-
import { denialResult, loop } from "@vincemakes/kiso-core";
|
|
6
|
+
import { denialResult, loop, validateArgs } from "@vincemakes/kiso-core";
|
|
7
7
|
import { ABORTED, MergedSignal, abortable, openRunId } from "./recovery.js";
|
|
8
8
|
import { resolveReasoning } from "./provider/metadata.js";
|
|
9
9
|
import { deriveRecoveryPlan, invocationSeqOf } from "./recovery-plan.js";
|
|
@@ -37,8 +37,8 @@ export class Run {
|
|
|
37
37
|
#decisionIds = [];
|
|
38
38
|
#started = false;
|
|
39
39
|
constructor(store, adapter, config, session, input, externalSignal, resume,
|
|
40
|
-
// TV-1B: durable PROVENANCE for the input (e.g.
|
|
41
|
-
//
|
|
40
|
+
// TV-1B: durable PROVENANCE for the input (e.g. source:"system"
|
|
41
|
+
// for a line the product wrote) — who produced the line, never a
|
|
42
42
|
// provider-role escalation. Absent = plain user input.
|
|
43
43
|
source,
|
|
44
44
|
// 0.40.0: how a person composed the turn (a skill + the typed line) —
|
|
@@ -494,7 +494,21 @@ export class Run {
|
|
|
494
494
|
throw new Error(`the recovery plan derived a call outside the scope (seq ${invocationSeq})`);
|
|
495
495
|
// The chain's PolicyCall carries name+input only — callId is the
|
|
496
496
|
// framework's, the hook's is the provider-facing ToolCallPayload.
|
|
497
|
-
|
|
497
|
+
// 0430-F1: the fresh path's preflight, in the same order and with the
|
|
498
|
+
// same words — a call the kernel refuses before any policy (unknown
|
|
499
|
+
// tool, unparsable arguments, a schema failure) is refused here the
|
|
500
|
+
// same way: one invalid_input result, no decision, no execution.
|
|
501
|
+
const tool = this.#config.registry.get(call.name);
|
|
502
|
+
const schemaError = tool !== undefined && call.input !== null ? validateArgs(tool.parameters, call.input) : null;
|
|
503
|
+
const refusal = tool === undefined ? `Unknown tool: ${call.name}` : call.input === null ? "Arguments failed to parse as JSON" : schemaError !== null ? `Arguments failed schema validation:${schemaError}` : undefined;
|
|
504
|
+
if (refusal !== undefined) {
|
|
505
|
+
yield log.append({ type: "tool_result", callId: call.callId, invocationSeq: call.seq, content: refusal, isError: true, errorKind: "invalid_input" });
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
// #20: the same lexical evidence the fresh path hands onPreTool — THIS
|
|
509
|
+
// invocation's deltas, anchored by its seq; never given to the chain.
|
|
510
|
+
const rawInput = rawInputOf(log.all, call.callId, call.seq);
|
|
511
|
+
const payload = { callId: call.callId, name: call.name, input: call.input ?? {}, ...(rawInput !== undefined ? { rawInput } : {}) };
|
|
498
512
|
const policyCall = { name: payload.name, input: payload.input };
|
|
499
513
|
let verdict;
|
|
500
514
|
try {
|
|
@@ -525,13 +539,26 @@ export class Run {
|
|
|
525
539
|
}
|
|
526
540
|
if (verdict === undefined)
|
|
527
541
|
verdict = { action: "allow" }; // no policies — the kernel's default allow
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
//
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
//
|
|
534
|
-
//
|
|
542
|
+
// 0430-F1: a verdict WITHOUT a speaker — the default allow, an
|
|
543
|
+
// onPreTool allow or deny — is not a durable fact on the fresh path
|
|
544
|
+
// and is not one here: allow goes straight to the execution (the
|
|
545
|
+
// plan no longer re-decides a started invocation), deny straight to
|
|
546
|
+
// the denial result. Only a speaking chain's verdict is recorded,
|
|
547
|
+
// exactly as the fresh path records it. (Before: a fabricated
|
|
548
|
+
// "mode:default" decision, written so the plan could make progress.)
|
|
549
|
+
if (!("decidedBy" in verdict)) {
|
|
550
|
+
if (verdict.action === "allow") {
|
|
551
|
+
yield* this.#executeInvocation(call.seq, scope, log, signal);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (verdict.action === "deny") {
|
|
555
|
+
yield* this.#denialResult(call.callId, ("reason" in verdict && verdict.reason !== undefined ? verdict.reason : undefined) ?? "denied", call.seq);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
// A speaking chain's decision binds the call (the plan's E1 rule: a
|
|
560
|
+
// decidedBy-carrying decision binds the call — a human verdict binds
|
|
561
|
+
// its request, never the call).
|
|
535
562
|
const decisionId = `d-${log.all.length + 1}`;
|
|
536
563
|
if (verdict.action === "allow") {
|
|
537
564
|
yield log.append({
|
|
@@ -655,6 +682,9 @@ export class Run {
|
|
|
655
682
|
? {
|
|
656
683
|
type: "tool_result",
|
|
657
684
|
callId: ev.callId,
|
|
685
|
+
// 0430-F1: the receipt's invocation identity rides the fill, as
|
|
686
|
+
// a fresh result carries call.seq — bound exactly ever after.
|
|
687
|
+
...(ev.invocationSeq !== undefined ? { invocationSeq: ev.invocationSeq } : {}),
|
|
658
688
|
content: ev.result.content,
|
|
659
689
|
isError: false,
|
|
660
690
|
// round 8: the repaired result reproduces the normal path
|
|
@@ -665,6 +695,7 @@ export class Run {
|
|
|
665
695
|
: {
|
|
666
696
|
type: "tool_result",
|
|
667
697
|
callId: ev.callId,
|
|
698
|
+
...(ev.invocationSeq !== undefined ? { invocationSeq: ev.invocationSeq } : {}),
|
|
668
699
|
content: ev.error,
|
|
669
700
|
isError: true,
|
|
670
701
|
...(ev.errorKind !== undefined ? { errorKind: ev.errorKind } : {}),
|
|
@@ -687,7 +718,10 @@ export class Run {
|
|
|
687
718
|
const decided = log.all.find((e) => e.type === "permission_decided" &&
|
|
688
719
|
(request !== undefined
|
|
689
720
|
? e.decisionId === request.decisionId
|
|
690
|
-
: call !== undefined &&
|
|
721
|
+
: call !== undefined &&
|
|
722
|
+
e.decidedBy !== undefined &&
|
|
723
|
+
// 0430-F1: exact invocation first; an old log's decision by callId
|
|
724
|
+
(e.invocationSeq !== undefined ? e.invocationSeq === call.seq : e.callId === call.callId && e.seq > call.seq)));
|
|
691
725
|
if (decided === undefined || (request?.callId ?? call?.callId) === undefined) {
|
|
692
726
|
throw new Error(`the recovery plan derived a denial without its decision (seq ${invocationSeq})`);
|
|
693
727
|
}
|
|
@@ -755,6 +789,12 @@ export class Run {
|
|
|
755
789
|
const log = this.#session.log;
|
|
756
790
|
const tool = this.#config.registry.get(name);
|
|
757
791
|
const executionId = `ex-${log.lastSeq + 1}`;
|
|
792
|
+
// 0.43.0 (#13, 0420-F1): ToolContext is a function of the durable
|
|
793
|
+
// invocation and execution, never of fresh vs recovery — the same
|
|
794
|
+
// sessionId, callId, executionId and lexical arguments the fresh path
|
|
795
|
+
// hands the handler, the last derived from THIS invocation's deltas.
|
|
796
|
+
const rawInput = rawInputOf(log.all, callId, invocationSeq);
|
|
797
|
+
const ctx = { signal, sessionId: this.#session.id, callId, executionId, ...(rawInput !== undefined ? { rawInput } : {}) };
|
|
758
798
|
// An abort that landed while the decision was being applied must
|
|
759
799
|
// not start the side effect (finding 3).
|
|
760
800
|
if (signal.aborted)
|
|
@@ -777,7 +817,7 @@ export class Run {
|
|
|
777
817
|
result = { content: "aborted before execution", isError: true, errorKind: "fatal" };
|
|
778
818
|
}
|
|
779
819
|
else {
|
|
780
|
-
result = await tool.execute(input,
|
|
820
|
+
result = await tool.execute(input, ctx);
|
|
781
821
|
}
|
|
782
822
|
}
|
|
783
823
|
catch (err) {
|
|
@@ -788,7 +828,7 @@ export class Run {
|
|
|
788
828
|
};
|
|
789
829
|
}
|
|
790
830
|
if (this.#config.hooks?.onPostTool) {
|
|
791
|
-
result = await this.#config.hooks.onPostTool({ callId, name, input }, result, { sessionId: this.#session.id });
|
|
831
|
+
result = await this.#config.hooks.onPostTool({ callId, name, input, ...(rawInput !== undefined ? { rawInput } : {}) }, result, { sessionId: this.#session.id });
|
|
792
832
|
}
|
|
793
833
|
}
|
|
794
834
|
// ruling #12 correction one: the honest note rides the recovered failure too —
|
|
@@ -850,6 +890,7 @@ export class Run {
|
|
|
850
890
|
content: denial.content,
|
|
851
891
|
isError: true,
|
|
852
892
|
errorKind: denial.errorKind,
|
|
893
|
+
tags: denial.tags, // 0430-F1: the fresh path's denial carries them
|
|
853
894
|
});
|
|
854
895
|
}
|
|
855
896
|
}
|
|
@@ -861,3 +902,27 @@ function resolveReasoningOrThrow(model, setting, endpoint) {
|
|
|
861
902
|
throw new Error(`the session's recorded reasoning setting cannot run here: ${r.reason}`);
|
|
862
903
|
return Object.keys(r.wire).length > 0 ? { reasoning: r.wire } : {};
|
|
863
904
|
}
|
|
905
|
+
/** 0.43.0 (#13): the invocation's lexical arguments from the durable
|
|
906
|
+
* log — the tool_call_input_delta events between ITS tool_call_start
|
|
907
|
+
* and ITS tool_call_end (anchored by invocationSeq, never "the latest
|
|
908
|
+
* same callId"), the window the kernel buffered on the fresh path.
|
|
909
|
+
* Undefined when no delta was streamed, or when the invocation's seq is
|
|
910
|
+
* unknown. */
|
|
911
|
+
function rawInputOf(events, callId, invocationSeq) {
|
|
912
|
+
if (invocationSeq === undefined)
|
|
913
|
+
return undefined;
|
|
914
|
+
const end = events.findIndex((e) => e.seq === invocationSeq);
|
|
915
|
+
if (end < 0)
|
|
916
|
+
return undefined;
|
|
917
|
+
let text;
|
|
918
|
+
for (let i = end - 1; i >= 0; i -= 1) {
|
|
919
|
+
const e = events[i];
|
|
920
|
+
if (e.type === "tool_call_start" && e.callId === callId)
|
|
921
|
+
break;
|
|
922
|
+
if (e.type === "stop" || e.type === "user_input" || e.type === "terminal" || e.type === "assistant_start")
|
|
923
|
+
break;
|
|
924
|
+
if (e.type === "tool_call_input_delta" && e.callId === callId)
|
|
925
|
+
text = e.inputJsonDelta + (text ?? "");
|
|
926
|
+
}
|
|
927
|
+
return text;
|
|
928
|
+
}
|
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";
|
|
@@ -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
|
@@ -501,8 +501,9 @@ export function estimateEventTokens(ev) {
|
|
|
501
501
|
* rounds exist (nothing worth covering yet).
|
|
502
502
|
*
|
|
503
503
|
* ⑥ (task round): a tool result tagged do-not-compact is DURABLE work
|
|
504
|
-
* memory (
|
|
505
|
-
*
|
|
504
|
+
* memory (a tool declares it so; the retired task extension's list was
|
|
505
|
+
* the first) — the summary must never cover its round, or the model
|
|
506
|
+
* loses it. When the base boundary would
|
|
506
507
|
* cover such a result, the boundary pulls back to just before the round
|
|
507
508
|
* containing the LATEST one (still a turn boundary). A protected round
|
|
508
509
|
* as the FIRST uncovered round leaves nothing before it to cover →
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.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.44.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.44.0",
|
|
32
|
+
"@vincemakes/kiso-provider-openai": "0.44.0",
|
|
33
|
+
"@vincemakes/kiso-provider-openai-responses": "0.44.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.44.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
|
-
}
|