principles-disciple 1.7.2 → 1.7.4

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 (53) hide show
  1. package/agents/auditor.md +61 -0
  2. package/agents/diagnostician.md +277 -0
  3. package/agents/explorer.md +65 -0
  4. package/agents/implementer.md +68 -0
  5. package/agents/planner.md +65 -0
  6. package/agents/reporter.md +78 -0
  7. package/agents/reviewer.md +66 -0
  8. package/dist/commands/evolution-status.js +4 -2
  9. package/dist/core/config.d.ts +11 -0
  10. package/dist/core/config.js +19 -1
  11. package/dist/core/evolution-logger.d.ts +137 -0
  12. package/dist/core/evolution-logger.js +256 -0
  13. package/dist/core/evolution-reducer.d.ts +23 -0
  14. package/dist/core/evolution-reducer.js +73 -29
  15. package/dist/core/evolution-types.d.ts +6 -0
  16. package/dist/core/focus-history.d.ts +53 -0
  17. package/dist/core/focus-history.js +429 -0
  18. package/dist/core/init.js +24 -0
  19. package/dist/core/risk-calculator.d.ts +15 -0
  20. package/dist/core/risk-calculator.js +48 -0
  21. package/dist/core/trajectory.d.ts +73 -0
  22. package/dist/core/trajectory.js +206 -0
  23. package/dist/hooks/gate.js +127 -17
  24. package/dist/hooks/lifecycle.js +104 -0
  25. package/dist/hooks/pain.js +31 -0
  26. package/dist/hooks/prompt.js +66 -19
  27. package/dist/hooks/subagent.d.ts +1 -0
  28. package/dist/hooks/subagent.js +201 -18
  29. package/dist/http/principles-console-route.js +58 -0
  30. package/dist/service/control-ui-query-service.d.ts +2 -0
  31. package/dist/service/control-ui-query-service.js +4 -0
  32. package/dist/service/empathy-observer-manager.d.ts +8 -0
  33. package/dist/service/empathy-observer-manager.js +40 -0
  34. package/dist/service/evolution-query-service.d.ts +155 -0
  35. package/dist/service/evolution-query-service.js +258 -0
  36. package/dist/service/evolution-worker.d.ts +4 -0
  37. package/dist/service/evolution-worker.js +185 -63
  38. package/dist/service/phase3-input-filter.d.ts +37 -0
  39. package/dist/service/phase3-input-filter.js +106 -0
  40. package/dist/service/runtime-summary-service.d.ts +15 -0
  41. package/dist/service/runtime-summary-service.js +111 -23
  42. package/dist/tools/agent-spawn.js +17 -6
  43. package/dist/tools/deep-reflect.js +9 -2
  44. package/dist/utils/subagent-probe.d.ts +23 -0
  45. package/dist/utils/subagent-probe.js +36 -0
  46. package/openclaw.plugin.json +12 -13
  47. package/package.json +5 -4
  48. package/templates/langs/en/core/AGENTS.md +15 -3
  49. package/templates/langs/en/core/BOOTSTRAP.md +24 -1
  50. package/templates/langs/en/core/TOOLS.md +9 -0
  51. package/templates/langs/zh/core/AGENTS.md +15 -3
  52. package/templates/langs/zh/core/BOOTSTRAP.md +24 -1
  53. package/templates/langs/zh/core/TOOLS.md +9 -0
@@ -60,9 +60,19 @@ export interface TrustSettings {
60
60
  limits: {
61
61
  stage_2_max_lines: number;
62
62
  stage_3_max_lines: number;
63
+ stage_2_max_percentage: number;
64
+ stage_3_max_percentage: number;
65
+ min_lines_fallback: number;
63
66
  };
64
67
  history_limit?: number;
65
68
  }
