dsh-continual-evolve 0.7.1 → 0.7.2

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/lib/index.js CHANGED
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * Mounts the evolution engine, registers the model-facing evolve_* tools,
5
5
  * the human-facing /evolve command, the system-prompt guidance section, and
6
- * (opt-in) the automatic review gate that runs the planner on a turn
7
- * interval. Store roots default under the resolved DSH home; a deployment
8
- * may override `baseDir` in the plugin config.
6
+ * the automatic review gate. The listener is always registered, while its
7
+ * effective state is controlled by the runtime switch. Successful turns feed
8
+ * incremental snapshots through a per-session serial scheduler.
9
9
  */
10
10
  import { join } from "node:path";
11
11
  import z from "@deepseek-ai/schemastery";
@@ -29,9 +29,9 @@ export const Config = z.object({
29
29
  baseDir: z.string(),
30
30
  /** System-prompt section order for the evolution guidance. */
31
31
  sectionOrder: z.natural().default(118),
32
- /** Enable the automatic review gate (off by default: it costs model calls). */
32
+ /** Initial automatic-review default when no runtime switch exists (off by default: it costs model calls). */
33
33
  autoReview: z.boolean().default(false),
34
- /** Gate runs when this many turns have passed since the last review. */
34
+ /** Legacy/local-fate cadence fallback; successful-turn review is snapshot-driven. */
35
35
  reviewIntervalTurns: z.natural().default(6),
36
36
  /** Trajectory slice handed to the gate, in characters. */
37
37
  maxReviewInputChars: z.natural().default(40000),
@@ -177,25 +177,28 @@ export function apply(ctx, config) {
177
177
  void restoreMounted(ctx, baseDir).catch((cause) => {
178
178
  ctx.logger("continual-evolve").warn(`mount restore failed: ${cause instanceof Error ? cause.message : String(cause)}`);
179
179
  });
180
- if (config.autoReview) {
181
- registerAutoReview(ctx, engine, {
182
- intervalTurns: config.reviewIntervalTurns ?? 6,
183
- maxInputChars: config.maxReviewInputChars ?? 40000,
184
- budgetTokens: config.reviewBudgetTokens ?? 4096,
185
- notifyOnAutoReview: config.notifyOnAutoReview ?? true,
186
- localFate: config.localFate ?? true,
187
- fateIntervalTurns: config.fateIntervalTurns ?? config.reviewIntervalTurns ?? 6,
188
- goalBlockedWrapupTurns: config.goalBlockedWrapupTurns ?? 3,
189
- promotionPolicy,
190
- autoCase: config.autoCase ?? true,
191
- rubricKey,
192
- ...(config.historyRetain?.reviews !== undefined ? { reviewsRetain: config.historyRetain.reviews } : {}),
193
- ...(config.reviewModel ? { reviewModel: config.reviewModel } : {}),
194
- ...(config.plannerPrefixCache ? { prefixCacheMode: config.plannerPrefixCache } : {}),
195
- ...(config.plannerPrefixMaxChars !== undefined ? { prefixMaxChars: config.plannerPrefixMaxChars } : {}),
196
- });
197
- ctx.logger("continual-evolve").info(`continual-evolve auto-review enabled (every ${config.reviewIntervalTurns ?? 6} turns; local-fate ${config.localFate ?? true ? "on" : "off"} every ${config.fateIntervalTurns ?? config.reviewIntervalTurns ?? 6} turns)`);
198
- }
180
+ // The listener is always registered. `autoReview` is only the initial
181
+ // runtime default; `/evolve resume` can enable it without a profile edit
182
+ // or process restart, while manual tools and commands stay independent.
183
+ registerAutoReview(ctx, engine, {
184
+ intervalTurns: config.reviewIntervalTurns ?? 6,
185
+ enabledByDefault: config.autoReview ?? false,
186
+ maxInputChars: config.maxReviewInputChars ?? 40000,
187
+ budgetTokens: config.reviewBudgetTokens ?? 4096,
188
+ notifyOnAutoReview: config.notifyOnAutoReview ?? true,
189
+ localFate: config.localFate ?? true,
190
+ fateIntervalTurns: config.fateIntervalTurns ?? config.reviewIntervalTurns ?? 6,
191
+ goalBlockedWrapupTurns: config.goalBlockedWrapupTurns ?? 3,
192
+ promotionPolicy,
193
+ autoCase: config.autoCase ?? true,
194
+ rubricKey,
195
+ ...(config.historyRetain?.reviews !== undefined ? { reviewsRetain: config.historyRetain.reviews } : {}),
196
+ ...(config.reviewModel ? { reviewModel: config.reviewModel } : {}),
197
+ ...(config.plannerPrefixCache ? { prefixCacheMode: config.plannerPrefixCache } : {}),
198
+ ...(config.plannerPrefixMaxChars !== undefined ? { prefixMaxChars: config.plannerPrefixMaxChars } : {}),
199
+ });
200
+ const runtimeDefault = config.autoReview ?? false;
201
+ ctx.logger("continual-evolve").info(`continual-evolve auto-review registered (default ${runtimeDefault ? "on" : "off"}; successful-turn snapshots; local-fate ${config.localFate ?? true ? "on" : "off"} every ${config.fateIntervalTurns ?? config.reviewIntervalTurns ?? 6} turns)`);
199
202
  ctx.logger("continual-evolve").info(`continual-evolve mounted (baseDir=${baseDir})`);
200
203
  }
201
204
  //# sourceMappingURL=index.js.map
package/lib/planner.d.ts CHANGED
@@ -24,6 +24,11 @@ export interface PlanOptions {
24
24
  * every planning call is grounded in what the user actually said.
25
25
  */
26
26
  trajectory?: string;
27
+ /**
28
+ * Explicit session-trajectory rows for prefix-cache Route A. When supplied,
29
+ * the planner sees only the scheduler snapshot rather than the full session.
30
+ */
31
+ trajectoryEvents?: readonly unknown[];
27
32
  /**
28
33
  * Skills root to read the skill-creator template facts from
29
34
  * (`<root>/skill-creator/references/template.md`). When omitted or the
package/lib/planner.js CHANGED
@@ -90,7 +90,7 @@ export async function planWithLlm(ctx, options) {
90
90
  // as a session-derived message prefix (cache-eligible on providers with
91
91
  // prompt caching) and drops the auto-extracted trajectory block; Route B
92
92
  // keeps the legacy flat-text block. An empty prefix falls back to B.
93
- const events = sessionEventsOf(agent);
93
+ const events = options.trajectoryEvents ?? sessionEventsOf(agent);
94
94
  const routing = resolvePrefixCache(options.prefixCache);
95
95
  const route = detectPlannerRoute(events, routing.mode);
96
96
  let prefixMessages = [];
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Serial latest-pending scheduler for automatic review snapshots.
3
+ *
4
+ * This is the DSH equivalent of ZCode's memory extraction scheduler: one
5
+ * runner per session, at most one running snapshot, and one replaceable
6
+ * pending snapshot while the runner is busy. A cursor advances only after a
7
+ * successful or no-op execution; errors and aborts leave it untouched so the
8
+ * next snapshot retries the same durable boundary.
9
+ */
10
+ /** Terminal state of one scheduled snapshot. */
11
+ export type ReviewSchedulerStatus = "success" | "no-op" | "error" | "aborted";
12
+ /** A scheduler execution receives the exact snapshot and its owner signal. */
13
+ export interface ReviewSchedulerExecution<TSnapshot> {
14
+ readonly snapshot: TSnapshot;
15
+ readonly signal: AbortSignal;
16
+ }
17
+ /** Public scheduler surface used by the auto-review driver and tests. */
18
+ export interface ReviewScheduler<TSnapshot> {
19
+ /** Wait until the current run and all coalesced pending work settle. */
20
+ drain(): Promise<void>;
21
+ /** Last successfully processed or skipped snapshot boundary. */
22
+ getCursor(): string | undefined;
23
+ /** Whether a run or a replaceable pending snapshot exists. */
24
+ hasPendingWork(): boolean;
25
+ /** Submit a snapshot (or an async acquisition) without blocking the caller. */
26
+ schedule(snapshot: TSnapshot | Promise<TSnapshot>): void;
27
+ /** Abort the owner signal, drop pending work, and make later schedules no-ops. */
28
+ shutdown(): void;
29
+ }
30
+ /**
31
+ * Create a scheduler with ZCode-compatible coalescing and cursor semantics.
32
+ *
33
+ * @param execute - one serial extraction/review operation
34
+ * @param cursorOf - extracts the durable boundary from a snapshot
35
+ */
36
+ export declare function createReviewScheduler<TSnapshot>(execute: (input: ReviewSchedulerExecution<TSnapshot>) => Promise<ReviewSchedulerStatus>, cursorOf: (snapshot: TSnapshot) => string | undefined): ReviewScheduler<TSnapshot>;
37
+ //# sourceMappingURL=review-scheduler.d.ts.map
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Serial latest-pending scheduler for automatic review snapshots.
3
+ *
4
+ * This is the DSH equivalent of ZCode's memory extraction scheduler: one
5
+ * runner per session, at most one running snapshot, and one replaceable
6
+ * pending snapshot while the runner is busy. A cursor advances only after a
7
+ * successful or no-op execution; errors and aborts leave it untouched so the
8
+ * next snapshot retries the same durable boundary.
9
+ */
10
+ /**
11
+ * Create a scheduler with ZCode-compatible coalescing and cursor semantics.
12
+ *
13
+ * @param execute - one serial extraction/review operation
14
+ * @param cursorOf - extracts the durable boundary from a snapshot
15
+ */
16
+ export function createReviewScheduler(execute, cursorOf) {
17
+ let cursor;
18
+ let latestPending;
19
+ let running;
20
+ let shuttingDown = false;
21
+ const shutdownController = new AbortController();
22
+ const processSnapshot = async (snapshot) => {
23
+ if (shuttingDown || shutdownController.signal.aborted)
24
+ return;
25
+ let status;
26
+ try {
27
+ status = await execute({ snapshot, signal: shutdownController.signal });
28
+ }
29
+ catch {
30
+ // A failed acquisition/extraction is deliberately contained. The
31
+ // cursor remains unchanged and the next snapshot retries it.
32
+ return;
33
+ }
34
+ if (!shuttingDown && (status === "success" || status === "no-op")) {
35
+ cursor = cursorOf(snapshot);
36
+ }
37
+ };
38
+ const run = async (first) => {
39
+ try {
40
+ let current = first;
41
+ while (current && !shuttingDown) {
42
+ const acquisition = await waitForSnapshotAcquisitionOrShutdown(current, shutdownController.signal);
43
+ if (acquisition.status === "shutdown" || shuttingDown)
44
+ break;
45
+ if (acquisition.status === "acquired") {
46
+ await processSnapshot(acquisition.snapshot);
47
+ }
48
+ current = shuttingDown ? undefined : latestPending;
49
+ latestPending = undefined;
50
+ }
51
+ }
52
+ finally {
53
+ if (shuttingDown)
54
+ latestPending = undefined;
55
+ running = undefined;
56
+ }
57
+ };
58
+ return {
59
+ async drain() {
60
+ while (running) {
61
+ await running;
62
+ }
63
+ },
64
+ getCursor() {
65
+ return cursor;
66
+ },
67
+ hasPendingWork() {
68
+ return running !== undefined || latestPending !== undefined;
69
+ },
70
+ schedule(snapshot) {
71
+ if (shuttingDown)
72
+ return;
73
+ const acquisition = acquireSnapshot(snapshot);
74
+ if (running) {
75
+ // Replace, never queue: a burst of turns contributes one latest
76
+ // boundary and the next extraction consumes all intervening rows.
77
+ latestPending = acquisition;
78
+ return;
79
+ }
80
+ running = run(acquisition);
81
+ },
82
+ shutdown() {
83
+ if (shuttingDown)
84
+ return;
85
+ shuttingDown = true;
86
+ latestPending = undefined;
87
+ shutdownController.abort();
88
+ },
89
+ };
90
+ }
91
+ function acquireSnapshot(snapshot) {
92
+ return Promise.resolve(snapshot).then((value) => ({ status: "acquired", snapshot: value }), () => ({ status: "error" }));
93
+ }
94
+ function waitForSnapshotAcquisitionOrShutdown(acquisition, signal) {
95
+ if (signal.aborted)
96
+ return Promise.resolve({ status: "shutdown" });
97
+ return new Promise((resolve) => {
98
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
99
+ const onAbort = () => {
100
+ cleanup();
101
+ resolve({ status: "shutdown" });
102
+ };
103
+ signal.addEventListener("abort", onAbort, { once: true });
104
+ void acquisition.then((result) => {
105
+ cleanup();
106
+ resolve(result);
107
+ });
108
+ });
109
+ }
110
+ //# sourceMappingURL=review-scheduler.js.map
package/lib/review.d.ts CHANGED
@@ -15,7 +15,7 @@ export interface AutoRefineReview {
15
15
  rationale: string;
16
16
  instructions?: string;
17
17
  }
18
- export type AutoRefineReason = "turn_interval" | "compact" | "goal_blocked";
18
+ export type AutoRefineReason = "turn_snapshot" | "turn_interval" | "compact" | "goal_blocked";
19
19
  export interface AutoRefineReviewContext {
20
20
  reason: AutoRefineReason;
21
21
  turnsSinceLastReview: number;
@@ -27,6 +27,8 @@ export interface ReviewOptions {
27
27
  context: AutoRefineReviewContext;
28
28
  /** Serialized trajectory text; when absent the gate is skipped by the caller. */
29
29
  trajectory?: string;
30
+ /** Incremental surface rows captured for this turn snapshot. */
31
+ trajectoryEvents?: readonly unknown[];
30
32
  signal?: AbortSignal;
31
33
  budgetTokens?: number;
32
34
  /** Gap C1: optional provider/model override for the review gate (cheaper model). */
package/lib/review.js CHANGED
@@ -101,7 +101,7 @@ export async function reviewAutoRefine(ctx, options) {
101
101
  }
102
102
  // Route A carries the judged conversation as a session-derived message
103
103
  // prefix and drops the flat block; an empty prefix falls back to B.
104
- const events = sessionEventsOf(agent);
104
+ const events = options.trajectoryEvents ?? sessionEventsOf(agent);
105
105
  const routing = resolvePrefixCache(options.prefixCache);
106
106
  const route = detectPlannerRoute(events, routing.mode);
107
107
  let prefixMessages = [];
package/lib/runtime.d.ts CHANGED
@@ -1,6 +1,9 @@
1
+ /** On-disk runtime gate state. Version 2 adds an explicit enable bit. */
1
2
  export interface GateRuntime {
2
- version: 1;
3
- /** True while the human paused the automatic review gate. */
3
+ version: 2;
4
+ /** Whether the human has enabled automatic review. */
5
+ enabled: boolean;
6
+ /** Whether the human has explicitly paused automatic review. */
4
7
  paused: boolean;
5
8
  /** ISO timestamp of the last pause/resume write. */
6
9
  updatedAt: string;
@@ -8,17 +11,14 @@ export interface GateRuntime {
8
11
  /** Full path of the runtime switch file. */
9
12
  export declare function runtimePath(baseDir: string): string;
10
13
  /**
11
- * Load the runtime switch; fail open to "running" on any unreadable or
12
- * malformed content (called on every gate trigger, so it must never throw
13
- * for a bad file).
14
+ * Load the runtime switch. Missing, malformed, or legacy content uses the
15
+ * supplied configured default and never throws; this is a trigger-path read.
14
16
  */
15
- export declare function loadGateRuntime(baseDir: string): GateRuntime;
16
- /**
17
- * Persist the pause switch atomically (temp file + rename). Never throws
18
- * for a bad file — callers (pause/resume commands) surface real IO errors
19
- * through the command's own error path instead.
20
- */
21
- export declare function saveGateRuntime(baseDir: string, paused: boolean): string;
22
- /** True while the human paused the automatic review gate (fail-open). */
17
+ export declare function loadGateRuntime(baseDir: string, defaultEnabled?: boolean): GateRuntime;
18
+ /** Persist the runtime switch atomically (temp file + rename). */
19
+ export declare function saveGateRuntime(baseDir: string, paused: boolean, enabled?: boolean): string;
20
+ /** True while the human paused the automatic review gate. */
23
21
  export declare function isGatePaused(baseDir: string): boolean;
22
+ /** Effective automatic-review state, including the explicit enable bit. */
23
+ export declare function isGateEnabled(baseDir: string, defaultEnabled?: boolean): boolean;
24
24
  //# sourceMappingURL=runtime.d.ts.map
package/lib/runtime.js CHANGED
@@ -1,18 +1,15 @@
1
1
  /**
2
- * Runtime gate switch (#21 P2): a tiny human-owned file that pauses the
3
- * automatic review gate without touching the plugin config or restarting.
2
+ * Runtime gate switch: a small human-owned file that enables or pauses the
3
+ * automatic review listener without editing the profile or restarting DSH.
4
4
  *
5
- * - file: `<baseDir>/evolve/runtime.json` (`{version, paused, updatedAt}`)
6
- * - `/evolve pause` sets paused, `/evolve resume` clears it, `/evolve
7
- * status` shows it alongside the static patch config.
8
- * - the gate consults it on every trigger (turn-interval AND compaction)
9
- * and stays dormant while paused — manual tools/commands keep working.
10
- * - fail-open: a missing or corrupt file means "running". Pausing is the
11
- * exceptional state; a broken file must never silently wedge the gate
12
- * shut (the next save rewrites it cleanly).
5
+ * - file: `<baseDir>/evolve/runtime.json` (`{version: 2, enabled, paused, updatedAt}`)
6
+ * - the file is consulted at every trigger and before scheduler execution;
7
+ * - `/evolve pause` sets `paused`, `/evolve resume` sets `enabled=true` and
8
+ * clears the pause, while manual tools and commands remain independent;
9
+ * - a missing or malformed file uses the caller's configured default. A
10
+ * legacy v1 `{paused}` file is readable and preserves its pause state.
13
11
  */
14
- import { existsSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
15
- import { readFileSync } from "node:fs";
12
+ import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
16
13
  import { randomUUID } from "node:crypto";
17
14
  import { join } from "node:path";
18
15
  import { EVOLVE_DIR } from "./store.js";
@@ -21,57 +18,54 @@ const RUNTIME_FILE = "runtime.json";
21
18
  export function runtimePath(baseDir) {
22
19
  return join(baseDir, EVOLVE_DIR, RUNTIME_FILE);
23
20
  }
24
- function running() {
25
- return { version: 1, paused: false, updatedAt: new Date(0).toISOString() };
21
+ function state(enabled, paused) {
22
+ return { version: 2, enabled, paused, updatedAt: new Date(0).toISOString() };
26
23
  }
27
24
  /**
28
- * Load the runtime switch; fail open to "running" on any unreadable or
29
- * malformed content (called on every gate trigger, so it must never throw
30
- * for a bad file).
25
+ * Load the runtime switch. Missing, malformed, or legacy content uses the
26
+ * supplied configured default and never throws; this is a trigger-path read.
31
27
  */
32
- export function loadGateRuntime(baseDir) {
28
+ export function loadGateRuntime(baseDir, defaultEnabled = false) {
33
29
  const path = runtimePath(baseDir);
34
- if (!existsSync(path)) {
35
- return running();
36
- }
30
+ if (!existsSync(path))
31
+ return state(defaultEnabled, false);
37
32
  try {
38
33
  const raw = JSON.parse(readFileSync(path, "utf8"));
39
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
40
- return running();
41
- }
34
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
35
+ return state(defaultEnabled, false);
42
36
  const record = raw;
37
+ const paused = record["paused"] === true;
38
+ // v1 had no enable bit. Its only meaningful state was pause, so retain
39
+ // the configured default when it is not paused.
40
+ const enabled = typeof record["enabled"] === "boolean" ? record["enabled"] : defaultEnabled;
43
41
  return {
44
- version: 1,
45
- paused: record["paused"] === true,
42
+ version: 2,
43
+ enabled,
44
+ paused,
46
45
  updatedAt: typeof record["updatedAt"] === "string" ? record["updatedAt"] : new Date(0).toISOString(),
47
46
  };
48
47
  }
49
48
  catch {
50
- return running();
49
+ return state(defaultEnabled, false);
51
50
  }
52
51
  }
53
- /**
54
- * Persist the pause switch atomically (temp file + rename). Never throws
55
- * for a bad file — callers (pause/resume commands) surface real IO errors
56
- * through the command's own error path instead.
57
- */
58
- export function saveGateRuntime(baseDir, paused) {
52
+ /** Persist the runtime switch atomically (temp file + rename). */
53
+ export function saveGateRuntime(baseDir, paused, enabled = true) {
59
54
  const path = runtimePath(baseDir);
60
55
  mkdirSync(join(baseDir, EVOLVE_DIR), { recursive: true });
61
56
  const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
62
- const payload = { version: 1, paused, updatedAt: new Date().toISOString() };
57
+ const payload = { version: 2, enabled, paused, updatedAt: new Date().toISOString() };
63
58
  writeFileSync(tempPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
64
59
  try {
65
60
  renameSync(tempPath, path);
66
61
  }
67
62
  finally {
68
- if (existsSync(tempPath)) {
63
+ if (existsSync(tempPath))
69
64
  unlinkSync(tempPath);
70
- }
71
65
  }
72
66
  return path;
73
67
  }
74
- /** True while the human paused the automatic review gate (fail-open). */
68
+ /** True while the human paused the automatic review gate. */
75
69
  export function isGatePaused(baseDir) {
76
70
  try {
77
71
  return loadGateRuntime(baseDir).paused;
@@ -80,4 +74,14 @@ export function isGatePaused(baseDir) {
80
74
  return false;
81
75
  }
82
76
  }
77
+ /** Effective automatic-review state, including the explicit enable bit. */
78
+ export function isGateEnabled(baseDir, defaultEnabled = false) {
79
+ try {
80
+ const current = loadGateRuntime(baseDir, defaultEnabled);
81
+ return current.enabled && !current.paused;
82
+ }
83
+ catch {
84
+ return defaultEnabled;
85
+ }
86
+ }
83
87
  //# sourceMappingURL=runtime.js.map
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Turn-boundary snapshots for the automatic review gate.
3
+ *
4
+ * The scheduler must see a bounded, mechanically filtered slice of the
5
+ * current session, not a fresh full-history LLM request on every turn. This
6
+ * module owns the snapshot boundary, cursor-compatible incremental rows, and
7
+ * the cheap eligibility checks that run before any model call.
8
+ */
9
+ import type { Context } from "@deepseek-ai/cordis";
10
+ import type { Agent } from "@deepseek-ai/dsh-agent";
11
+ /** Why a scheduler snapshot was captured. */
12
+ export type TurnSnapshotReason = "turn_snapshot" | "compact";
13
+ /** Mechanical reasons a snapshot can be skipped without an LLM call. */
14
+ export type SnapshotSkipReason = "no-new-events" | "internal-agent" | "direct-memory-write" | "no-user-prose";
15
+ /** One immutable scheduler input. The cursor is a durable event boundary. */
16
+ export interface TurnSnapshot {
17
+ readonly sessionId: string;
18
+ readonly projectKey?: string;
19
+ readonly turn: number;
20
+ readonly reason: TurnSnapshotReason;
21
+ readonly cursor: string;
22
+ readonly events: readonly unknown[];
23
+ readonly trajectory: string;
24
+ readonly userText: string;
25
+ readonly sourceSeqs: readonly number[];
26
+ readonly eligible: boolean;
27
+ readonly skipReason?: SnapshotSkipReason;
28
+ }
29
+ /** Eligibility result kept separate so tests and future extractors share it. */
30
+ export interface SnapshotEligibility {
31
+ readonly eligible: boolean;
32
+ readonly reason?: SnapshotSkipReason;
33
+ readonly userText: string;
34
+ }
35
+ /**
36
+ * Capture the current durable surface and select only rows after `cursor`.
37
+ * A seq-bearing session uses `seq:<n>`; older/test surfaces fall back to an
38
+ * event-count cursor so the scheduler still has a deterministic boundary.
39
+ */
40
+ export declare function captureTurnSnapshot(ctx: Context, agent: Agent, opts: {
41
+ turn: number;
42
+ reason: TurnSnapshotReason;
43
+ cursor?: string;
44
+ maxChars: number;
45
+ }): Promise<TurnSnapshot>;
46
+ /**
47
+ * Decide whether the incremental rows deserve a model call. This intentionally
48
+ * does not decide whether a fact is valuable; that remains the review model's
49
+ * job. It only removes mechanical noise before the scheduler spends tokens.
50
+ */
51
+ export declare function evaluateSnapshotEligibility(events: readonly unknown[], internalAgent?: boolean): SnapshotEligibility;
52
+ /** True when an assistant row records an explicit evolve memory mutation. */
53
+ export declare function containsDirectMemoryWrite(events: readonly unknown[]): boolean;
54
+ /** Serialize only user/assistant text, retaining the bounded tail. */
55
+ export declare function serializeSnapshotEvents(events: readonly unknown[], maxChars: number): string;
56
+ /** A DSH subagent header is an internal source when its origin says so. */
57
+ export declare function isInternalAgent(agent: Agent | undefined): boolean;
58
+ //# sourceMappingURL=turn-snapshot.d.ts.map