principles-disciple 1.35.0 → 1.37.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.
Files changed (32) hide show
  1. package/openclaw.plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/src/commands/nocturnal-train.ts +1 -0
  4. package/src/core/correction-cue-learner.ts +23 -8
  5. package/src/core/event-log.ts +3 -0
  6. package/src/core/evolution-engine.ts +1 -0
  7. package/src/core/init.ts +2 -2
  8. package/src/core/nocturnal-trinity-types.ts +124 -0
  9. package/src/core/session-tracker.ts +1 -0
  10. package/src/core/training-program.ts +1 -0
  11. package/src/hooks/gate-block-helper.ts +1 -1
  12. package/src/hooks/prompt.ts +3 -3
  13. package/src/index.ts +2 -1
  14. package/src/service/central-sync-service.ts +2 -0
  15. package/src/service/evolution-dedup.ts +74 -0
  16. package/src/service/evolution-pain-context.ts +79 -0
  17. package/src/service/evolution-queue-lock.ts +47 -0
  18. package/src/service/evolution-queue-migration.ts +173 -0
  19. package/src/service/evolution-worker.ts +43 -34
  20. package/src/service/keyword-optimization-service.ts +2 -2
  21. package/src/service/subagent-workflow/correction-observer-types.ts +69 -0
  22. package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +246 -0
  23. package/src/service/subagent-workflow/index.ts +13 -0
  24. package/src/service/subagent-workflow/workflow-manager-base.ts +1 -0
  25. package/tests/core/correction-cue-learner.test.ts +345 -0
  26. package/tests/core/pain-score.property.test.ts +205 -0
  27. package/tests/integration/chaos-resilience.test.ts +348 -0
  28. package/tests/integration/gate-real-io.e2e.test.ts +251 -0
  29. package/tests/integration/pain-diagnostician-loop.e2e.test.ts +380 -0
  30. package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +8 -2
  31. package/tests/integration/trajectory-lifecycle.e2e.test.ts +523 -0
  32. package/vitest.config.ts +23 -4
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Evolution Queue Migration — V1 to V2 Schema
3
+ *
4
+ * Pure transformation functions and shared queue types.
5
+ */
6
+
7
+ import type { TaskKind, TaskPriority } from '../core/trajectory-types.js';
8
+
9
+ // Re-export TaskKind and TaskPriority for convenience
10
+ export type { TaskKind, TaskPriority } from '../core/trajectory-types.js';
11
+
12
+ /**
13
+ * Queue item status values.
14
+ */
15
+ export type QueueStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'canceled';
16
+
17
+ /**
18
+ * Task resolution strings.
19
+ */
20
+ export type TaskResolution =
21
+ | 'marker_detected'
22
+ | 'auto_completed_timeout'
23
+ | 'failed_max_retries'
24
+ | 'runtime_unavailable'
25
+ | 'canceled'
26
+ | 'late_marker_principle_created'
27
+ | 'late_marker_no_principle'
28
+ | 'stub_fallback'
29
+ | 'skipped_thin_violation';
30
+
31
+ /**
32
+ * Recent pain context for sleep_reflection tasks.
33
+ * Attached to queue items to provide pain signal context.
34
+ */
35
+ export interface RecentPainContext {
36
+ mostRecent: { score: number; source: string; reason: string; timestamp: string; sessionId: string } | null;
37
+ recentPainCount: number;
38
+ recentMaxPainScore: number;
39
+ }
40
+
41
+ /**
42
+ * Default values for new V2 fields when migrating legacy items.
43
+ */
44
+ export const DEFAULT_TASK_KIND: TaskKind = 'pain_diagnosis';
45
+ export const DEFAULT_PRIORITY: TaskPriority = 'medium';
46
+ export const DEFAULT_MAX_RETRIES = 3;
47
+
48
+ /**
49
+ * Legacy (pre-V2) queue item schema.
50
+ */
51
+ export interface LegacyEvolutionQueueItem {
52
+ id: string;
53
+ source: string;
54
+ traceId?: string;
55
+ task?: string;
56
+ score: number;
57
+ reason: string;
58
+ timestamp: string;
59
+ enqueued_at?: string;
60
+ started_at?: string;
61
+ completed_at?: string;
62
+ assigned_session_key?: string;
63
+ trigger_text_preview?: string;
64
+ status?: string;
65
+ resolution?: string;
66
+ session_id?: string;
67
+ agent_id?: string;
68
+ taskKind?: string;
69
+ priority?: string;
70
+ retryCount?: number;
71
+ maxRetries?: number;
72
+ lastError?: string;
73
+ resultRef?: string;
74
+ }
75
+
76
+ /**
77
+ * V2 queue item schema.
78
+ */
79
+ export interface EvolutionQueueItem {
80
+ id: string;
81
+ taskKind: TaskKind;
82
+ priority: TaskPriority;
83
+ source: string;
84
+ traceId?: string;
85
+ task?: string;
86
+ score: number;
87
+ reason: string;
88
+ timestamp: string;
89
+ enqueued_at?: string;
90
+ started_at?: string;
91
+ completed_at?: string;
92
+ assigned_session_key?: string;
93
+ trigger_text_preview?: string;
94
+ status: QueueStatus;
95
+ resolution?: TaskResolution;
96
+ session_id?: string;
97
+ agent_id?: string;
98
+ retryCount: number;
99
+ maxRetries: number;
100
+ lastError?: string;
101
+ resultRef?: string;
102
+ recentPainContext?: RecentPainContext;
103
+ }
104
+
105
+ export type RawQueueItem = Record<string, unknown>;
106
+
107
+ /**
108
+ * Migrate a legacy queue item to V2 schema.
109
+ * Old items without taskKind are assumed to be pain_diagnosis for backward compatibility.
110
+ */
111
+ export function migrateToV2(item: LegacyEvolutionQueueItem): {
112
+ id: string;
113
+ taskKind: TaskKind;
114
+ priority: TaskPriority;
115
+ source: string;
116
+ traceId?: string;
117
+ task?: string;
118
+ score: number;
119
+ reason: string;
120
+ timestamp: string;
121
+ enqueued_at?: string;
122
+ started_at?: string;
123
+ completed_at?: string;
124
+ assigned_session_key?: string;
125
+ trigger_text_preview?: string;
126
+ status: QueueStatus;
127
+ resolution?: TaskResolution;
128
+ session_id?: string;
129
+ agent_id?: string;
130
+ retryCount: number;
131
+ maxRetries: number;
132
+ lastError?: string;
133
+ resultRef?: string;
134
+ } {
135
+ return {
136
+ id: item.id,
137
+ taskKind: (item.taskKind as TaskKind) || DEFAULT_TASK_KIND,
138
+ priority: (item.priority as TaskPriority) || DEFAULT_PRIORITY,
139
+ source: item.source,
140
+ traceId: item.traceId,
141
+ task: item.task,
142
+ score: item.score,
143
+ reason: item.reason,
144
+ timestamp: item.timestamp,
145
+ enqueued_at: item.enqueued_at,
146
+ started_at: item.started_at,
147
+ completed_at: item.completed_at,
148
+ assigned_session_key: item.assigned_session_key,
149
+ trigger_text_preview: item.trigger_text_preview,
150
+ status: (item.status as QueueStatus) || 'pending',
151
+ resolution: item.resolution as TaskResolution | undefined,
152
+ session_id: item.session_id,
153
+ agent_id: item.agent_id,
154
+ retryCount: item.retryCount || 0,
155
+ maxRetries: item.maxRetries || DEFAULT_MAX_RETRIES,
156
+ lastError: item.lastError,
157
+ resultRef: item.resultRef,
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Check if an item is a legacy (pre-V2) queue item.
163
+ */
164
+ export function isLegacyQueueItem(item: RawQueueItem): boolean {
165
+ return item && typeof item === 'object' && !('taskKind' in item);
166
+ }
167
+
168
+ /**
169
+ * Migrate entire queue to V2 schema if needed.
170
+ */
171
+ export function migrateQueueToV2(queue: RawQueueItem[]): ReturnType<typeof migrateToV2>[] {
172
+ return queue.map(item => isLegacyQueueItem(item) ? migrateToV2(item as unknown as LegacyEvolutionQueueItem) : item as unknown as ReturnType<typeof migrateToV2>);
173
+ }
@@ -16,11 +16,10 @@ import { getEvolutionLogger } from '../core/evolution-logger.js';
16
16
  import type { TaskKind, TaskPriority } from '../core/trajectory-types.js';
17
17
  export type { TaskKind, TaskPriority } from '../core/trajectory-types.js';
18
18
  import { LockUnavailableError } from '../config/index.js';
19
- import { PAIN_QUEUE_DEDUP_WINDOW_MS } from '../config/defaults/runtime.js';
20
19
  import { checkWorkspaceIdle, checkCooldown } from './nocturnal-runtime.js';
21
20
  import { loadNocturnalConfig } from './nocturnal-config.js';
22
21
  import { WorkflowStore } from './subagent-workflow/workflow-store.js';
23
- import type { WorkflowRow, RecentPainContext } from './subagent-workflow/types.js';
22
+ import type { WorkflowRow } from './subagent-workflow/types.js';
24
23
  import { EmpathyObserverWorkflowManager } from './subagent-workflow/empathy-observer-workflow-manager.js';
25
24
  import { DeepReflectWorkflowManager } from './subagent-workflow/deep-reflect-workflow-manager.js';
26
25
  import { NocturnalWorkflowManager, nocturnalWorkflowSpec } from './subagent-workflow/nocturnal-workflow-manager.js';
@@ -32,22 +31,14 @@ import {
32
31
  import { validateNocturnalSnapshotIngress } from '../core/nocturnal-snapshot-contract.js';
33
32
  import { isExpectedSubagentError } from './subagent-workflow/subagent-error-utils.js';
34
33
  import { readPainFlagContract } from '../core/pain.js';
35
- import { CorrectionObserverWorkflowManager, correctionObserverWorkflowSpec } from './correction-observer-workflow-manager.js';
36
- import type { CorrectionObserverPayload } from './correction-observer-types.js';
34
+ import { CorrectionObserverWorkflowManager, correctionObserverWorkflowSpec } from './subagent-workflow/correction-observer-workflow-manager.js';
35
+ import type { CorrectionObserverPayload } from './subagent-workflow/correction-observer-types.js';
37
36
  import { KeywordOptimizationService } from './keyword-optimization-service.js';
38
37
  import { TrajectoryRegistry } from '../core/trajectory.js';
39
38
  import { CorrectionCueLearner } from '../core/correction-cue-learner.js';
40
- import { WORKFLOW_TTL_MS } from '../config/defaults/runtime.js';
41
- import { OpenClawTrinityRuntimeAdapter } from '../core/nocturnal-trinity.js';
42
39
 
43
- /**
44
- * Atomic file write — write to temp then rename to prevent partial writes on crash.
45
- */
46
- function atomicWriteFileSync(filePath: string, data: string): void {
47
- const tmpPath = filePath + '.tmp';
48
- fs.writeFileSync(tmpPath, data, 'utf8');
49
- fs.renameSync(tmpPath, filePath);
50
- }
40
+ const WORKFLOW_TTL_MS = 5 * 60 * 1000; // 5 minutes default TTL for helper workflows
41
+ import { OpenClawTrinityRuntimeAdapter } from '../core/nocturnal-trinity.js';
51
42
 
52
43
  // ── Workflow Watchdog ────────────────────────────────────────────────────────
53
44
  // Detects stale/orphaned workflows, invalid results, and cleanup failures.
@@ -209,6 +200,27 @@ let timeoutId: NodeJS.Timeout | null = null;
209
200
  export type QueueStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'canceled';
210
201
  export type TaskResolution = 'marker_detected' | 'auto_completed_timeout' | 'failed_max_retries' | 'runtime_unavailable' | 'canceled' | 'late_marker_principle_created' | 'late_marker_no_principle' | 'stub_fallback' | 'skipped_thin_violation';
211
202
 
203
+ /**
204
+ * Recent pain context attached to sleep_reflection tasks.
205
+ * Carries explicit recent pain signal metadata without being a separate task kind.
206
+ * Used by NocturnalTargetSelector for ranking bias and context enrichment.
207
+ */
208
+ export interface RecentPainContext {
209
+ /** Most recent unresolved pain event */
210
+ mostRecent: {
211
+ score: number;
212
+ source: string;
213
+ reason: string;
214
+ timestamp: string;
215
+ /** Session ID where the pain occurred */
216
+ sessionId: string;
217
+ } | null;
218
+ /** Count of pain events in the recent window (for signal strength) */
219
+ recentPainCount: number;
220
+ /** Highest pain score in the recent window */
221
+ recentMaxPainScore: number;
222
+ }
223
+
212
224
  export interface EvolutionQueueItem {
213
225
  // Core identity
214
226
  id: string;
@@ -414,6 +426,7 @@ function buildFallbackNocturnalSnapshot(
414
426
  };
415
427
  }
416
428
 
429
+ const PAIN_QUEUE_DEDUP_WINDOW_MS = 30 * 60 * 1000;
417
430
 
418
431
  // P0 fix: File lock constants and helper for queue operations (prevents TOCTOU race)
419
432
  export const EVOLUTION_QUEUE_LOCK_SUFFIX = '.lock';
@@ -711,7 +724,7 @@ function enqueueNewSleepReflectionTask(
711
724
  recentPainContext,
712
725
  });
713
726
 
714
- atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
727
+ fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
715
728
  logger?.info?.(`[PD:EvolutionWorker] Enqueued sleep_reflection task ${taskId}`);
716
729
  }
717
730
 
@@ -856,7 +869,7 @@ async function doEnqueuePainTask(
856
869
  retryCount: 0, maxRetries: 3,
857
870
  });
