@vincemakes/kiso-core 0.15.2 → 0.15.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-core",
3
- "version": "0.15.2",
3
+ "version": "0.15.3",
4
4
  "description": "kiso (foundation) core \u2014 protocol, event log, loop, hooks, modes, permissions, compaction, delivery truth. The 2,000-line kernel at the bottom of the kiso framework.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "openai"
34
34
  ],
35
35
  "devDependencies": {
36
- "@vincemakes/kiso-evals": "0.15.2",
36
+ "@vincemakes/kiso-evals": "0.15.3",
37
37
  "@types/node": "^26.1.2",
38
38
  "typescript": "^5.7.2",
39
39
  "vitest": "^3.0.0"
@@ -1,46 +0,0 @@
1
- /**
2
- * Delivery truth — "done" means the ledger says so, not the model (uooki
3
- * done_guard, 30 incidents distilled).
4
- *
5
- * The kernel's terminal is honest but shallow: `completed` means "the loop
6
- * ended on its own terms". Whether the turn DELIVERED what was asked is a
7
- * harness-side verdict over the trajectory — this module computes it from
8
- * the same events the loop yielded, so the verdict is replayable and the
9
- * model's narration never participates in its own grading.
10
- *
11
- * Producers are named by the CALLER, in `DeliveryConfig.producers` — the
12
- * hand-maintained set is the ONLY source of delivery truth. SC-1b removed
13
- * `Tool.delivers`, the flag this note used to describe as the eventual
14
- * replacement: it was declared on the contract and read by nothing, here
15
- * or anywhere. Should a tool's own declaration ever supersede the caller's
16
- * set, it enters as a new field with a wiring and a gate, never as a
17
- * standing promise the code has not kept.
18
- *
19
- * The verdict counts producer calls that COMPLETED (non-error results).
20
- * `claimedInText` is computed and REPORTED but does not enter `passed` —
21
- * a caller that wants "claimed but not delivered" to fail combines the two
22
- * itself (the evals fixture does). With `required: false`, text claiming
23
- * delivery over zero producer calls passes here. The canonical lie —
24
- * "generated-document" with zero producer calls and a clean completed
25
- * terminal — is caught by that combination.
26
- *
27
- * In M3.5 the emission side (artifact URLs extracted from results) joins;
28
- * today a completed producer IS the emission.
29
- */
30
- import type { Event } from "../protocol/events.js";
31
- export interface DeliveryConfig {
32
- /** Whether this turn was required to deliver at all. */
33
- readonly required: boolean;
34
- /** Tool names that produce a deliverable. */
35
- readonly producers: ReadonlySet<string>;
36
- }
37
- export interface DeliveryVerdict {
38
- readonly passed: boolean;
39
- /** Producer calls the model actually made. */
40
- readonly producerCalls: readonly string[];
41
- /** Producer calls that completed (non-error result). */
42
- readonly completedProducers: readonly string[];
43
- /** The text claimed delivery (a claim is a lie only when unbacked). */
44
- readonly claimedInText: boolean;
45
- }
46
- export declare function analyzeDelivery(events: readonly Event[], config: DeliveryConfig): DeliveryVerdict;
@@ -1,58 +0,0 @@
1
- /**
2
- * Delivery truth — "done" means the ledger says so, not the model (uooki
3
- * done_guard, 30 incidents distilled).
4
- *
5
- * The kernel's terminal is honest but shallow: `completed` means "the loop
6
- * ended on its own terms". Whether the turn DELIVERED what was asked is a
7
- * harness-side verdict over the trajectory — this module computes it from
8
- * the same events the loop yielded, so the verdict is replayable and the
9
- * model's narration never participates in its own grading.
10
- *
11
- * Producers are named by the CALLER, in `DeliveryConfig.producers` — the
12
- * hand-maintained set is the ONLY source of delivery truth. SC-1b removed
13
- * `Tool.delivers`, the flag this note used to describe as the eventual
14
- * replacement: it was declared on the contract and read by nothing, here
15
- * or anywhere. Should a tool's own declaration ever supersede the caller's
16
- * set, it enters as a new field with a wiring and a gate, never as a
17
- * standing promise the code has not kept.
18
- *
19
- * The verdict counts producer calls that COMPLETED (non-error results).
20
- * `claimedInText` is computed and REPORTED but does not enter `passed` —
21
- * a caller that wants "claimed but not delivered" to fail combines the two
22
- * itself (the evals fixture does). With `required: false`, text claiming
23
- * delivery over zero producer calls passes here. The canonical lie —
24
- * "generated-document" with zero producer calls and a clean completed
25
- * terminal — is caught by that combination.
26
- *
27
- * In M3.5 the emission side (artifact URLs extracted from results) joins;
28
- * today a completed producer IS the emission.
29
- */
30
- const CLAIM_PATTERN = /created|completed|delivered|done/i;
31
- export function analyzeDelivery(events, config) {
32
- const producerCalls = [];
33
- const completedProducers = [];
34
- let claimedInText = false;
35
- for (const ev of events) {
36
- switch (ev.type) {
37
- case "text_delta":
38
- if (CLAIM_PATTERN.test(ev.text))
39
- claimedInText = true;
40
- break;
41
- case "tool_call_end":
42
- if (config.producers.has(ev.name))
43
- producerCalls.push(ev.callId);
44
- break;
45
- case "tool_result":
46
- if (producerCalls.includes(ev.callId) && !ev.isError) {
47
- completedProducers.push(ev.callId);
48
- }
49
- break;
50
- }
51
- }
52
- return {
53
- passed: !config.required || completedProducers.length > 0,
54
- producerCalls,
55
- completedProducers,
56
- claimedInText,
57
- };
58
- }
@@ -1,45 +0,0 @@
1
- /**
2
- * L2 — the execution ledger: exactly-once side effects from the event log.
3
- *
4
- * Every tool execution writes `tool_execution_started` before the handler
5
- * and `tool_execution_succeeded` / `tool_execution_failed` after
6
- * (kernel/loop.ts). From those events alone — no second store — this module
7
- * answers the recovery questions:
8
- *
9
- * 1. What is the durable status of execution X? (`executionLedger`)
10
- * 2. What is the latest execution of call Y? (`executionForCallId`)
11
- *
12
- * IDENTITY (Area 3): the ledger is keyed by `executionId` — a persistent,
13
- * framework-generated id unique per log (one per started event). The
14
- * provider's `callId` is correlation only and may repeat; two logical calls
15
- * with identical (name, input) are two executions.
16
- *
17
- * Status derivation:
18
- * started, no terminal event yet → "uncertain" (interrupted: human)
19
- * succeeded → "succeeded" (confirmed, never re-run)
20
- * failed (any) → "failed" (a complete receipt IS
21
- * the outcome — ruling #12 / ADR-0038;
22
- * safeToRetry stays on the event for
23
- * history, it no longer feeds status)
24
- * resolved "rerun" → "rerun" (human cleared it)
25
- * resolved "abandoned" → "abandoned" (human killed it)
26
- */
27
- import type { Event } from "../protocol/events.js";
28
- export type ExecutionStatus = "uncertain" | "succeeded" | "failed" | "rerun" | "abandoned";
29
- export interface ExecutionRecord {
30
- readonly executionId: string;
31
- readonly callId: string;
32
- readonly name: string;
33
- readonly input: Readonly<Record<string, unknown>>;
34
- readonly status: ExecutionStatus;
35
- /** Present when `status` is "succeeded" — the durable result to replay. */
36
- readonly result?: {
37
- readonly content: string;
38
- readonly isError: false;
39
- };
40
- readonly error?: string;
41
- }
42
- /** executionId → durable status, rebuilt purely from events (ADR-0002). */
43
- export declare function executionLedger(events: readonly Event[]): Map<string, ExecutionRecord>;
44
- /** The LATEST execution record for a provider call id (correlation only). */
45
- export declare function executionForCallId(events: readonly Event[], callId: string): ExecutionRecord | undefined;
@@ -1,90 +0,0 @@
1
- /**
2
- * L2 — the execution ledger: exactly-once side effects from the event log.
3
- *
4
- * Every tool execution writes `tool_execution_started` before the handler
5
- * and `tool_execution_succeeded` / `tool_execution_failed` after
6
- * (kernel/loop.ts). From those events alone — no second store — this module
7
- * answers the recovery questions:
8
- *
9
- * 1. What is the durable status of execution X? (`executionLedger`)
10
- * 2. What is the latest execution of call Y? (`executionForCallId`)
11
- *
12
- * IDENTITY (Area 3): the ledger is keyed by `executionId` — a persistent,
13
- * framework-generated id unique per log (one per started event). The
14
- * provider's `callId` is correlation only and may repeat; two logical calls
15
- * with identical (name, input) are two executions.
16
- *
17
- * Status derivation:
18
- * started, no terminal event yet → "uncertain" (interrupted: human)
19
- * succeeded → "succeeded" (confirmed, never re-run)
20
- * failed (any) → "failed" (a complete receipt IS
21
- * the outcome — ruling #12 / ADR-0038;
22
- * safeToRetry stays on the event for
23
- * history, it no longer feeds status)
24
- * resolved "rerun" → "rerun" (human cleared it)
25
- * resolved "abandoned" → "abandoned" (human killed it)
26
- */
27
- /** executionId → durable status, rebuilt purely from events (ADR-0002). */
28
- export function executionLedger(events) {
29
- const ledger = new Map();
30
- for (const ev of events) {
31
- switch (ev.type) {
32
- case "tool_execution_started":
33
- ledger.set(ev.executionId, {
34
- executionId: ev.executionId,
35
- callId: ev.callId,
36
- name: ev.name,
37
- input: ev.input,
38
- status: "uncertain",
39
- });
40
- break;
41
- case "tool_execution_succeeded": {
42
- const prior = ledger.get(ev.executionId);
43
- if (prior) {
44
- ledger.set(ev.executionId, { ...prior, status: "succeeded", result: ev.result });
45
- }
46
- break;
47
- }
48
- case "tool_execution_failed": {
49
- const prior = ledger.get(ev.executionId);
50
- if (prior) {
51
- ledger.set(ev.executionId, {
52
- ...prior,
53
- // ruling #12 (ADR-0038): a complete receipt IS the outcome —
54
- // failed is "failed", never "uncertain"; uncertainty
55
- // belongs to the crash window alone (started, no receipt).
56
- status: "failed",
57
- ...(ev.error !== undefined ? { error: ev.error } : {}),
58
- });
59
- }
60
- break;
61
- }
62
- case "tool_execution_resolved": {
63
- const prior = ledger.get(ev.executionId);
64
- if (prior) {
65
- ledger.set(ev.executionId, {
66
- ...prior,
67
- status: ev.resolution === "rerun" ? "rerun" : "abandoned",
68
- });
69
- }
70
- break;
71
- }
72
- default:
73
- break;
74
- }
75
- }
76
- return ledger;
77
- }
78
- /** The LATEST execution record for a provider call id (correlation only). */
79
- export function executionForCallId(events, callId) {
80
- const ledger = executionLedger(events);
81
- let found;
82
- for (const ev of events) {
83
- if (ev.type !== "tool_execution_started")
84
- continue;
85
- if (ev.callId !== callId)
86
- continue;
87
- found = ledger.get(ev.executionId);
88
- }
89
- return found;
90
- }
@@ -1,58 +0,0 @@
1
- /**
2
- * L2 — the /compact summary layer (ADR-0044): the MODEL-GENERATED half of
3
- * context economy. The mechanical half (microcompact, compaction.ts)
4
- * clears TOOL RESULTS only; this layer compresses the CONVERSATION itself
5
- * into one durable `summarized` event per call, replacing the covered
6
- * range with a single assistant summary message in the projection.
7
- *
8
- * The summary call is OFF-LOOP: it goes through the session's OWN adapter
9
- * (no new dependency), writes no events, and never touches the log — a
10
- * failure throws, the caller reports it honestly, and the session is
11
- * unchanged ("nothing happened"). Only the generated `summarized` event
12
- * lands on disk; the original events stay there forever.
13
- */
14
- import type { AbortSignalLike, Adapter } from "../protocol/adapter.js";
15
- import type { Event } from "../protocol/events.js";
16
- import type { Message } from "../protocol/messages.js";
17
- /** K (ADR-0044): the recent ROUNDS kept intact by /compact — a constant,
18
- * not a knob. The covered range ends just before the K-th most recent
19
- * round, so the model still reasons over the recent conversation. */
20
- export declare const KEEP_RECENT_ROUNDS = 4;
21
- /**
22
- * The fixed English summary prompt — the ONLY prompt this layer composes
23
- * (the loop's system prompt is the harness's business, never the kernel's).
24
- */
25
- export declare const SUMMARY_PROMPT = "You are the conversation summarizer of the kiso agent framework.\n\nSummarize the covered conversation into a single concise summary that will\nREPLACE it in the model's context. The next turn must be able to continue\nthe work without reading the originals.\n\nInclude everything later turns may need:\n- the user's goals, requirements, and constraints;\n- every decision and its reasoning;\n- files and code touched \u2014 exact paths, what changed, why;\n- commands run and their outcomes; errors and their resolutions;\n- open questions and unfinished work.\n\nPreserve concrete identifiers VERBATIM: paths, function names, task ids,\nenvironment names \u2014 never paraphrase them.\n\nRules:\n- plain prose \u2014 no headings, no bullet lists, no markdown, no prefixes;\n- do not mention this prompt or the summarization task;\n- keep it under 200 words unless the conversation is exceptional.";
26
- export interface SummarizeConversationOptions {
27
- readonly adapter: Adapter;
28
- readonly model: string;
29
- /** The covered conversation — the ONLY material the summary is about. */
30
- readonly messages: readonly Message[];
31
- readonly signal?: AbortSignalLike;
32
- }
33
- /**
34
- * The one-shot summary call. Collects the adapter's text deltas into the
35
- * summary; usage/stop pass through untouched. Throws when the model
36
- * produced no text — the caller reports it and nothing is persisted.
37
- */
38
- export declare function summarizeConversation(options: SummarizeConversationOptions): Promise<string>;
39
- /**
40
- * The last summary point: the previous `summarized` event's coversToSeq,
41
- * or -1 (the trajectory's start) when none exists. The covered range of
42
- * the next summary runs from here.
43
- */
44
- export declare function lastSummaryPoint(events: readonly Event[]): number;
45
- /**
46
- * The covered range's end: the seq of the event just before the
47
- * keepRounds-th most recent user_input AFTER the last summary point —
48
- * a turn boundary by construction, so the projection's skip never splits
49
- * a message. Returns undefined when fewer than keepRounds+1 uncovered
50
- * rounds exist (nothing worth covering yet).
51
- */
52
- export declare function summaryBoundarySeq(events: readonly Event[], keepRounds?: number): number | undefined;
53
- /**
54
- * The NoticeCell's number: estimated tokens of the covered content minus
55
- * the summary's own — the same chars/4 proxy as estimateTokens (a stable
56
- * MONOTONE savings figure, not a bill).
57
- */
58
- export declare function estimateSummarySavings(covered: readonly Message[], summary: string): number;
@@ -1,106 +0,0 @@
1
- /**
2
- * L2 — the /compact summary layer (ADR-0044): the MODEL-GENERATED half of
3
- * context economy. The mechanical half (microcompact, compaction.ts)
4
- * clears TOOL RESULTS only; this layer compresses the CONVERSATION itself
5
- * into one durable `summarized` event per call, replacing the covered
6
- * range with a single assistant summary message in the projection.
7
- *
8
- * The summary call is OFF-LOOP: it goes through the session's OWN adapter
9
- * (no new dependency), writes no events, and never touches the log — a
10
- * failure throws, the caller reports it honestly, and the session is
11
- * unchanged ("nothing happened"). Only the generated `summarized` event
12
- * lands on disk; the original events stay there forever.
13
- */
14
- import { estimateTokens } from "./compaction.js";
15
- /** K (ADR-0044): the recent ROUNDS kept intact by /compact — a constant,
16
- * not a knob. The covered range ends just before the K-th most recent
17
- * round, so the model still reasons over the recent conversation. */
18
- export const KEEP_RECENT_ROUNDS = 4;
19
- /**
20
- * The fixed English summary prompt — the ONLY prompt this layer composes
21
- * (the loop's system prompt is the harness's business, never the kernel's).
22
- */
23
- export const SUMMARY_PROMPT = `You are the conversation summarizer of the kiso agent framework.
24
-
25
- Summarize the covered conversation into a single concise summary that will
26
- REPLACE it in the model's context. The next turn must be able to continue
27
- the work without reading the originals.
28
-
29
- Include everything later turns may need:
30
- - the user's goals, requirements, and constraints;
31
- - every decision and its reasoning;
32
- - files and code touched — exact paths, what changed, why;
33
- - commands run and their outcomes; errors and their resolutions;
34
- - open questions and unfinished work.
35
-
36
- Preserve concrete identifiers VERBATIM: paths, function names, task ids,
37
- environment names — never paraphrase them.
38
-
39
- Rules:
40
- - plain prose — no headings, no bullet lists, no markdown, no prefixes;
41
- - do not mention this prompt or the summarization task;
42
- - keep it under 200 words unless the conversation is exceptional.`;
43
- /**
44
- * The one-shot summary call. Collects the adapter's text deltas into the
45
- * summary; usage/stop pass through untouched. Throws when the model
46
- * produced no text — the caller reports it and nothing is persisted.
47
- */
48
- export async function summarizeConversation(options) {
49
- const { adapter, model, messages } = options;
50
- let text = "";
51
- for await (const ev of adapter.stream({
52
- model,
53
- messages,
54
- systemPrompt: SUMMARY_PROMPT,
55
- ...(options.signal !== undefined ? { signal: options.signal } : {}),
56
- })) {
57
- if (ev.type === "text_delta")
58
- text += ev.text;
59
- }
60
- const trimmed = text.trim();
61
- if (trimmed === "") {
62
- throw new Error("the summary call produced no text");
63
- }
64
- return trimmed;
65
- }
66
- /**
67
- * The last summary point: the previous `summarized` event's coversToSeq,
68
- * or -1 (the trajectory's start) when none exists. The covered range of
69
- * the next summary runs from here.
70
- */
71
- export function lastSummaryPoint(events) {
72
- let prev = -1;
73
- for (const ev of events) {
74
- if (ev.type === "summarized" && ev.coversToSeq > prev)
75
- prev = ev.coversToSeq;
76
- }
77
- return prev;
78
- }
79
- /**
80
- * The covered range's end: the seq of the event just before the
81
- * keepRounds-th most recent user_input AFTER the last summary point —
82
- * a turn boundary by construction, so the projection's skip never splits
83
- * a message. Returns undefined when fewer than keepRounds+1 uncovered
84
- * rounds exist (nothing worth covering yet).
85
- */
86
- export function summaryBoundarySeq(events, keepRounds = KEEP_RECENT_ROUNDS) {
87
- const prevPoint = lastSummaryPoint(events);
88
- const uncoveredInputs = [];
89
- for (const ev of events) {
90
- if (ev.type === "user_input" && ev.seq > prevPoint)
91
- uncoveredInputs.push(ev.seq);
92
- }
93
- if (uncoveredInputs.length <= keepRounds)
94
- return undefined;
95
- // The input at m - keepRounds opens the FIRST KEPT round; everything
96
- // before it (m - keepRounds ≥ 1 covered rounds) is summarizable.
97
- return uncoveredInputs[uncoveredInputs.length - keepRounds] - 1;
98
- }
99
- /**
100
- * The NoticeCell's number: estimated tokens of the covered content minus
101
- * the summary's own — the same chars/4 proxy as estimateTokens (a stable
102
- * MONOTONE savings figure, not a bill).
103
- */
104
- export function estimateSummarySavings(covered, summary) {
105
- return Math.max(0, estimateTokens(covered) - Math.ceil(summary.length / 4));
106
- }