@vincemakes/kiso-core 0.10.0 → 0.12.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.
@@ -8,10 +8,21 @@
8
8
  * the same events the loop yielded, so the verdict is replayable and the
9
9
  * model's narration never participates in its own grading.
10
10
  *
11
- * Producers are declared on tools (`delivers`, tools/tool.ts); the verdict
12
- * counts producer calls that completed (non-error results), against a
13
- * delivery claim in the text. The canonical lie "generated-document" with zero
14
- * producer calls and a clean completed terminal fails here.
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.
15
26
  *
16
27
  * In M3.5 the emission side (artifact URLs extracted from results) joins;
17
28
  * today a completed producer IS the emission.
@@ -8,10 +8,21 @@
8
8
  * the same events the loop yielded, so the verdict is replayable and the
9
9
  * model's narration never participates in its own grading.
10
10
  *
11
- * Producers are declared on tools (`delivers`, tools/tool.ts); the verdict
12
- * counts producer calls that completed (non-error results), against a
13
- * delivery claim in the text. The canonical lie "generated-document" with zero
14
- * producer calls and a clean completed terminal fails here.
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.
15
26
  *
16
27
  * In M3.5 the emission side (artifact URLs extracted from results) joins;
17
28
  * today a completed producer IS the emission.
@@ -21,6 +21,8 @@ import type { Message } from "../protocol/messages.js";
21
21
  /**
22
22
  * Rough token estimate (chars/4 + structural overhead). Calibration-free on
23
23
  * purpose: context economy only needs a stable MONOTONE proxy, not an exact
24
- * count — the threshold absorbs the error (mauri ADR-0007).
24
+ * count — the threshold absorbs the error (mauri ADR-0007). The proxy's
25
+ * three-word contract, and the pins that hold it, are
26
+ * packages/core/tests/sc1b-estimator.test.ts.
25
27
  */
26
28
  export declare function estimateTokens(messages: readonly Message[]): number;
@@ -17,16 +17,43 @@
17
17
  * The model-generated half of context economy (the /compact summary layer)
18
18
  * lives in kernel/summarize.ts.
19
19
  */
20
+ /**
21
+ * What a non-text block (an image) contributes. There is no character count
22
+ * to proxy, and this module refuses to guess a provider's image
23
+ * tokenization; the figure exists so a block is never worth ZERO, which is
24
+ * what MONOTONE requires. Deliberately small: under-stating an image is a
25
+ * threshold that fires slightly late, over-stating it is one that fires on
26
+ * a conversation that was never large.
27
+ */
28
+ const NON_TEXT_BLOCK_TOKENS = 8;
29
+ /**
30
+ * chars/4 over a `string | ContentBlock[]` content field.
31
+ *
32
+ * SC-1b ①: the array arm must be summed BLOCK BY BLOCK. Reading `.length`
33
+ * off it yields the block COUNT — a five-block 50 KB message scored ~2
34
+ * tokens, and the live microcompact threshold believed it.
35
+ */
36
+ function contentTokens(content) {
37
+ if (typeof content === "string")
38
+ return Math.ceil(content.length / 4);
39
+ let tokens = 0;
40
+ for (const block of content) {
41
+ tokens += block.type === "text" ? Math.ceil(block.text.length / 4) : NON_TEXT_BLOCK_TOKENS;
42
+ }
43
+ return tokens;
44
+ }
20
45
  /**
21
46
  * Rough token estimate (chars/4 + structural overhead). Calibration-free on
22
47
  * purpose: context economy only needs a stable MONOTONE proxy, not an exact
23
- * count — the threshold absorbs the error (mauri ADR-0007).
48
+ * count — the threshold absorbs the error (mauri ADR-0007). The proxy's
49
+ * three-word contract, and the pins that hold it, are
50
+ * packages/core/tests/sc1b-estimator.test.ts.
24
51
  */