858
871
 
859
- atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
872
+ fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
860
873
  fs.appendFileSync(painFlagPath, `\nstatus: queued\ntask_id: ${taskId}\n`, 'utf8');
861
874
  result.enqueued = true;
862
875
 
@@ -1645,7 +1658,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1645
1658
 
1646
1659
  // Write claimed state (includes any pain changes from above) and release lock
1647
1660
  if (queueChanged) {
1648
- atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
1661
+ fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
1649
1662
  }
1650
1663
  releaseLock();
1651
1664
  for (const sleepTask of sleepReflectionTasks) {
@@ -1899,7 +1912,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1899
1912
  freshQueue[idx] = sleepTask;
1900
1913
  }
1901
1914
  }
1902
- atomicWriteFileSync(queuePath, JSON.stringify(freshQueue, null, 2));
1915
+ fs.writeFileSync(queuePath, JSON.stringify(freshQueue, null, 2), 'utf8');
1903
1916
 
1904
1917
  // Log completions to EvolutionLogger
1905
1918
  for (const sleepTask of sleepReflectionTasks) {
@@ -1992,14 +2005,10 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1992
2005
  };
1993
2006
 
1994
2007
  // Dispatch LLM subagent via CorrectionObserverWorkflowManager
1995
- const subagent = api?.runtime?.subagent;
1996
- if (!subagent) {
1997
- throw new Error('[PD:EvolutionWorker] subagent runtime not available for keyword_optimization');
1998
- }
1999
2008
  const manager = new CorrectionObserverWorkflowManager({
2000
2009
  workspaceDir: wctx.workspaceDir,
2001
2010
  logger,
2002
- subagent,
2011
+ subagent: api?.runtime?.subagent!,
2003
2012
  agentSession: api?.runtime?.agent?.session,
2004
2013
  });
