@vincemakes/kiso-runtime 0.46.1 → 0.47.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.
@@ -29,7 +29,7 @@ export { canonicalizeUsageForModel } from "./usage/canonical.js";
29
29
  export { BUILTIN_MANIFESTS, resolveContinuationScope, type ModelRef, type ProviderManifest } from "./provider/manifest.js";
30
30
  export * from "./checkpoint.js";
31
31
  export * from "./request-budget.js";
32
- export { readProfile, readSummary, writeSummary, type ExecutionProfile, type ProfileReadResult } from "./profile.js";
32
+ export { readProfile, readSessionName, readSummary, writeSessionName, writeSummary, type ExecutionProfile, type ProfileReadResult } from "./profile.js";
33
33
  export { SUMMARY_MIGRATION_MARKER, listSessionSidecars, migrateSummaries, sessionSummary, summarizeEvents, summaryMigrationPending, type SessionListing, type SessionSummary } from "./session-summary.js";
34
34
  export * from "./tasks/journal.js";
35
35
  export * from "./tasks/manager.js";
package/dist/internal.js CHANGED
@@ -38,7 +38,7 @@ export { BUILTIN_MANIFESTS, resolveContinuationScope } from "./provider/manifest
38
38
  export * from "./checkpoint.js";
39
39
  export * from "./request-budget.js";
40
40
  // 0.40.0: the listing reads the recorded workspace and profile name.
41
- export { readProfile, readSummary, writeSummary } from "./profile.js";
41
+ export { readProfile, readSessionName, readSummary, writeSessionName, writeSummary } from "./profile.js";
42
42
  export { SUMMARY_MIGRATION_MARKER, listSessionSidecars, migrateSummaries, sessionSummary, summarizeEvents, summaryMigrationPending } from "./session-summary.js";
43
43
  // ADR-0058 §6: the task journal and the TaskManager (internal until the
44
44
  // public shape is proven by the shell and delegate steps)