69
+ export interface DiagnosticianSettings {
70
+ context: {
71
+ time_window_minutes: number;
72
+ max_message_length: number;
73
+ max_summary_length: number;
74
+ };
75
+ }
66
76
  export interface PainSettings {
67
77
  language: 'en' | 'zh';
68
78
  trajectory?: {
@@ -70,6 +80,7 @@ export interface PainSettings {
70
80
  busy_timeout_ms?: number;
71
81
  orphan_blob_grace_days?: number;
72
82
  };
83
+ diagnostician?: DiagnosticianSettings;
73
84
  thresholds: {
74
85
  pain_trigger: number;
75
86
  cognitive_paralysis_input: number;
@@ -10,6 +10,13 @@ import * as path from 'path';
10
10
  // ─────────────────────────────────────────────────────────────
11
11
  export const DEFAULT_SETTINGS = {
12
12
  language: 'zh', // Optimized for the primary user base
13
+ diagnostician: {
14
+ context: {
15
+ time_window_minutes: 5, // pain_timestamp 前后各 5 分钟
16
+ max_message_length: 500, // 每条消息截断到 500 字符
17
+ max_summary_length: 3000, // 对话摘要最大 3000 字符
18
+ }
19
+ },
13
20
  thresholds: {
14
21
  pain_trigger: 40, // Increased tolerance before forcing a stop
15
22
  cognitive_paralysis_input: 4000,
@@ -37,7 +44,7 @@ export const DEFAULT_SETTINGS = {
37
44
  intervals: {
38
45
  worker_poll_ms: 15 * 60 * 1000,
39
46
  initial_delay_ms: 5000,
40
- task_timeout_ms: 30 * 60 * 1000
47
+ task_timeout_ms: 60 * 60 * 1000 // 1 hour, matching evolution-worker.ts default
41
48
  },
42
49
  trust: {
43
50
  stages: {
@@ -73,6 +80,9 @@ export const DEFAULT_SETTINGS = {
73
80
  limits: {
74
81
  stage_2_max_lines: 50, // Was 10. 10 lines is barely enough to fix a function signature.
75
82
  stage_3_max_lines: 300, // Was 100. Allow substantial feature implementation.
83
+ stage_2_max_percentage: 10, // Percentage-based threshold for Stage 2
84
+ stage_3_max_percentage: 15, // Percentage-based threshold for Stage 3
85
+ min_lines_fallback: 20, // Minimum threshold even for small files
76
86
  },
77
87
  history_limit: 50
78
88
  },
@@ -210,6 +220,14 @@ export class PainConfig {
210
220
  // Ensure intervals are positive
211
221
  if (settings.intervals.worker_poll_ms < 1000)
212
222
  settings.intervals.worker_poll_ms = 15 * 60 * 1000;
223
+ // Ensure percentage limits are in valid range [0, 100]
224
+ const l = settings.trust.limits;
225
+ if (l.stage_2_max_percentage < 0 || l.stage_2_max_percentage > 100)
226
+ l.stage_2_max_percentage = 10;
227
+ if (l.stage_3_max_percentage < 0 || l.stage_3_max_percentage > 100)
228
+ l.stage_3_max_percentage = 15;
229
+ if (l.min_lines_fallback < 1)
230
+ l.min_lines_fallback = 20;
213
231
  }
214
232
  /**
215
233
  * Gets a value using dot notation (e.g. 'thresholds.pain_trigger')
@@ -0,0 +1,137 @@
1
+ /**
2
+ * EvolutionLogger - 进化流程日志服务
3
+ *
4
+ * 提供两层日志:
5
+ * - 技术层:结构化 JSON,包含 trace_id、stage、metadata
6
+ * - 用户层:中文摘要,便于小白用户理解
7
+ *
8
+ * 日志写入两个地方:
9
+ * 1. SYSTEM_LOG (system.jsonl) - 技术细节
10
+ * 2. evolution_events 表 (SQLite) - 结构化查询
11
+ */
12
+ import type { TrajectoryDatabase } from './trajectory.js';
13
+ export type EvolutionStage = 'pain_detected' | 'queued' | 'started' | 'analyzing' | 'principle_generated' | 'completed' | 'failed';
14
+ export type EvolutionLogLevel = 'debug' | 'info' | 'warn' | 'error';
15
+ export interface EvolutionLogEntry {
16
+ traceId: string;
17
+ stage: EvolutionStage;
18
+ level: EvolutionLogLevel;
19
+ message: string;
20
+ summary: string;
21
+ timestamp: string;
22
+ metadata?: Record<string, unknown>;
23
+ taskId?: string;
24
+ sessionId?: string;
25
+ }
26
+ export interface EvolutionLogInput {
27
+ traceId: string;
28
+ stage: EvolutionStage;
29
+ level?: EvolutionLogLevel;
30
+ message: string;
31
+ summary: string;
32
+ metadata?: Record<string, unknown>;
33
+ taskId?: string;
34
+ sessionId?: string;
35
+ }
36
+ export declare const STAGE_LABELS: Record<EvolutionStage, string>;
37
+ export declare const STAGE_COLORS: Record<EvolutionStage, string>;
38
+ /**
39
+ * 创建新的 trace_id
40
+ * 格式: ev_{timestamp}_{random}
41
+ * 使用 crypto.randomBytes 确保不可预测性
42
+ */
43
+ export declare function createTraceId(): string;
44
+ /**
45
+ * EvolutionLogger 类
46
+ * 管理进化流程的日志记录
47
+ */
48
+ export declare class EvolutionLogger {
49
+ private readonly workspaceDir;
50
+ private readonly trajectory;
51
+ constructor(workspaceDir: string, trajectory?: TrajectoryDatabase);
52
+ /**
53
+ * 记录进化事件
54
+ * 同时写入 SYSTEM_LOG 和 evolution_events 表
55
+ */
56
+ log(input: EvolutionLogInput): void;
57
+ /**
58
+ * 记录 pain_detected 事件
59
+ */
60
+ logPainDetected(params: {
61
+ traceId: string;
62
+ source: string;
63
+ reason: string;
64
+ score: number;
65
+ toolName?: string;
66
+ filePath?: string;
67
+ sessionId?: string;
68
+ }): void;
69
+ /**
70
+ * 记录 queued 事件
71
+ */
72
+ logQueued(params: {
73
+ traceId: string;
74
+ taskId: string;
75
+ score: number;
76
+ source: string;
77
+ reason: string;
78
+ }): void;
79
+ /**
80
+ * 记录 started 事件
81
+ */
82
+ logStarted(params: {
83
+ traceId: string;
84
+ taskId: string;
85
+ }): void;
86
+ /**
87
+ * 记录 analyzing 事件
88
+ */
89
+ logAnalyzing(params: {
90
+ traceId: string;
91
+ taskId: string;
92
+ analysisType?: string;
93
+ }): void;
94
+ /**
95
+ * 记录 principle_generated 事件
96
+ */
97
+ logPrincipleGenerated(params: {
98
+ traceId: string;
99
+ taskId: string;
100
+ principleId: string;
101
+ principleText: string;
102
+ }): void;
103
+ /**
104
+ * 记录 completed 事件
105
+ */
106
+ logCompleted(params: {
107
+ traceId: string;
108
+ taskId: string;
109
+ resolution: 'marker_detected' | 'auto_completed_timeout' | 'manual';
110
+ durationMs?: number;
111
+ principlesGenerated?: number;
112
+ }): void;
113
+ /**
114
+ * 记录 failed 事件
115
+ */
116
+ logFailed(params: {
117
+ traceId: string;
118
+ taskId: string;
119
+ error: string;
120
+ stack?: string;
121
+ }): void;
122
+ }
123
+ /**
124
+ * 获取 EvolutionLogger 实例(单例)
125
+ *
126
+ * 注意:带 trajectory 和不带 trajectory 的请求会返回不同的实例,
127
+ * 因为 trajectory 影响事件持久化行为。
128
+ */
129
+ export declare function getEvolutionLogger(workspaceDir: string, trajectory?: TrajectoryDatabase): EvolutionLogger;
130
+ /**
131
+ * 清理指定 workspace 的 logger 缓存
132
+ */
133
+ export declare function disposeEvolutionLogger(workspaceDir: string): boolean;
134
+ /**
135
+ * 清理所有 logger 缓存(用于测试或进程退出)
136
+ */
137
+ export declare function disposeAllEvolutionLoggers(): void;
@@ -0,0 +1,256 @@
1
+ /**
2
+ * EvolutionLogger - 进化流程日志服务
3
+ *
4
+ * 提供两层日志:
5
+ * - 技术层:结构化 JSON,包含 trace_id、stage、metadata
6
+ * - 用户层:中文摘要,便于小白用户理解
7
+ *
8
+ * 日志写入两个地方:
9
+ * 1. SYSTEM_LOG (system.jsonl) - 技术细节
10
+ * 2. evolution_events 表 (SQLite) - 结构化查询
11
+ */
12
+ import { randomBytes } from 'crypto';
13
+ import { SystemLogger } from './system-logger.js';
14
+ // 阶段对应的中文标签
15
+ export const STAGE_LABELS = {
16
+ pain_detected: '痛点检测',
17
+ queued: '加入队列',
18
+ started: '开始处理',
19
+ analyzing: '分析中',
20
+ principle_generated: '原则生成',
21
+ completed: '完成',
22
+ failed: '失败',
23
+ };
24
+ // 阶段对应的颜色(用于 UI)
25
+ export const STAGE_COLORS = {
26
+ pain_detected: '#ef4444', // 红色
27
+ queued: '#f59e0b', // 橙色
28
+ started: '#3b82f6', // 蓝色
29
+ analyzing: '#8b5cf6', // 紫色
30
+ principle_generated: '#10b981', // 绿色
31
+ completed: '#22c55e', // 绿色
32
+ failed: '#dc2626', // 深红色
33
+ };
34
+ /**
35
+ * 创建新的 trace_id
36
+ * 格式: ev_{timestamp}_{random}
37
+ * 使用 crypto.randomBytes 确保不可预测性
38
+ */
39
+ export function createTraceId() {
40
+ const timestamp = Date.now().toString(36);
41
+ const random = randomBytes(3).toString('hex');
42
+ return `ev_${timestamp}_${random}`;
43
+ }
44
+ /**
45
+ * EvolutionLogger 类
46
+ * 管理进化流程的日志记录
47
+ */
48
+ export class EvolutionLogger {
49
+ workspaceDir;
50
+ trajectory;
51
+ constructor(workspaceDir, trajectory) {
52
+ this.workspaceDir = workspaceDir;
53
+ this.trajectory = trajectory || null;
54
+ }
55
+ /**
56
+ * 记录进化事件
57
+ * 同时写入 SYSTEM_LOG 和 evolution_events 表
58
+ */
59
+ log(input) {
60
+ const entry = {
61
+ ...input,
62
+ level: input.level || 'info',
63
+ timestamp: new Date().toISOString(),
64
+ };
65
+ // 1. 写入 SYSTEM_LOG (技术细节)
66
+ SystemLogger.log(this.workspaceDir, 'EVOLUTION', JSON.stringify({
67
+ traceId: entry.traceId,
68
+ stage: entry.stage,
69
+ level: entry.level,
70
+ message: entry.message,
71
+ taskId: entry.taskId,
72
+ sessionId: entry.sessionId,
73
+ metadata: entry.metadata,
74
+ }));
75
+ // 2. 写入 evolution_events 表 (如果有 trajectory)
76
+ if (this.trajectory) {
77
+ try {
78
+ this.trajectory.recordEvolutionEvent({
79
+ traceId: entry.traceId,
80
+ taskId: entry.taskId,
81
+ stage: entry.stage,
82
+ level: entry.level,
83
+ message: entry.message,
84
+ summary: entry.summary,
85
+ metadata: entry.metadata,
86
+ createdAt: entry.timestamp,
87
+ });
88
+ }
89
+ catch (err) {
90
+ // 数据库写入失败不影响主流程
91
+ console.error(`[EvolutionLogger] Failed to write to trajectory: ${String(err)}`);
92
+ }
93
+ }
94
+ }
95
+ /**
96
+ * 记录 pain_detected 事件
97
+ */
98
+ logPainDetected(params) {
99
+ this.log({
100
+ traceId: params.traceId,
101
+ stage: 'pain_detected',
102
+ level: 'info',
103
+ message: `Pain detected: ${params.source} - ${params.reason}`,
104
+ summary: `检测到痛点:${params.source} - ${params.reason}`,
105
+ metadata: {
106
+ score: params.score,
107
+ toolName: params.toolName,
108
+ filePath: params.filePath,
109
+ },
110
+ sessionId: params.sessionId,
111
+ });
112
+ }
113
+ /**
114
+ * 记录 queued 事件
115
+ */
116
+ logQueued(params) {
117
+ this.log({
118
+ traceId: params.traceId,
119
+ taskId: params.taskId,
120
+ stage: 'queued',
121
+ level: 'info',
122
+ message: `Task ${params.taskId} enqueued with score ${params.score}`,
123
+ summary: `任务 ${params.taskId} 已加入进化队列,优先级分数 ${params.score}`,
124
+ metadata: {
125
+ score: params.score,
126
+ source: params.source,
127
+ reason: params.reason,
128
+ },
129
+ });
130
+ }
131
+ /**
132
+ * 记录 started 事件
133
+ */
134
+ logStarted(params) {
135
+ this.log({
136
+ traceId: params.traceId,
137
+ taskId: params.taskId,
138
+ stage: 'started',
139
+ level: 'info',
140
+ message: `Task ${params.taskId} started processing`,
141
+ summary: `任务 ${params.taskId} 开始处理,Diagnostician 正在分析...`,
142
+ });
143
+ }
144
+ /**
145
+ * 记录 analyzing 事件
146
+ */
147
+ logAnalyzing(params) {
148
+ this.log({
149
+ traceId: params.traceId,
150
+ taskId: params.taskId,
151
+ stage: 'analyzing',
152
+ level: 'info',
153
+ message: `Task ${params.taskId} analysis started`,
154
+ summary: `任务 ${params.taskId} 正在分析根因...`,
155
+ metadata: {
156
+ analysisType: params.analysisType,
157
+ },
158
+ });
159
+ }
160
+ /**
161
+ * 记录 principle_generated 事件
162
+ */
163
+ logPrincipleGenerated(params) {
164
+ const truncatedText = params.principleText.length > 100
165
+ ? params.principleText.substring(0, 100) + '...'
166
+ : params.principleText;
167
+ this.log({
168
+ traceId: params.traceId,
169
+ taskId: params.taskId,
170
+ stage: 'principle_generated',
171
+ level: 'info',
172
+ message: `Principle ${params.principleId} generated for task ${params.taskId}`,
173
+ summary: `生成新原则:${truncatedText}`,
174
+ metadata: {
175
+ principleId: params.principleId,
176
+ principleText: params.principleText,
177
+ },
178
+ });
179
+ }
180
+ /**
181
+ * 记录 completed 事件
182
+ */
183
+ logCompleted(params) {
184
+ let summary;
185
+ if (params.resolution === 'marker_detected') {
186
+ summary = `任务 ${params.taskId} 完成,已生成 ${params.principlesGenerated || 0} 条原则`;
187
+ }
188
+ else if (params.resolution === 'auto_completed_timeout') {
189
+ summary = `任务 ${params.taskId} 超时自动完成`;
190
+ }
191
+ else {
192
+ summary = `任务 ${params.taskId} 已完成`;
193
+ }
194
+ this.log({
195
+ traceId: params.traceId,
196
+ taskId: params.taskId,
197
+ stage: 'completed',
198
+ level: params.resolution === 'auto_completed_timeout' ? 'warn' : 'info',
199
+ message: `Task ${params.taskId} completed with resolution: ${params.resolution}`,
200
+ summary,
201
+ metadata: {
202
+ resolution: params.resolution,
203
+ durationMs: params.durationMs,
204
+ principlesGenerated: params.principlesGenerated,
205
+ },
206
+ });
207
+ }
208
+ /**
209
+ * 记录 failed 事件
210
+ */
211
+ logFailed(params) {
212
+ this.log({
213
+ traceId: params.traceId,
214
+ taskId: params.taskId,
215
+ stage: 'failed',
216
+ level: 'error',
217
+ message: `Task ${params.taskId} failed: ${params.error}`,
218
+ summary: `任务 ${params.taskId} 失败:${params.error}`,
219
+ metadata: {
220
+ error: params.error,
221
+ stack: params.stack,
222
+ },
223
+ });
224
+ }
225
+ }
226
+ // 单例缓存:键格式为 "workspaceDir" 或 "workspaceDir::with_trajectory"
227
+ const loggerCache = new Map();
228
+ /**
229
+ * 获取 EvolutionLogger 实例(单例)
230
+ *
231
+ * 注意:带 trajectory 和不带 trajectory 的请求会返回不同的实例,
232
+ * 因为 trajectory 影响事件持久化行为。
233
+ */
234
+ export function getEvolutionLogger(workspaceDir, trajectory) {
235
+ // 缓存键区分是否带 trajectory,避免持久化行为不一致
236
+ const cacheKey = trajectory ? `${workspaceDir}::with_trajectory` : workspaceDir;
237
+ const cached = loggerCache.get(cacheKey);
238
+ if (cached) {
239
+ return cached;
240
+ }
241
+ const logger = new EvolutionLogger(workspaceDir, trajectory);
242
+ loggerCache.set(cacheKey, logger);
243
+ return logger;
244
+ }
245
+ /**
246
+ * 清理指定 workspace 的 logger 缓存
247
+ */
248
+ export function disposeEvolutionLogger(workspaceDir) {
249
+ return loggerCache.delete(workspaceDir);
250
+ }
251
+ /**
252
+ * 清理所有 logger 缓存(用于测试或进程退出)
253
+ */
254
+ export function disposeAllEvolutionLoggers() {
255
+ loggerCache.clear();
256
+ }
@@ -11,6 +11,17 @@ export interface EvolutionReducer {
11
11
  deprecate(principleId: string, reason: string): void;
12
12
  rollbackPrinciple(principleId: string, reason: string): void;
13
13
  recordProbationFeedback(principleId: string, success: boolean): void;
14
+ /**
15
+ * Creates a new principle with generalized trigger/action from diagnostician.
16
+ * Called after diagnostician analysis to create principle directly.
17
+ */
18
+ createPrincipleFromDiagnosis(params: {
19
+ painId: string;
20
+ painType: 'tool_failure' | 'subagent_error' | 'user_frustration';
21
+ triggerPattern: string;
22
+ action: string;
23
+ source: string;
24
+ }): string | null;
14
25
  getStats(): {
15
26
  candidateCount: number;
16
27
  probationCount: number;
@@ -43,6 +54,18 @@ export declare class EvolutionReducerImpl implements EvolutionReducer {
43
54
  deprecate(principleId: string, reason: string): void;
44
55
  rollbackPrinciple(principleId: string, reason: string): void;
45
56
  recordProbationFeedback(principleId: string, success: boolean): void;
57
+ /**
58
+ * Creates a new principle with generalized trigger/action from diagnostician.
59
+ * Called after diagnostician analysis to create principle directly (no intermediate overfitted principle).
60
+ * @returns the new principle ID, or null if creation failed
61
+ */
62
+ createPrincipleFromDiagnosis(params: {
63
+ painId: string;
64
+ painType: 'tool_failure' | 'subagent_error' | 'user_frustration';
65
+ triggerPattern: string;
66
+ action: string;
67
+ source: string;
68
+ }): string | null;
46
69
  getStats(): {
47
70
  candidateCount: number;
48
71
  probationCount: number;
@@ -136,6 +136,66 @@ export class EvolutionReducerImpl {
136
136
  this.deprecate(principleId, 'conflict_detected');
137
137
  }
138
138
  }
139
+ /**
140
+ * Creates a new principle with generalized trigger/action from diagnostician.
141
+ * Called after diagnostician analysis to create principle directly (no intermediate overfitted principle).
142
+ * @returns the new principle ID, or null if creation failed
143
+ */
144
+ createPrincipleFromDiagnosis(params) {
145
+ // Check blacklist first
146
+ if (this.isBlacklisted(params.painId, params.triggerPattern)) {
147
+ SystemLogger.log(this.workspaceDir, 'PRINCIPLE_BLACKLISTED', `Principle creation blocked by blacklist for trigger: "${params.triggerPattern.slice(0, 50)}..."`);
148
+ return null;
149
+ }
150
+ // Check if a principle already exists for this painId
151
+ const existingPrinciple = [...this.principles.values()].find(p => p.source.painId === params.painId);
152
+ if (existingPrinciple) {
153
+ // Update existing principle instead of creating new one
154
+ existingPrinciple.trigger = params.triggerPattern;
155
+ existingPrinciple.action = params.action;
156
+ existingPrinciple.text = `When ${params.triggerPattern}, then ${params.action}.`;
157
+ existingPrinciple.version += 1;
158
+ SystemLogger.log(this.workspaceDir, 'PRINCIPLE_UPDATED', `Principle ${existingPrinciple.id} updated from diagnostician: "${params.triggerPattern.slice(0, 50)}..."`);
159
+ return existingPrinciple.id;
160
+ }
161
+ // Create new principle with generalized content
162
+ const principleId = this.nextPrincipleId();
163
+ const now = new Date().toISOString();
164
+ const principle = {
165
+ id: principleId,
166
+ version: 1,
167
+ text: `When ${params.triggerPattern}, then ${params.action}.`,
168
+ source: {
169
+ painId: params.painId,
170
+ painType: params.painType,
171
+ timestamp: now,
172
+ },
173
+ trigger: params.triggerPattern,
174
+ action: params.action,
175
+ contextTags: [params.source],
176
+ validation: { successCount: 0, conflictCount: 0 },
177
+ status: 'candidate',
178
+ feedbackScore: 0,
179
+ usageCount: 0,
180
+ createdAt: now,
181
+ };
182
+ this.principles.set(principleId, principle);
183
+ this.emitSync({
184
+ ts: now,
185
+ type: 'candidate_created',
186
+ data: {
187
+ painId: principle.source.painId,
188
+ principleId,
189
+ trigger: params.triggerPattern,
190
+ action: params.action,
191
+ status: 'candidate',
192
+ },
193
+ });
194
+ // Auto-promote since it's already generalized
195
+ this.promote(principleId, 'diagnostician_generalized');
196
+ SystemLogger.log(this.workspaceDir, 'PRINCIPLE_CREATED', `Principle ${principleId} created from diagnostician: "${params.triggerPattern.slice(0, 50)}..."`);
197
+ return principleId;
198
+ }
139
199
  getStats() {
140
200
  return {
141
201
  candidateCount: this.getCandidatePrinciples().length,
@@ -254,7 +314,6 @@ export class EvolutionReducerImpl {
254
314
  }
255
315
  onPainDetected(data, eventTs) {
256
316
  const trigger = String(data.reason ?? data.source ?? 'unknown trigger');
257
- const action = `Prevent recurrence for: ${String(data.source ?? 'unknown')}`;
258
317
  // Defense in depth: protocol/system tokens must never become principles,
259
318
  // even if a pain_detected event is emitted from a new callsite in the future.
260
319
  if (shouldIgnorePainProtocolText(trigger)) {
@@ -263,38 +322,23 @@ export class EvolutionReducerImpl {
263
322
  if (this.isBlacklisted(data.painId, trigger)) {
264
323
  return;
265
324
  }
266
- const principleId = this.nextPrincipleId();
267
- const principle = {
268
- id: principleId,
269
- version: 1,
270
- text: `When ${trigger}, then ${action}.`,
271
- source: {
272
- painId: data.painId,
273
- painType: data.painType,
274
- timestamp: eventTs,
275
- },
276
- trigger,
277
- action,
278
- contextTags: [data.source],
279
- validation: { successCount: 0, conflictCount: 0 },
280
- status: 'candidate',
281
- feedbackScore: 0,
282
- usageCount: 0,
283
- createdAt: eventTs,
284
- };
285
- this.principles.set(principleId, principle);
325
+ // NOTE: Principle creation is now deferred to diagnostician analysis.
326
+ // The diagnostician will read conversation context, generalize the trigger pattern,
327
+ // and create a principle via createPrincipleFromDiagnosis() after analysis.
328
+ // Record pain for tracking purposes (but don't create principle yet)
286
329
  this.emitSync({
287
330
  ts: new Date().toISOString(),
288
- type: 'candidate_created',
331
+ type: 'pain_recorded',
289
332
  data: {
290
- painId: principle.source.painId,
291
- principleId,
292
- trigger,
293
- action,
294
- status: 'candidate',
333
+ painId: data.painId,
334
+ painType: data.painType,
335
+ source: data.source,
336
+ reason: data.reason,
337
+ sessionId: data.sessionId,
338
+ agentId: data.agentId,
295
339
  },
296
340
  });
297
- this.promote(principleId, 'auto_from_pain');
341
+ // Circuit breaker logic remains for subagent errors
298
342
  if (data.painType === 'subagent_error') {
299
343
  const key = String(data.taskId ?? data.source ?? 'subagent');
300
344
  const next = this.failureStreak.get(key) ?? 0;
@@ -305,7 +349,7 @@ export class EvolutionReducerImpl {
305
349
  type: 'circuit_breaker_opened',
306
350
  data: {
307
351
  taskId: key,
308
- painId: principle.source.painId,
352
+ painId: data.painId,
309
353
  failCount: next,
310
354
  reason: 'Max retries exceeded',
311
355
  requireHuman: true,
@@ -157,7 +157,9 @@ export interface PainDetectedData {
157
157
  reason: string;
158
158
  score?: number;
159
159
  sessionId?: string;
160
+ agentId?: string;
160
161
  taskId?: string;
162
+ traceId?: string;
161
163
  }
162
164
  export interface CandidateCreatedData {
163
165
  painId: string;
@@ -202,6 +204,10 @@ export type EvolutionLoopEvent = {
202
204
  ts: string;
203
205
  type: 'pain_detected';
204
206
  data: PainDetectedData;
207
+ } | {
208
+ ts: string;
209
+ type: 'pain_recorded';
210
+ data: PainDetectedData;
205
211
  } | {
206
212
  ts: string;
207
213
  type: 'candidate_created';