2005
2014
 
@@ -2013,11 +2022,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
2013
2022
  workflowId = handle.workflowId;
2014
2023
  koTask.resultRef = workflowId;
2015
2024
  } else {
2016
- // isPolling implies resultRef exists (checked above)
2017
- workflowId = koTask.resultRef;
2018
- if (!workflowId) {
2019
- throw new Error(`[PD:EvolutionWorker] keyword_optimization task ${koTask.id} has no resultRef in polling mode`);
2020
- }
2025
+ workflowId = koTask.resultRef!;
2021
2026
  }
2022
2027
 
2023
2028
  // Poll workflow state
@@ -2029,7 +2034,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
2029
2034
 
2030
2035
  if (parsedResult?.updated) {
2031
2036
  koService.applyResult(parsedResult);
2032
- await learner.recordOptimizationPerformed();
2037
+ learner.recordOptimizationPerformed();
2033
2038
  logger?.info?.(`[PD:EvolutionWorker] keyword_optimization applied mutations: ${parsedResult.summary}`);
2034
2039
  } else {
2035
2040
  logger?.info?.(`[PD:EvolutionWorker] keyword_optimization completed with no updates`);
@@ -2082,7 +2087,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
2082
2087
  freshQueue.push(koTask);
2083
2088
  }
2084
2089
  }