25
52
  export function estimateTokens(messages) {
26
53
  let total = 0;
27
54
  for (const msg of messages) {
28
55
  if (msg.role === "user") {
29
- total += Math.ceil(msg.content.length / 4);
56
+ total += contentTokens(msg.content);
30
57
  }
31
58
  else if (msg.role === "assistant") {
32
59
  for (const block of msg.blocks) {
@@ -37,7 +64,7 @@ export function estimateTokens(messages) {
37
64
  }
38
65
  }
39
66
  else {
40
- total += Math.ceil(msg.content.length / 4) + 10;
67
+ total += contentTokens(msg.content) + 10;
41
68
  }
42
69
  }
43
70
  return total;
@@ -37,7 +37,12 @@ export declare class EventLog {
37
37
  */
38
38
  append(ev: EventInput): Event;
39
39
  get all(): readonly Event[];
40
- /** Incremental view for consumers that already saw `seq` and before. */
40
+ /**
41
+ * Incremental view for consumers that already saw `seq` and before —
42
+ * STRICTLY after, so a poll loop delivers each event exactly once. A
43
+ * consumer that has seen nothing passes -1. (SC-1b ④: this filtered
44
+ * `>= seq` and re-served the seam event on every poll.)
45
+ */
41
46
  since(seq: number): readonly Event[];
42
47
  get lastSeq(): number;
43
48
  }
@@ -41,9 +41,14 @@ export class EventLog {
41
41
  get all() {
42
42
  return this.#events;
43
43
  }
44
- /** Incremental view for consumers that already saw `seq` and before. */
44
+ /**
45
+ * Incremental view for consumers that already saw `seq` and before —
46
+ * STRICTLY after, so a poll loop delivers each event exactly once. A
47
+ * consumer that has seen nothing passes -1. (SC-1b ④: this filtered
48
+ * `>= seq` and re-served the seam event on every poll.)
49
+ */
45
50
  since(seq) {
46
- return this.#events.filter((e) => e.seq >= seq);
51
+ return this.#events.filter((e) => e.seq > seq);
47
52
  }
48
53
  get lastSeq() {
49
54
  return this.#next - 1;
@@ -2,8 +2,8 @@
2
2
  * L2 — the ReAct loop. The kernel's only loop; everything else is harness.
3
3
  *
4
4
  * An async generator that yields every event as it happens (never buffers a
5
- * turn into a list — the agno failure), and converges on exactly one
6
- * `terminal` event per run (ADR-0004).
5
+ * turn into a list — the reference implementation's failure), and converges
6
+ * on exactly one `terminal` event per run (ADR-0004).
7
7
  *
8
8
  * SINGLE TRUTH (Phase B): the loop holds ONE EventLog. Messages are never
9
9
  * stored alongside it — every adapter call derives them via
@@ -100,13 +100,23 @@ export interface LoopConfig {
100
100
  */
101
101
  readonly approvalVerdict?: (decisionId: string) => boolean | undefined;
102
102
  /**
103
- * C group: the channel that resolves a failed NON-idempotent execution.
104
- * The loop persists `uncertain_pending`, yields it, and AWAITS the
105
- * human verdict no next model turn, no sibling tool, no auto-retry.
106
- * Absent, the failure is recorded `abandoned` (never retried).
103
+ * DEAD accepted by the type, never read by this loop.
104
+ *
105
+ * It was the C group channel for the failed-receipt pause: the loop
106
+ * persisted `uncertain_pending` and awaited a human verdict. ADR-0038
107
+ * removed that pause (a complete receipt IS the outcome), and with it
108
+ * every read of this field — a failure is now simply recorded failed and
109
+ * the siblings run on. The runtime still passes both callbacks
110
+ * (runtime/run.ts), so supplying them is harmless and changes nothing.
111
+ *
112
+ * The crash window — started, no receipt — is the ONLY uncertainty left,
113
+ * and the runtime's recovery driver owns it (RESOLVE_UNCERTAIN in
114
+ * runtime/recovery-plan.ts), not the loop. Both fields are kept because
115
+ * the surface is frozen (ADR-0051); do not read them as live wiring.
107
116
  */
108
117
  readonly resolveUncertainty?: (executionId: string) => Promise<"rerun" | "abandoned">;
109
- /** round 4 (adversarial): the uncertainty twin of `approvalVerdict`. */
118
+ /** DEAD, as above the uncertainty twin of `approvalVerdict`
119
+ * (round 4, adversarial). Never read by the loop since ADR-0038. */
110
120
  readonly uncertaintyVerdict?: (executionId: string) => "rerun" | "abandoned" | undefined;
111
121
  /**
112
122
  * E1: the COMPOSED approval chain — the runtime composes the
@@ -2,8 +2,8 @@
2
2
  * L2 — the ReAct loop. The kernel's only loop; everything else is harness.
3
3
  *
4
4
  * An async generator that yields every event as it happens (never buffers a
5
- * turn into a list — the agno failure), and converges on exactly one
6
- * `terminal` event per run (ADR-0004).
5
+ * turn into a list — the reference implementation's failure), and converges
6
+ * on exactly one `terminal` event per run (ADR-0004).
7
7
  *
8
8
  * SINGLE TRUTH (Phase B): the loop holds ONE EventLog. Messages are never
9
9
  * stored alongside it — every adapter call derives them via
@@ -6,21 +6,19 @@
6
6
  * - `visibleToolNames` — physical removal: the registry is subset() to these
7
7
  * tools BEFORE the adapter is called. The model cannot call what it cannot
8
8
  * see; no system-prompt overlay can make that guarantee.
9
- * - `systemOverlay` — appended to the system prompt by the harness (the
10
- * kernel does not compose prompts); kept here because it is part of the
11
- * mode's contract.
12
- * - `permissionDefault`the decision onPreTool falls back to when no hook
13
- * or store decides.
14
- * - `compactionKeepExtra` / `stopPredicate` reserved (ADR-0011); read by
15
- * the harness when the semantics land.
9
+ *
10
+ * That is the whole profile. SC-1b: `systemOverlay`, `permissionDefault`,
11
+ * `compactionKeepExtra`, and `stopPredicate` were REMOVED at 0.12.0 by the
12
+ * SC-1 memo's adjudication all four were declared here and read by
13
+ * nothing, in the kernel or above it. Two of them ("reserved, read by the
14
+ * harness when the semantics land") had been waiting since mauri ADR-0011
15
+ * for semantics that never landed. A field that describes an intention
16
+ * rather than a behavior is a promise the type system makes on the
17
+ * kernel's behalf and the kernel does not keep; the honest profile is the
18
+ * one member that is actually applied.
16
19
  */
17
- import type { PermissionDecision } from "./permission.js";
18
20
  export interface ModeProfile {
19
21
  readonly name: string;
20
- readonly systemOverlay?: string;
21
22
  readonly visibleToolNames?: readonly string[];
22
- readonly permissionDefault?: PermissionDecision;
23
- readonly compactionKeepExtra?: readonly string[];
24
- readonly stopPredicate?: string;
25
23
  }
26
24
  export declare function resolveModeProfile(modes: readonly ModeProfile[] | undefined, name: string | undefined): ModeProfile | undefined;
@@ -6,13 +6,16 @@
6
6
  * - `visibleToolNames` — physical removal: the registry is subset() to these
7
7
  * tools BEFORE the adapter is called. The model cannot call what it cannot
8
8
  * see; no system-prompt overlay can make that guarantee.
9
- * - `systemOverlay` — appended to the system prompt by the harness (the
10
- * kernel does not compose prompts); kept here because it is part of the
11
- * mode's contract.
12
- * - `permissionDefault`the decision onPreTool falls back to when no hook
13
- * or store decides.
14
- * - `compactionKeepExtra` / `stopPredicate` reserved (ADR-0011); read by
15
- * the harness when the semantics land.
9
+ *
10
+ * That is the whole profile. SC-1b: `systemOverlay`, `permissionDefault`,
11
+ * `compactionKeepExtra`, and `stopPredicate` were REMOVED at 0.12.0 by the
12
+ * SC-1 memo's adjudication all four were declared here and read by
13
+ * nothing, in the kernel or above it. Two of them ("reserved, read by the
14
+ * harness when the semantics land") had been waiting since mauri ADR-0011
15
+ * for semantics that never landed. A field that describes an intention
16
+ * rather than a behavior is a promise the type system makes on the
17
+ * kernel's behalf and the kernel does not keep; the honest profile is the
18
+ * one member that is actually applied.
16
19
  */
17
20
  export function resolveModeProfile(modes, name) {
18
21
  if (!name || !modes)
@@ -5,9 +5,18 @@
5
5
  * allow one call, deny with a reason fed back to the model, or defer to a
6
6
  * human. The kernel's contract is only the decision shape; where decisions
7
7
  * are stored (accept-for-session) is harness territory (PermissionStore,
8
- * M2). M1 treats `defer` as a deny with reason "awaiting user" — the model
9
- * sees the refusal and can adjust; the human-in-the-loop wiring arrives
10
- * with the harness.
8
+ * M2).
9
+ *
10
+ * `defer` IS A REAL PAUSE. The M1 note that once lived here — "defer is a
11
+ * deny with reason 'awaiting user'; the human-in-the-loop wiring arrives
12
+ * with the harness" — described the behavior ADR-0024 decision #3 replaced,
13
+ * and that ADR's own Context names this note as the thing it fixed (a
14
+ * denial "fakes the pause": the model sees a refusal and adjusts while no
15
+ * human ever decided). Shipped now: the loop persists `permission_requested`
16
+ * and AWAITS the approval channel in the same run frame, the human's
17
+ * verdict is persisted as `permission_decided`, and the pause survives a
18
+ * crash (kernel/loop.ts). With no approval channel configured the deferral
19
+ * degrades to an HONEST denial that says so.
11
20
  */
12
21
  export type PermissionDecision = {
13
22
  readonly action: "allow";
@@ -5,9 +5,18 @@
5
5
  * allow one call, deny with a reason fed back to the model, or defer to a
6
6
  * human. The kernel's contract is only the decision shape; where decisions
7
7
  * are stored (accept-for-session) is harness territory (PermissionStore,
8
- * M2). M1 treats `defer` as a deny with reason "awaiting user" — the model
9
- * sees the refusal and can adjust; the human-in-the-loop wiring arrives
10
- * with the harness.
8
+ * M2).
9
+ *
10
+ * `defer` IS A REAL PAUSE. The M1 note that once lived here — "defer is a
11
+ * deny with reason 'awaiting user'; the human-in-the-loop wiring arrives
12
+ * with the harness" — described the behavior ADR-0024 decision #3 replaced,
13
+ * and that ADR's own Context names this note as the thing it fixed (a
14
+ * denial "fakes the pause": the model sees a refusal and adjusts while no
15
+ * human ever decided). Shipped now: the loop persists `permission_requested`
16
+ * and AWAITS the approval channel in the same run frame, the human's
17
+ * verdict is persisted as `permission_decided`, and the pause survives a
18
+ * crash (kernel/loop.ts). With no approval channel configured the deferral
19
+ * degrades to an HONEST denial that says so.
11
20
  */
12
21
  /** The denial a tool result carries when a call was refused pre-flight. */
13
22
  export function denialResult(reason) {
@@ -19,8 +19,10 @@
19
19
  * re-runs the compaction algorithm (a future version could differ, A group/D
20
20
  * group); `microcompacted` boundaries re-derive the cleared view from the
21
21
  * stream itself (deterministic and idempotent); `summarized` (ADR-0044)
22
- * replaces its covered range with one assistant summary message. All
23
- * three are persisted factsthe replay equals the live run. See ADR-0002.
22
+ * replaces its covered range with one USER message carrying the summary
23
+ * behind SUMMARY_FRAMING (E6's boundary honesty see that constant
24
+ * below; it is deliberately NOT an assistant message). All three are
25
+ * persisted facts — the replay equals the live run. See ADR-0002.
24
26
  */
25
27
  import type { Event } from "../protocol/events.js";
26
28
  import type { EventInput } from "./event-log.js";
@@ -19,8 +19,10 @@
19
19
  * re-runs the compaction algorithm (a future version could differ, A group/D
20
20
  * group); `microcompacted` boundaries re-derive the cleared view from the
21
21
  * stream itself (deterministic and idempotent); `summarized` (ADR-0044)
22
- * replaces its covered range with one assistant summary message. All
23
- * three are persisted factsthe replay equals the live run. See ADR-0002.
22
+ * replaces its covered range with one USER message carrying the summary
23
+ * behind SUMMARY_FRAMING (E6's boundary honesty see that constant
24
+ * below; it is deliberately NOT an assistant message). All three are
25
+ * persisted facts — the replay equals the live run. See ADR-0002.
24
26
  */
25
27
  /**
26
28
  * C area: tools whose output is eligible for microcompact clearing — reads,
@@ -8,9 +8,9 @@
8
8
  * events carry `seq`). The three properties that let a ReAct loop stream
9
9
  * without buffering and without losing its place.
10
10
  *
11
- * WHY not a `Promise<Event[]>`: a buffered adapter is what made agno's
12
- * streaming a second-class feature bolted onto a batch loop. The contract
13
- * shape IS the architecture — see ADR-0001.
11
+ * WHY not a `Promise<Event[]>`: a buffered adapter is what made the
12
+ * reference implementation's streaming a second-class feature bolted onto
13
+ * a batch loop. The contract shape IS the architecture — see ADR-0001.
14
14
  *
15
15
  * Adapters translate provider wire events INTO `Event`. They never see tool
16
16
  * handlers (only `ToolSpec` projections) — the kernel is the only thing that
@@ -8,9 +8,9 @@
8
8
  * events carry `seq`). The three properties that let a ReAct loop stream
9
9
  * without buffering and without losing its place.
10
10
  *
11
- * WHY not a `Promise<Event[]>`: a buffered adapter is what made agno's
12
- * streaming a second-class feature bolted onto a batch loop. The contract
13
- * shape IS the architecture — see ADR-0001.
11
+ * WHY not a `Promise<Event[]>`: a buffered adapter is what made the
12
+ * reference implementation's streaming a second-class feature bolted onto
13
+ * a batch loop. The contract shape IS the architecture — see ADR-0001.
14
14
  *
15
15
  * Adapters translate provider wire events INTO `Event`. They never see tool
16
16
  * handlers (only `ToolSpec` projections) — the kernel is the only thing that
@@ -220,10 +220,17 @@ export interface ToolExecutionSucceeded {
220
220
  readonly tags?: readonly string[];
221
221
  }
222
222
  /**
223
- * The side effect ran and FAILED. `safeToRetry` is the tool's own proof
224
- * (declared idempotent): only then is a failure a clean "failed"; a
225
- * non-idempotent failure may have produced a side effect and is UNCERTAIN
226
- * until a human decides (Area 3).
223
+ * The side effect ran and FAILED. A complete receipt IS the outcome, so this
224
+ * execution is "failed" NEVER "uncertain", whatever `safeToRetry` says
225
+ * (ADR-0038 superseding ADR-0025 decision #3: uncertainty belongs to the
226
+ * crash window alone started, no receipt). The run does not pause here;
227
+ * siblings continue and the model retries with the error in hand, and that
228
+ * retry is a NEW call that re-passes the approval chain.
229
+ *
230
+ * `safeToRetry` is the tool's own `idempotent: true` declaration, carried
231
+ * for HISTORY: since ADR-0038 it no longer feeds the ledger's status
232
+ * derivation (runtime/ledger.ts). Its one live consequence is the honest
233
+ * note appended to a non-idempotent failure's result (ADR-0038 Amendment 1).
227
234
  */
228
235
  export interface ToolExecutionFailed {
229
236
  readonly seq: number;
@@ -294,11 +301,20 @@ export interface PermissionExpired {
294
301
  readonly reason: string;
295
302
  }
296
303
  /**
297
- * A non-idempotent execution FAILED and the run PAUSES until a human
298
- * decides (C group): no next model turn, no sibling tool, no auto-retry. The
299
- * verdict is recorded by the session (resolveUncertain) and the ledger
300
- * transitions uncertain rerun/abandoned; the event itself is the durable
301
- * pause marker.
304
+ * HISTORICAL the current kernel NEVER emits this event.
305
+ *
306
+ * It marked the C group's failed-receipt pause: a non-idempotent execution
307
+ * that FAILED paused the run until a human ruled. ADR-0038 removed that
308
+ * pause (a complete receipt IS the outcome), so nothing appends this any
309
+ * more — pinned by `packages/core/tests/execution-gate.test.ts` and
310
+ * `packages/runtime/tests/execution.test.ts`, which assert its ABSENCE.
311
+ *
312
+ * The variant stays because the durable contract is frozen (ADR-0051) and
313
+ * old logs replay theirs verbatim (ADR-0038, Consequences): the projection
314
+ * renders nothing for it, and the ledger reports those receipted executions
315
+ * as "failed". The crash window — started, no receipt — is now the only
316
+ * source of uncertainty, and it is resolved through
317
+ * `uncertainExecutions()` / `resolveUncertain()`, not through this event.
302
318
  */
303
319
  export interface UncertainPending {
304
320
  readonly seq: number;
@@ -342,9 +358,11 @@ export interface MicroCompactEvent {
342
358
  * range. `coversToSeq` is the seq of the LAST covered event; the covered
343
359
  * range runs from just past the previous `summarized` event's coversToSeq
344
360
  * (or the trajectory's start for the first) up to coversToSeq. The
345
- * projection replaces exactly those events with ONE assistant summary
346
- * message (`summary`); every `summarized` event always renders its own
347
- * message. Byte-stable: a summarized event is a persisted fact, so the
361
+ * projection replaces exactly those events with ONE USER message carrying
362
+ * the summary behind a fixed framing line (E6's boundary honesty: the
363
+ * model reads compressed history as CONTEXT, never as a reply it produced
364
+ * — see SUMMARY_FRAMING in kernel/project.ts). It is not an assistant
365
+ * message; every `summarized` event always renders its own message. Byte-stable: a summarized event is a persisted fact, so the
348
366
  * same events derive the same messages on every replay.
349
367
  *
350
368
  * The summary is generated OFF-LOOP through the session's own adapter —
@@ -83,7 +83,7 @@ export interface KisoExtension {
83
83
  * E2: EXTEND the system prompt — append-only, never replace (a replace
84
84
  * is a footgun; appends guarantee "adding an extension never removes
85
85
  * existing guidance" — the monotonicity family of the approval chain's
86
- * deny>ask>allow and the veto short-circuit). The session's own
86
+ * deny > allow > ask and the veto short-circuit). The session's own
87
87
  * systemPrompt comes first, then each extension's append in load order,
88
88
  * \n\n-joined.
89
89
  */
@@ -10,7 +10,12 @@
10
10
  * carries images. Forcing every caller into the array shape to serve the
11
11
  * 5% that need vision is a tax paid on every line of product code.
12
12
  *
13
- * See ADR-0003 (sum type) and ADR-0008 (tool contract).
13
+ * See ADR-0003 (sum type). The tool contract's record is mauri ADR-0008
14
+ * the PREDECESSOR series, cited the same way in kernel/hooks.ts and
15
+ * kernel/mode.ts: kiso's own numbers 0006-0019 were NEVER assigned
16
+ * (docs/adrs/README.md), so an unprefixed "ADR-0008" sends a reader to a
17
+ * record that does not exist in this repo. The live tool contract is
18
+ * tools/tool.ts itself.
14
19
  *
15
20
  * This module is types-only: it compiles to nothing.
16
21
  */
@@ -107,8 +112,12 @@ export type Message = UserMessage | AssistantMessage | ToolResultMessage;
107
112
  /**
108
113
  * What the adapter advertises to the model.
109
114
  *
110
- * Deliberately minimal: the full tool (handler, repair, concurrency, dedup)
111
- * is L3-internal and an adapter must never see it. An adapter that can reach
115
+ * Deliberately minimal: the full tool (the handler above all, plus the
116
+ * declarations the kernel keeps to itself) is L3-internal and an adapter
117
+ * must never see it. NB the historical list here named a `repair` member
118
+ * and a `dedup` member — neither exists on `Tool`: the (name, input) dedup
119
+ * guard was removed by ADR-0025, and repair means RECEIPT repair, which is
120
+ * the runtime's, not a tool's. An adapter that can reach
112
121
  * the handler will eventually call it, and then the kernel is no longer the
113
122
  * only thing that runs tools.
114
123
  */
@@ -10,7 +10,12 @@
10
10
  * carries images. Forcing every caller into the array shape to serve the
11
11
  * 5% that need vision is a tax paid on every line of product code.
12
12
  *
13
- * See ADR-0003 (sum type) and ADR-0008 (tool contract).
13
+ * See ADR-0003 (sum type). The tool contract's record is mauri ADR-0008
14
+ * the PREDECESSOR series, cited the same way in kernel/hooks.ts and
15
+ * kernel/mode.ts: kiso's own numbers 0006-0019 were NEVER assigned
16
+ * (docs/adrs/README.md), so an unprefixed "ADR-0008" sends a reader to a
17
+ * record that does not exist in this repo. The live tool contract is
18
+ * tools/tool.ts itself.
14
19
  *
15
20
  * This module is types-only: it compiles to nothing.
16
21
  */
@@ -4,9 +4,15 @@
4
4
  * A tool is a pure declaration + handler pair. The kernel never branches on
5
5
  * a tool's internals: `parameters` is a JSON Schema the kernel validates and
6
6
  * projects to the adapter as `ToolSpec` (adapter never sees the handler —
7
- * see ADR-0001), `concurrencySafe` decides batch scheduling, `delivers`
8
- * marks a tool as an artifact producer (consumed by harness-side delivery
9
- * tracking; the kernel only carries the flag).
7
+ * see ADR-0001).
8
+ *
9
+ * SC-1b: `concurrencySafe` and `delivers` were REMOVED at 0.12.0 by the
10
+ * SC-1 memo's adjudication — both were declared and consulted by nothing.
11
+ * Their absence is the honest contract; the concurrency RACE they were
12
+ * mistaken for a defense against is a real open question and moved to EC-1,
13
+ * which owes a mechanism that does not depend on a per-tool opt-in.
14
+ * Delivery truth is named by the CALLER (governance/delivery.ts's
15
+ * `DeliveryConfig.producers`), which is where it always actually lived.
10
16
  *
11
17
  * WHY JSON Schema instead of a runtime library: the kernel has zero runtime
12
18
  * dependencies (ADR-0001). Zod / TypeBox / valibot live at the harness layer;
@@ -49,22 +55,28 @@ export interface Tool<I = unknown> {
49
55
  /** JSON Schema (draft-07 subset). Validated before execute. */
50
56
  readonly parameters: Readonly<Record<string, unknown>>;
51
57
  /**
52
- * Per-call concurrency predicate a shape the reference implementation's
53
- * static executionMode cannot express: the same tool may be parallel-safe for one
54
- * input and must be serial for another (generate_image with
55
- * `chain_to_previous`). Absent = safe when true-ish; see ADR-0015.
56
- */
57
- readonly concurrencySafe?: (input: I) => boolean;
58
- /** Marks this tool as an artifact producer (harness-side delivery truth). */
59
- readonly delivers?: {
60
- readonly kind: string;
61
- };
62
- /**
63
- * Exactly-once guard escape hatch (Phase D): a tool whose side effects
64
- * are safe to repeat (reads, searches, pure computations) declares
65
- * `idempotent: true`. Without it, the kernel refuses to run the same
66
- * tool+input twice in a session a confirmed success is replayed, an
67
- * interrupted attempt blocks. The default is the safe side.
58
+ * The tool's own declaration that REPEATING its side effect is safe
59
+ * (reads, searches, pure computations).
60
+ *
61
+ * It is NOT a dedup guard. The kernel runs every logical call, including
62
+ * one whose (name, input) is identical to an earlier one — ADR-0024
63
+ * decision #2's (name, input) guard was REMOVED by ADR-0025 decision #1
64
+ * ("NO (name, input) dedup", kernel/loop.ts). Exactly-once is enforced
65
+ * by execution IDENTITY instead: a confirmed success is never
66
+ * re-executed (a lost model-facing result is repaired FROM the durable
67
+ * receipt), and an execution that STARTED without reporting — the crash
68
+ * window — waits for a human verdict. A FAILED execution is failed, not
69
+ * uncertain: a complete receipt IS the outcome (ADR-0038).
70
+ *
71
+ * What this flag actually decides, and nothing more:
72
+ * - a failure from a tool that did NOT declare it carries the honest
73
+ * "side effects may have partially applied; verify before retrying"
74
+ * note on the result (ADR-0038 Amendment 1);
75
+ * - it rides the durable receipt as `tool_execution_failed.safeToRetry`
76
+ * — history only, since ADR-0038 stopped it feeding ledger status.
77
+ *
78
+ * Undeclared is the safe side: unknown idempotency means the note
79
+ * applies. A retry is a NEW call and re-passes the approval chain.
68
80
  */
69
81
  readonly idempotent?: boolean;
70
82
  /** R-C: ONE line for the system prompt — the tool's role, never the
@@ -4,9 +4,15 @@
4
4
  * A tool is a pure declaration + handler pair. The kernel never branches on
5
5
  * a tool's internals: `parameters` is a JSON Schema the kernel validates and
6
6
  * projects to the adapter as `ToolSpec` (adapter never sees the handler —
7
- * see ADR-0001), `concurrencySafe` decides batch scheduling, `delivers`
8
- * marks a tool as an artifact producer (consumed by harness-side delivery
9
- * tracking; the kernel only carries the flag).
7
+ * see ADR-0001).
8
+ *
9
+ * SC-1b: `concurrencySafe` and `delivers` were REMOVED at 0.12.0 by the
10
+ * SC-1 memo's adjudication — both were declared and consulted by nothing.
11
+ * Their absence is the honest contract; the concurrency RACE they were
12
+ * mistaken for a defense against is a real open question and moved to EC-1,
13
+ * which owes a mechanism that does not depend on a per-tool opt-in.
14
+ * Delivery truth is named by the CALLER (governance/delivery.ts's
15
+ * `DeliveryConfig.producers`), which is where it always actually lived.
10
16
  *
11
17
  * WHY JSON Schema instead of a runtime library: the kernel has zero runtime
12
18
  * dependencies (ADR-0001). Zod / TypeBox / valibot live at the harness layer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-core",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "kiso (foundation) core — 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.10.0",
36
+ "@vincemakes/kiso-evals": "0.12.0",
37
37
  "@types/node": "^26.1.2",
38
38
  "typescript": "^5.7.2",
39
39
  "vitest": "^3.0.0"