package/dist/profile.d.ts CHANGED
@@ -83,6 +83,17 @@ export declare function writeProfile(root: string, sessionId: string, profile: E
83
83
  export declare function writeSummary(root: string, sessionId: string, summary: import("./session-summary.js").SessionSummary): void;
84
84
  /** The summary tenant, or null (no sidecar, no summary, or unreadable). */
85
85
  export declare function readSummary(root: string, sessionId: string): import("./session-summary.js").SessionSummary | null;
86
+ /** Graphite R3d — the name the person gave the session (`/name`): the
87
+ * sidecar's third tenant, beside `profile` and `summary`, carried
88
+ * through their writes untouched as they are through its. Session
89
+ * metadata, not an event (ADR-0051 §6's OUT class, the profile's own
90
+ * footing): the derivation never reads it; the lists and the title do,
91
+ * the derived title (`sessionTitle`) the fallback. Null when unnamed or
92
+ * unreadable. */
93
+ export declare function readSessionName(root: string, sessionId: string): string | null;
94
+ /** Name the session, or clear its name (`null`). Atomic and fail-closed
95
+ * like every tenant's write; the others' bytes ride through. */
96
+ export declare function writeSessionName(root: string, sessionId: string, name: string | null): void;
86
97
  export type ProfileReadResult = {
87
98
  readonly kind: "ok";
88
99
  readonly profile: ExecutionProfile;
package/dist/profile.js CHANGED
@@ -80,6 +80,24 @@ export function readSummary(root, sessionId) {
80
80
  const t = currentTenants(root, sessionId).summary;
81
81
  return t !== undefined && typeof t.updatedAt === "number" ? t : null;
82
82
  }
83
+ /** Graphite R3d — the name the person gave the session (`/name`): the
84
+ * sidecar's third tenant, beside `profile` and `summary`, carried
85
+ * through their writes untouched as they are through its. Session
86
+ * metadata, not an event (ADR-0051 §6's OUT class, the profile's own
87
+ * footing): the derivation never reads it; the lists and the title do,
88
+ * the derived title (`sessionTitle`) the fallback. Null when unnamed or
89
+ * unreadable. */
90
+ export function readSessionName(root, sessionId) {
91
+ const n = currentTenants(root, sessionId).name;
92
+ return typeof n === "string" && n.trim() !== "" ? n : null;
93
+ }
94
+ /** Name the session, or clear its name (`null`). Atomic and fail-closed
95
+ * like every tenant's write; the others' bytes ride through. */
96
+ export function writeSessionName(root, sessionId, name) {
97
+ const tenants = { ...currentTenants(root, sessionId) };
98
+ delete tenants.name;
99
+ writeTenants(root, sessionId, name === null ? tenants : { ...tenants, name });
100
+ }
83
101
  function writeTenants(root, sessionId, tenants) {
84
102
  const path = profilePath(root, sessionId);
85
103
  const tmpDir = mkdtempSync(join(root, ".meta-"));
@@ -68,6 +68,9 @@ export interface SessionListing {
68
68
  readonly summary: SessionSummary | null;
69
69
  readonly workspace: string | null;
70
70
  readonly profileName: string | null;
71
+ /** Graphite R3d: the name the person gave it (`/name`), or null —
72
+ * optional, so a listing built before names existed still types */
73
+ readonly name?: string | null;
71
74
  }
72
75
  /** Every session in `root`, from the DIRECTORY and the sidecars only — no
73
76
  * log is opened. The 0.40.0 dogfood's list path. */
@@ -23,7 +23,7 @@
23
23
  */
24
24
  import { existsSync, readdirSync, statSync, writeFileSync } from "node:fs";
25
25
  import { join } from "node:path";
26
- import { readProfile, readSummary, writeSummary } from "./profile.js";
26
+ import { readProfile, readSessionName, readSummary, writeSummary } from "./profile.js";
27
27
  import { executionLedger } from "./ledger.js";
28
28
  import { openRunId } from "./recovery.js";
29
29
  import { sessionTitle } from "./store.js";
@@ -124,6 +124,7 @@ export function listSessionSidecars(root) {
124
124
  summary: readSummary(root, id),
125
125
  workspace: profile.kind === "ok" ? profile.profile.workspace : null,
126
126
  profileName: profile.kind === "ok" ? profile.profile.profileName : null,
127
+ name: readSessionName(root, id),
127
128
  });
128
129
  }
129
130
  return out;
package/dist/session.d.ts CHANGED
@@ -130,7 +130,7 @@ export declare class AgentSession {
130
130
  /** Deliver this session's task transitions to the model: into a live
131
131
  * run at its next safe point, or — idle — held for the next run, or one
132
132
  * continuation run through `onWake` (off with `wake: false`). */
133
- useTasks(manager: TaskManager, options?: Pick<TaskDeliveryOptions, "wake" | "onWake" | "windowMs"> & {
133
+ useTasks(manager: TaskManager, options?: Pick<TaskDeliveryOptions, "wake" | "onWake" | "windowMs" | "onLost"> & {
134
134
  readonly autoDetachMinAgeMs?: number;
135
135
  }): () => void;
136
136
  /** ADR-0057 §5 (3e D1) — a person's steer arrived: the foreground
@@ -514,6 +514,30 @@ export interface ContextPolicy {
514
514
  readonly model: string;
515
515
  readonly baseUrl?: string;
516
516
  }) => void;
517
+ /** The compaction round (owner, 2026-10-06): an in-run summary call,
518
+ * told as `/compact`'s caller is told one — when it starts (the tier
519
+ * that fired it, and `onStart`'s pre-call data: the covered rounds
520
+ * and their estimated tokens), its progress against the output
521
+ * budget (`onProgress`'s reports), and how it ended: `kept` (a
522
+ * `summarized` event follows), `discarded` (it did not shrink the
523
+ * context; `onDiscard` is told too) or `failed` (both paths refused,
524
+ * or the run was aborted). `pre` and `post` are the context's
525
+ * chars/4 estimates before and after (equal when nothing was kept).
526
+ * Display only: nothing here reaches a request, and a callback that
527
+ * throws changes nothing. */
528
+ readonly onSummary?: (event: {
529
+ readonly phase: "start";
530
+ readonly reason: string;
531
+ readonly info: CompactInfo;
532
+ } | {
533
+ readonly phase: "progress";
534
+ readonly progress: import("./summarize.js").SummaryProgress;
535
+ } | {
536
+ readonly phase: "end";
537
+ readonly outcome: "kept" | "discarded" | "failed";
538
+ readonly pre: number;
539
+ readonly post: number;
540
+ }) => void;
517
541
  };
518
542
  }
519
543
  export interface SessionConfig {
package/dist/session.js CHANGED
@@ -385,7 +385,7 @@ export class AgentSession {
385
385
  const breakerOpen = reason !== "overflow" && this.#summaryFailures >= (tiersPolicy.maxFailures ?? MAX_SUMMARY_FAILURES);
386
386
  if (breakerOpen && !urgent)
387
387
  return [];
388
- const summarized = breakerOpen ? null : await this.#summarizeInRun(events, messages, t.tail, reason, run, tiersPolicy.onDiscard);
388
+ const summarized = breakerOpen ? null : await this.#summarizeInRun(events, messages, t.tail, reason, run, tiersPolicy.onDiscard, tiersPolicy.onSummary);
389
389
  if (summarized !== null)
390
390
  return [summarized];
391
391
  if (!urgent)
@@ -441,10 +441,32 @@ export class AgentSession {
441
441
  /** A2 — one in-run summary: in-band first, the serialised form once on
442
442
  * a rejection; null when neither produced a valid checkpoint. The
443
443
  * summary call's usage and path ride the trace ledger, as /compact's do. */
444
- async #summarizeInRun(events, messages, tail, reason, run, onDiscard) {
444
+ async #summarizeInRun(events, messages, tail, reason, run, onDiscard, onSummary) {
445
445
  const boundary = checkpointBoundarySeq(events, { keepTokens: tail });
446
446
  if (boundary === undefined)
447
447
  return null;
448
+ // the compaction round (owner, 2026-10-06): what /compact's onStart
449
+ // and onProgress tell its caller, told here too — display only, and
450
+ // a callback that throws changes nothing
451
+ const tell = (event) => {
452
+ try {
453
+ onSummary?.(event);
454
+ }
455
+ catch {
456
+ // observation only
457
+ }
458
+ };
459
+ const prevPoint = lastSummaryPoint(events);
460
+ const pre = estimateTokens(projectMessages(events));
461
+ tell({
462
+ phase: "start",
463
+ reason,
464
+ info: {
465
+ coversToSeq: boundary,
466
+ rounds: events.filter((e) => e.type === "user_input" && e.seq > prevPoint && e.seq <= boundary).length,
467
+ tokens: estimateTokens(projectMessages(events.filter((e) => e.seq > prevPoint && e.seq <= boundary && e.type !== "summarized"))),
468
+ },
469
+ });
448
470
  const summaryPrompt = typeof run.systemPrompt === "function" ? run.systemPrompt() : run.systemPrompt;
449
471
  let path;
450
472
  let result;
@@ -453,15 +475,17 @@ export class AgentSession {
453
475
  messages,
454
476
  inBand: { ...(summaryPrompt !== undefined ? { systemPrompt: summaryPrompt } : {}), tools: run.tools() },
455
477
  ...(run.reasoning !== undefined ? { reasoning: run.reasoning } : {}),
456
- serialized: () => serializeCovered({ events, prevPoint: lastSummaryPoint(events), boundary }),
478
+ serialized: () => serializeCovered({ events, prevPoint, boundary }),
457
479
  serializedReasoning: true,
458
480
  budget: MANUAL_SUMMARY_BUDGET,
459
481
  ...(run.signal !== undefined ? { signal: run.signal } : {}),
482
+ ...(onSummary !== undefined ? { onProgress: (progress) => tell({ phase: "progress", progress }) } : {}),
460
483
  }));
461
484
  }
462
485
  catch {
463
486
  if (run.signal?.aborted !== true)
464
487
  this.#summaryFailures += 1;
488
+ tell({ phase: "end", outcome: "failed", pre, post: pre });
465
489
  return null;
466
490
  }
467
491
  // ADR-0055 Amendment 2 (the shrink invariant): a fire is kept only if
@@ -472,7 +496,6 @@ export class AgentSession {
472
496
  // counts toward the breaker.
473
497
  const text = this.#withTasks(result.text);
474
498
  const fire = { type: "summarized", coversToSeq: boundary, summary: text };
475
- const pre = estimateTokens(projectMessages(events));
476
499
  const post = estimateTokens(projectMessages([...events, { ...fire, seq: (events.at(-1)?.seq ?? -1) + 1 }]));
477
500
  const written = estimateTokens([{ role: "user", content: `${SUMMARY_FRAMING}\n\n${text}` }]);
478
501
  const shrinks = pre - post >= written;
@@ -498,9 +521,11 @@ export class AgentSession {
498
521
  catch {
499
522
  // observation only — a notice that cannot be shown changes nothing
500
523
  }
524
+ tell({ phase: "end", outcome: "discarded", pre, post });
501
525
  return null;
502
526
  }
503
527
  this.#summaryFailures = 0;
528
+ tell({ phase: "end", outcome: "kept", pre, post });
504
529
  return fire;
505
530
  }
506
531
  /**
package/dist/store.js CHANGED
@@ -45,6 +45,7 @@
45
45
  import { appendFileSync, closeSync, constants, existsSync, fsyncSync, fstatSync, statSync, ftruncateSync, mkdirSync, openSync, readFileSync, readdirSync, readSync, writeSync, } from "node:fs";
46
46
  import { dirname, join } from "node:path";
47
47
  import { isKisoEvent } from "@vincemakes/kiso-core";
48
+ import { readSessionName } from "./profile.js";
48
49
  import { LockedError, nativeLockAdapter, } from "./lock-adapter.js";
49
50
  /** History that does not parse as a contiguous kiso trajectory. */
50
51
  export class StoreCorruptionError extends Error {
@@ -441,7 +442,9 @@ export class SessionStore {
441
442
  continue;
442
443
  metas.push({
443
444
  id,
444
- title: sessionTitle(records),
445
+ // Graphite R3d: the name the person gave the session, when it has
446
+ // one; the title derived from its first real line otherwise
447
+ title: readSessionName(this.root, id) ?? sessionTitle(records),
445
448
  events: records.length,
446
449
  runs: new Set(records.map((r) => r.runId)).size,
447
450
  createdAt: records[0]?.ts ?? 0,
@@ -56,6 +56,10 @@ export interface TaskDeliveryOptions {
56
56
  }) => void;
57
57
  /** Transitions within this window merge into one notice. Default 1 s. */
58
58
  readonly windowMs?: number;
59
+ /** ADR-0058 Amendment 8: a loss the model has not been told of, at
60
+ * startup or as it is heard — the person can be told at once, while
61
+ * the model's notice keeps its own turn. A claimed loss never comes. */
62
+ readonly onLost?: (taskIds: readonly string[]) => void;
59
63
  /** How much of a failed task's output rides its notice. Default 2 KB. */
60
64
  readonly tailBytes?: number;
61
65
  /** Read a task's output tail (injectable for tests). */
@@ -69,6 +69,7 @@ export class TaskDelivery {
69
69
  // delivered, is delivered now as a notify — never a wake at startup
70
70
  this.#scan();
71
71
  const missed = [];
72
+ const lost = [];
72
73
  for (const task of this.#safeList()) {
73
74
  const item = itemOf(task);
74
75
  if (item === null || item.transition === "ready" || this.#receipts.has(key(item)))
@@ -76,6 +77,8 @@ export class TaskDelivery {
76
77
  // a tool result reported it: the claim alone decides (Amendment 7)
77
78
  if (task.claims?.some((c) => c.transition === item.transition) === true)
78
79
  continue;
80
+ if (item.transition === "unknown")
81
+ lost.push(task.id);
79
82
  if (task.agent !== undefined)
80
83
  this.#ended.set(task.id, { restart: true });
81
84
  else
@@ -85,6 +88,8 @@ export class TaskDelivery {
85
88
  this.#pending.push({ notice: { lines: missed.map((m) => m.line), items: missed.map((m) => m.item) }, wake: false });
86
89
  if (this.#ended.size > 0)
87
90
  this.#groups();
91
+ if (lost.length > 0)
92
+ options.onLost?.(lost);
88
93
  }
89
94
  close() {
90
95
  this.#unsubscribe();
@@ -154,6 +159,8 @@ export class TaskDelivery {
154
159
  const item = itemOf(task, transition);
155
160
  if (item === null)
156
161
  return;
162
+ if (item.transition === "unknown")
163
+ this.#o.onLost?.([task.id]);
157
164
  if (task.agent !== undefined && item.transition !== "ready")
158
165
  return this.#agentEnded(task, item);
159
166
  const mode = this.#modeOf(task, item);
@@ -76,6 +76,15 @@ export class TaskManager {
76
76
  if (cached !== undefined && cached.key === key && (cached.verdict === "gone" || (!fresh && Date.now() - cached.at < this.#identifyEveryMs)))
77
77
  return cached.verdict;
78
78
  const verdict = this.#backend.identify(runner.pid, runner.startedAt);
79
+ // Amendment 8: a probe that could not READ the identity just now (a
80
+ // loaded machine) is not a lost runner. For the state, a runner last
81
+ // verified, with a recorded start time, keeps that verdict until the
82
+ // next window; a stop checks fresh and still gets the raw answer, so
83
+ // a runner not verified NOW is never signalled.
84
+ if (verdict === "unverifiable" && runner.startedAt !== "" && cached?.key === key && cached.verdict === "verified") {
85
+ this.#identity.set(id, { key, verdict: "verified", at: Date.now() });
86
+ return fresh ? verdict : "verified";
87
+ }
79
88
  this.#identity.set(id, { key, verdict, at: Date.now() });
80
89
  return verdict;
81
90
  }
@@ -373,7 +382,9 @@ export class TaskManager {
373
382
  if (this.#waiting.has(id))
374
383
  continue;
375
384
  const before = this.#seen.get(id);
376
- if (before === "ended" || before === "unknown" || before === "not_run")
385
+ // a lost (`unknown`) task is still read: if its end is recorded
386
+ // later, that end is announced (Amendment 8)
387
+ if (before === "ended" || before === "not_run")
377
388
  continue;
378
389
  let info;
379
390
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-runtime",
3
- "version": "0.46.1",
3
+ "version": "0.47.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.46.1"
28
+ "@vincemakes/kiso-core": "0.47.0"
29
29
  },
30
30
  "peerDependencies": {
31
- "@vincemakes/kiso-provider-anthropic": "0.46.1",
32
- "@vincemakes/kiso-provider-openai": "0.46.1",
33
- "@vincemakes/kiso-provider-openai-responses": "0.46.1"
31
+ "@vincemakes/kiso-provider-anthropic": "0.47.0",
32
+ "@vincemakes/kiso-provider-openai": "0.47.0",
33
+ "@vincemakes/kiso-provider-openai-responses": "0.47.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.46.1",
47
+ "@vincemakes/kiso-evals": "0.47.0",
48
48
  "@types/node": "^26.1.2",
49
49
  "typescript": "^5.7.2",
50
50
  "vitest": "^3.0.0"