2085
- fs.writeFileSync(queuePath, JSON.stringify(freshQueue, null, 2));
2090
+ fs.writeFileSync(queuePath, JSON.stringify(freshQueue, null, 2), 'utf8');
2086
2091
  } catch (koResultErr) {
2087
2092
  logger?.warn?.(`[PD:EvolutionWorker] Failed to write keyword_optimization results: ${String(koResultErr)}`);
2088
2093
  } finally {
@@ -2092,7 +2097,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
2092
2097
  }
2093
2098
 
2094
2099
  if (queueChanged) {
2095
- atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
2100
+ fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
2096
2101
  }
2097
2102
 
2098
2103
  // Pipeline observability: log stage-level summary at end of cycle
@@ -2210,7 +2215,7 @@ export async function registerEvolutionTaskSession(
2210
2215
  if (!task.started_at) {
2211
2216
  task.started_at = new Date().toISOString();
2212
2217
  }
2213
- atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
2218
+ fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
2214
2219
  return true;
2215
2220
  } finally {
2216
2221
  releaseLock();
@@ -2250,7 +2255,7 @@ interface WorkerStatusReport {
2250
2255
  function writeWorkerStatus(stateDir: string, report: WorkerStatusReport): void {
2251
2256
  try {
2252
2257
  const statusPath = path.join(stateDir, 'worker-status.json');
2253
- atomicWriteFileSync(statusPath, JSON.stringify(report, null, 2));
2258
+ fs.writeFileSync(statusPath, JSON.stringify(report, null, 2), 'utf8');
2254
2259
  } catch (statusErr) {
2255
2260
  // Non-critical: worker-status.json is for monitoring, failure is acceptable
2256
2261
  // (no logger available in this standalone helper)
@@ -2281,7 +2286,7 @@ async function processEvolutionQueueWithResult(
2281
2286
  const purgeResult = purgeStaleFailedTasks(queue, logger);
2282
2287
  if (purgeResult.purged > 0) {
2283
2288
  // Write back the cleaned queue
2284
- atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
2289
+ fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2), 'utf8');
2285
2290
  }
2286
2291
 
2287
2292
  queueResult.total = queue.length;
@@ -2558,6 +2563,7 @@ export const EvolutionWorkerService: ExtendedEvolutionWorkerService = {
2558
2563
  }
2559
2564
 
2560
2565
  timeoutId = setTimeout(runCycle, interval);
2566
+ timeoutId.unref();
2561
2567
  }
2562
2568
 
2563
2569
  timeoutId = setTimeout(() => {
@@ -2573,11 +2579,14 @@ export const EvolutionWorkerService: ExtendedEvolutionWorkerService = {
2573
2579
  }
2574
2580
  // processPromotion removed (D-06)
2575
2581
  timeoutId = setTimeout(runCycle, interval);
2582
+ timeoutId.unref();
2576
2583
  })().catch((err) => {
2577
2584
  if (logger) logger.error(`[PD:EvolutionWorker] Startup worker cycle failed: ${String(err)}`);
2578
2585
  timeoutId = setTimeout(runCycle, interval);
2586
+ timeoutId.unref();
2579
2587
  });
2580
2588
  }, initialDelay);
2589
+ timeoutId.unref();
2581
2590
  },
2582
2591
 
2583
2592
  stop(ctx: OpenClawPluginServiceContext): void {
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { CorrectionCueLearner } from '../core/correction-cue-learner.js';
10
- import type { CorrectionObserverResult } from './correction-observer-types.js';
10
+ import type { CorrectionObserverResult } from './subagent-workflow/correction-observer-types.js';
11
11
  import type { PluginLogger } from '../openclaw-sdk.js';
12
12
  import { TrajectoryRegistry } from '../core/trajectory.js';
13
13
 
@@ -137,4 +137,4 @@ export type TrajectoryHistoryEntry = {
137
137
  };
138
138
 
139
139
  /** Re-export CorrectionObserverPayload for convenience */
140
- export type { CorrectionObserverPayload } from './correction-observer-types.js';
140
+ export type { CorrectionObserverPayload } from './subagent-workflow/correction-observer-types.js';
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Correction Observer Workflow - Type Definitions
3
+ *
4
+ * Types for the correction observer LLM optimization workflow.
5
+ * This workflow dispatches an LLM subagent to analyze keyword performance
6
+ * and recommend ADD/UPDATE/REMOVE actions for the correction keyword store.
7
+ */
8
+
9
+ import type { SubagentWorkflowSpec } from './types.js';
10
+
11
+ /**
12
+ * Input passed to the correction observer subagent.
13
+ */
14
+ export interface CorrectionObserverPayload {
15
+ /** Parent session that triggered the optimization */
16
+ parentSessionId: string;
17
+ /** Workspace directory */
18
+ workspaceDir: string;
19
+ /** Current keyword store summary for context */
20
+ keywordStoreSummary: {
21
+ totalKeywords: number;
22
+ terms: Array<{
23
+ term: string;
24
+ weight: number;
25
+ hitCount: number;
26
+ truePositiveCount: number;
27
+ falsePositiveCount: number;
28
+ }>;
29
+ };
30
+ /** Recent user messages for pattern analysis */
31
+ recentMessages: string[];
32
+
33
+ /**
34
+ * Trajectory history: user turns where correctionDetected=true (D-40-08).
35
+ * Includes term matched, timestamp, sessionId for FPR trend analysis.
36
+ */
37
+ trajectoryHistory: Array<{
38
+ sessionId: string;
39
+ timestamp: string;
40
+ term: string;
41
+ userMessage: string;
42
+ }>;
43
+ }
44
+
45
+ /**
46
+ * Result from the correction observer subagent.
47
+ */
48
+ export interface CorrectionObserverResult {
49
+ /** Whether any changes were made */
50
+ updated: boolean;
51
+ /** The optimization decisions returned by the LLM */
52
+ updates: Record<string, {
53
+ action: 'add' | 'update' | 'remove';
54
+ weight?: number;
55
+ falsePositiveRate?: number;
56
+ reasoning: string;
57
+ }>;
58
+ /** Human-readable summary */
59
+ summary: string;
60
+ }
61
+
62
+ /**
63
+ * Workflow spec for the correction observer optimization workflow.
64
+ */
65
+ export interface CorrectionObserverWorkflowSpec extends SubagentWorkflowSpec<CorrectionObserverResult> {
66
+ workflowType: 'correction_observer';
67
+ payload: CorrectionObserverPayload;
68
+ result?: CorrectionObserverResult;
69
+ }