chainlesschain 0.45.74 → 0.45.76
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/README.md +52 -15
- package/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AppLayout-BhJ3YFWt.js → AppLayout-2RCrdXxl.js} +1 -1
- package/src/assets/web-panel/assets/AppLayout-D9pBLPC3.css +1 -0
- package/src/assets/web-panel/assets/{Chat-DaxTP3x8.js → Chat-B2nB8o_F.js} +1 -1
- package/src/assets/web-panel/assets/{Dashboard-CjlX4CrX.js → Dashboard-DanoHPSI.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BCvgBkD3.js → Skills-CLlblJcG.js} +1 -1
- package/src/assets/web-panel/assets/chat-DWBA4-cl.js +1 -0
- package/src/assets/web-panel/assets/{index-DrmEk9S3.js → index-CyGtHm63.js} +2 -2
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/learning.js +273 -0
- package/src/commands/lowcode.js +23 -8
- package/src/gateways/discord/discord-formatter.js +89 -0
- package/src/gateways/gateway-base.js +189 -0
- package/src/gateways/telegram/telegram-formatter.js +93 -0
- package/src/index.js +2 -0
- package/src/lib/app-builder.js +136 -8
- package/src/lib/autonomous-agent.js +8 -1
- package/src/lib/cli-context-engineering.js +15 -0
- package/src/lib/execution-backend.js +239 -0
- package/src/lib/hook-manager.js +2 -0
- package/src/lib/iteration-budget.js +175 -0
- package/src/lib/learning/learning-hooks.js +117 -0
- package/src/lib/learning/learning-tables.js +66 -0
- package/src/lib/learning/outcome-feedback.js +243 -0
- package/src/lib/learning/reflection-engine.js +323 -0
- package/src/lib/learning/skill-improver.js +536 -0
- package/src/lib/learning/skill-synthesizer.js +315 -0
- package/src/lib/learning/trajectory-store.js +409 -0
- package/src/lib/plugin-autodiscovery.js +224 -0
- package/src/lib/session-search.js +193 -0
- package/src/lib/sub-agent-context.js +7 -2
- package/src/lib/user-profile.js +172 -0
- package/src/lib/web-ui-server.js +1 -1
- package/src/repl/agent-repl.js +109 -0
- package/src/runtime/agent-core.js +75 -4
- package/src/runtime/coding-agent-contract-shared.cjs +35 -0
- package/src/runtime/coding-agent-policy.cjs +10 -0
- package/src/assets/web-panel/assets/AppLayout-Cr2lWhF-.css +0 -1
- package/src/assets/web-panel/assets/chat-BmwHBi9M.js +0 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learning Loop — Hook registration.
|
|
3
|
+
*
|
|
4
|
+
* Wires the TrajectoryStore into the existing hook infrastructure:
|
|
5
|
+
* - UserPromptSubmit → startTrajectory()
|
|
6
|
+
* - PostToolUse → appendToolCall()
|
|
7
|
+
* - SessionEnd → (reserved for ReflectionEngine in P3)
|
|
8
|
+
*
|
|
9
|
+
* This module does NOT modify agent-core.js or agent-repl.js.
|
|
10
|
+
* Instead it provides an init function that the REPL calls once
|
|
11
|
+
* during session setup, passing a LearningLoop context object.
|
|
12
|
+
*
|
|
13
|
+
* Design:
|
|
14
|
+
* - Fire-and-forget: learning failures never break the host flow
|
|
15
|
+
* - No-op when learningCtx is null or disabled
|
|
16
|
+
* - Stateless — the trajectory ID is tracked on learningCtx
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} LearningContext
|
|
21
|
+
* @property {import("./trajectory-store.js").TrajectoryStore} trajectoryStore
|
|
22
|
+
* @property {string|null} currentTrajectoryId — set per user turn
|
|
23
|
+
* @property {boolean} enabled
|
|
24
|
+
* @property {string} sessionId
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Called on UserPromptSubmit — starts a new trajectory for this turn.
|
|
29
|
+
* @param {LearningContext} ctx
|
|
30
|
+
* @param {string} prompt — the user's raw input
|
|
31
|
+
*/
|
|
32
|
+
export function onUserPromptSubmit(ctx, prompt) {
|
|
33
|
+
if (!ctx || !ctx.enabled || !ctx.trajectoryStore) return;
|
|
34
|
+
try {
|
|
35
|
+
ctx.currentTrajectoryId = ctx.trajectoryStore.startTrajectory(
|
|
36
|
+
ctx.sessionId,
|
|
37
|
+
prompt,
|
|
38
|
+
);
|
|
39
|
+
} catch (_err) {
|
|
40
|
+
// Learning failures never break the REPL
|
|
41
|
+
ctx.currentTrajectoryId = null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Called on PostToolUse — appends a tool call to the current trajectory.
|
|
47
|
+
* @param {LearningContext} ctx
|
|
48
|
+
* @param {{tool:string, args:any, result:any, durationMs?:number, status?:string}} record
|
|
49
|
+
*/
|
|
50
|
+
export function onPostToolUse(ctx, record) {
|
|
51
|
+
if (!ctx || !ctx.enabled || !ctx.trajectoryStore || !ctx.currentTrajectoryId)
|
|
52
|
+
return;
|
|
53
|
+
try {
|
|
54
|
+
ctx.trajectoryStore.appendToolCall(ctx.currentTrajectoryId, record);
|
|
55
|
+
} catch (_err) {
|
|
56
|
+
// Swallow — never break the agent loop
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Called on response-complete — completes the current trajectory.
|
|
62
|
+
* @param {LearningContext} ctx
|
|
63
|
+
* @param {{finalResponse?:string, tags?:string[]}} data
|
|
64
|
+
* @returns {object|null} completed trajectory or null
|
|
65
|
+
*/
|
|
66
|
+
export function onResponseComplete(ctx, data) {
|
|
67
|
+
if (!ctx || !ctx.enabled || !ctx.trajectoryStore || !ctx.currentTrajectoryId)
|
|
68
|
+
return null;
|
|
69
|
+
try {
|
|
70
|
+
const trajectory = ctx.trajectoryStore.completeTrajectory(
|
|
71
|
+
ctx.currentTrajectoryId,
|
|
72
|
+
data,
|
|
73
|
+
);
|
|
74
|
+
// Reset for next turn
|
|
75
|
+
const finishedId = ctx.currentTrajectoryId;
|
|
76
|
+
ctx.currentTrajectoryId = null;
|
|
77
|
+
return trajectory;
|
|
78
|
+
} catch (_err) {
|
|
79
|
+
ctx.currentTrajectoryId = null;
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Called on SessionEnd — reserved for ReflectionEngine (P3).
|
|
86
|
+
* Currently a no-op placeholder.
|
|
87
|
+
* @param {LearningContext} ctx
|
|
88
|
+
*/
|
|
89
|
+
export function onSessionEnd(ctx) {
|
|
90
|
+
if (!ctx || !ctx.enabled) return;
|
|
91
|
+
// P3: will call reflectionEngine.onSessionEnd(ctx.sessionId)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create a LearningContext for a session.
|
|
96
|
+
* Returns null if db is not available or learning is disabled.
|
|
97
|
+
*
|
|
98
|
+
* @param {import("better-sqlite3").Database|null} db
|
|
99
|
+
* @param {string} sessionId
|
|
100
|
+
* @param {{enabled?:boolean}} [config]
|
|
101
|
+
* @returns {LearningContext|null}
|
|
102
|
+
*/
|
|
103
|
+
export function createLearningContext(db, sessionId, config = {}) {
|
|
104
|
+
if (!db) return null;
|
|
105
|
+
const enabled = config.enabled !== false;
|
|
106
|
+
if (!enabled) return null;
|
|
107
|
+
|
|
108
|
+
// Lazy import to avoid circular deps — TrajectoryStore calls ensureLearningTables
|
|
109
|
+
const { TrajectoryStore } = require("./trajectory-store.js");
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
trajectoryStore: new TrajectoryStore(db),
|
|
113
|
+
currentTrajectoryId: null,
|
|
114
|
+
enabled: true,
|
|
115
|
+
sessionId,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learning Loop — Database table definitions.
|
|
3
|
+
*
|
|
4
|
+
* Tables:
|
|
5
|
+
* - learning_trajectories — full execution traces (intent → tools → response)
|
|
6
|
+
* - learning_trajectory_tags — per-trajectory tag index for pattern matching
|
|
7
|
+
* - skill_improvement_log — skill patch history (Phase P2)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create all learning-related tables. Idempotent (IF NOT EXISTS).
|
|
12
|
+
* @param {import("better-sqlite3").Database} db
|
|
13
|
+
*/
|
|
14
|
+
export function ensureLearningTables(db) {
|
|
15
|
+
db.exec(`
|
|
16
|
+
CREATE TABLE IF NOT EXISTS learning_trajectories (
|
|
17
|
+
id TEXT PRIMARY KEY,
|
|
18
|
+
session_id TEXT NOT NULL,
|
|
19
|
+
user_intent TEXT,
|
|
20
|
+
tool_chain TEXT NOT NULL DEFAULT '[]',
|
|
21
|
+
tool_count INTEGER DEFAULT 0,
|
|
22
|
+
final_response TEXT,
|
|
23
|
+
outcome_score REAL,
|
|
24
|
+
outcome_source TEXT,
|
|
25
|
+
complexity_level TEXT DEFAULT 'simple',
|
|
26
|
+
synthesized_skill TEXT,
|
|
27
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
28
|
+
completed_at TEXT
|
|
29
|
+
)
|
|
30
|
+
`);
|
|
31
|
+
|
|
32
|
+
db.exec(`
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_traj_session
|
|
34
|
+
ON learning_trajectories(session_id)
|
|
35
|
+
`);
|
|
36
|
+
db.exec(`
|
|
37
|
+
CREATE INDEX IF NOT EXISTS idx_traj_score
|
|
38
|
+
ON learning_trajectories(outcome_score)
|
|
39
|
+
`);
|
|
40
|
+
db.exec(`
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_traj_complexity
|
|
42
|
+
ON learning_trajectories(complexity_level)
|
|
43
|
+
`);
|
|
44
|
+
|
|
45
|
+
db.exec(`
|
|
46
|
+
CREATE TABLE IF NOT EXISTS learning_trajectory_tags (
|
|
47
|
+
trajectory_id TEXT NOT NULL,
|
|
48
|
+
tag TEXT NOT NULL,
|
|
49
|
+
UNIQUE(trajectory_id, tag)
|
|
50
|
+
)
|
|
51
|
+
`);
|
|
52
|
+
|
|
53
|
+
db.exec(`
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_traj_tags_tid
|
|
55
|
+
ON learning_trajectory_tags(trajectory_id)
|
|
56
|
+
`);
|
|
57
|
+
|
|
58
|
+
db.exec(`
|
|
59
|
+
CREATE TABLE IF NOT EXISTS skill_improvement_log (
|
|
60
|
+
skill_name TEXT NOT NULL,
|
|
61
|
+
trigger_type TEXT NOT NULL,
|
|
62
|
+
detail TEXT,
|
|
63
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
64
|
+
)
|
|
65
|
+
`);
|
|
66
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OutcomeFeedback — Connects tool execution results to quality scores,
|
|
3
|
+
* producing reward signals for the learning loop.
|
|
4
|
+
*
|
|
5
|
+
* Three signal sources:
|
|
6
|
+
* 1. Auto-score — heuristic based on error rate, retries, final status
|
|
7
|
+
* 2. User feedback — explicit 👍/👎 or /feedback command
|
|
8
|
+
* 3. Correction detection — user redoes/corrects within a time window
|
|
9
|
+
*
|
|
10
|
+
* Propagation:
|
|
11
|
+
* - TrajectoryStore (backfill outcome_score)
|
|
12
|
+
* - InstinctManager (tool preference / workflow patterns)
|
|
13
|
+
* - EvolutionSystem (capability assessment)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { INSTINCT_CATEGORIES, recordInstinct } from "../instinct-manager.js";
|
|
17
|
+
import { assessCapability } from "../evolution-system.js";
|
|
18
|
+
|
|
19
|
+
// ── Auto-scoring ────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Detect retries: same tool called consecutively 2+ times.
|
|
23
|
+
* @param {Array<{tool:string}>} chain
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
export function hasRetries(chain) {
|
|
27
|
+
for (let i = 1; i < chain.length; i++) {
|
|
28
|
+
if (chain[i].tool === chain[i - 1].tool) return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Auto-score a trajectory based on execution quality heuristics.
|
|
35
|
+
* @param {{toolChain:Array<{status:string, tool:string}>}} trajectory
|
|
36
|
+
* @returns {number} score 0-1
|
|
37
|
+
*/
|
|
38
|
+
export function autoScore(trajectory) {
|
|
39
|
+
const chain = trajectory.toolChain || [];
|
|
40
|
+
if (chain.length === 0) return 0.5;
|
|
41
|
+
|
|
42
|
+
let score = 0.5;
|
|
43
|
+
|
|
44
|
+
const errorCount = chain.filter(
|
|
45
|
+
(t) => t.status === "error" || t.status === "failed",
|
|
46
|
+
).length;
|
|
47
|
+
const totalCount = chain.length;
|
|
48
|
+
|
|
49
|
+
// No errors → +0.2
|
|
50
|
+
if (errorCount === 0) score += 0.2;
|
|
51
|
+
|
|
52
|
+
// Error rate > 50% → -0.3
|
|
53
|
+
if (totalCount > 0 && errorCount / totalCount > 0.5) score -= 0.3;
|
|
54
|
+
|
|
55
|
+
// Has retries → -0.1
|
|
56
|
+
if (hasRetries(chain)) score -= 0.1;
|
|
57
|
+
|
|
58
|
+
// Final tool succeeded → +0.1
|
|
59
|
+
const lastTool = chain[chain.length - 1];
|
|
60
|
+
if (lastTool && lastTool.status === "completed") score += 0.1;
|
|
61
|
+
|
|
62
|
+
return Math.max(0, Math.min(1, score));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── Correction detection ────────────────────────────────
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Negation patterns for correction detection (Chinese + English).
|
|
69
|
+
*/
|
|
70
|
+
const NEGATION_PATTERNS = [
|
|
71
|
+
/不[是对]/,
|
|
72
|
+
/错了/,
|
|
73
|
+
/重[新做来]/,
|
|
74
|
+
/别这样/,
|
|
75
|
+
/不要这样/,
|
|
76
|
+
/not\s+right/i,
|
|
77
|
+
/wrong/i,
|
|
78
|
+
/redo/i,
|
|
79
|
+
/don't/i,
|
|
80
|
+
/undo/i,
|
|
81
|
+
/try\s+again/i,
|
|
82
|
+
/that's\s+not/i,
|
|
83
|
+
/incorrect/i,
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Detect if a user message is correcting the previous agent action.
|
|
88
|
+
* @param {string} currentMessage — the user's latest input
|
|
89
|
+
* @param {{toolChain:Array<{tool:string}>}} [previousTrajectory] — previous turn's trajectory
|
|
90
|
+
* @returns {{isCorrection:boolean, detail:string}}
|
|
91
|
+
*/
|
|
92
|
+
export function detectCorrection(currentMessage, previousTrajectory) {
|
|
93
|
+
if (!currentMessage || !previousTrajectory) {
|
|
94
|
+
return { isCorrection: false, detail: "" };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const msg = currentMessage.trim();
|
|
98
|
+
|
|
99
|
+
// Strategy 1: negation pattern match
|
|
100
|
+
for (const pattern of NEGATION_PATTERNS) {
|
|
101
|
+
if (pattern.test(msg)) {
|
|
102
|
+
return {
|
|
103
|
+
isCorrection: true,
|
|
104
|
+
detail: `Negation detected: "${msg.slice(0, 80)}"`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Strategy 2: user references same tool/file from previous trajectory
|
|
110
|
+
const prevTools = (previousTrajectory.toolChain || []).map((t) => t.tool);
|
|
111
|
+
const prevArgs = (previousTrajectory.toolChain || [])
|
|
112
|
+
.map((t) => {
|
|
113
|
+
if (!t.args) return [];
|
|
114
|
+
return Object.values(t.args).filter((v) => typeof v === "string");
|
|
115
|
+
})
|
|
116
|
+
.flat();
|
|
117
|
+
|
|
118
|
+
// Check if user mentions a file path from previous tool args
|
|
119
|
+
for (const arg of prevArgs) {
|
|
120
|
+
if (arg.length > 3 && msg.includes(arg)) {
|
|
121
|
+
// User references same file → could be correction
|
|
122
|
+
// Only flag if combined with imperative language
|
|
123
|
+
if (/instead|rather|actually|but|however/i.test(msg)) {
|
|
124
|
+
return {
|
|
125
|
+
isCorrection: true,
|
|
126
|
+
detail: `References previous arg "${arg}" with correction language`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { isCorrection: false, detail: "" };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── OutcomeFeedback class ───────────────────────────────
|
|
136
|
+
|
|
137
|
+
export class OutcomeFeedback {
|
|
138
|
+
/**
|
|
139
|
+
* @param {import("better-sqlite3").Database} db
|
|
140
|
+
* @param {import("./trajectory-store.js").TrajectoryStore} trajectoryStore
|
|
141
|
+
*/
|
|
142
|
+
constructor(db, trajectoryStore) {
|
|
143
|
+
this.db = db;
|
|
144
|
+
this.trajectoryStore = trajectoryStore;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Auto-score a trajectory and backfill the score.
|
|
149
|
+
* @param {string} trajectoryId
|
|
150
|
+
* @returns {number} the computed score
|
|
151
|
+
*/
|
|
152
|
+
scoreTrajectory(trajectoryId) {
|
|
153
|
+
const traj = this.trajectoryStore.getTrajectory(trajectoryId);
|
|
154
|
+
if (!traj) return 0.5;
|
|
155
|
+
|
|
156
|
+
const score = autoScore(traj);
|
|
157
|
+
this.trajectoryStore.setOutcomeScore(trajectoryId, score, "auto");
|
|
158
|
+
return score;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Record explicit user feedback, overriding auto-score.
|
|
163
|
+
* @param {string} trajectoryId
|
|
164
|
+
* @param {{rating:"positive"|"negative"|number, comment?:string}} feedback
|
|
165
|
+
*/
|
|
166
|
+
recordUserFeedback(trajectoryId, feedback) {
|
|
167
|
+
let score;
|
|
168
|
+
if (typeof feedback.rating === "number") {
|
|
169
|
+
score = Math.max(0, Math.min(1, feedback.rating));
|
|
170
|
+
} else {
|
|
171
|
+
score = feedback.rating === "positive" ? 1.0 : 0.0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.trajectoryStore.setOutcomeScore(trajectoryId, score, "user");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Check if a new user message is a correction of the previous turn,
|
|
179
|
+
* and if so, downgrade the previous trajectory's score.
|
|
180
|
+
* @param {string} currentMessage
|
|
181
|
+
* @param {string} previousTrajectoryId
|
|
182
|
+
* @returns {{isCorrection:boolean, detail:string}}
|
|
183
|
+
*/
|
|
184
|
+
checkCorrection(currentMessage, previousTrajectoryId) {
|
|
185
|
+
const prevTraj = this.trajectoryStore.getTrajectory(previousTrajectoryId);
|
|
186
|
+
if (!prevTraj) return { isCorrection: false, detail: "" };
|
|
187
|
+
|
|
188
|
+
const result = detectCorrection(currentMessage, prevTraj);
|
|
189
|
+
if (result.isCorrection) {
|
|
190
|
+
// Downgrade the previous trajectory's score
|
|
191
|
+
const currentScore = prevTraj.outcomeScore ?? 0.5;
|
|
192
|
+
const newScore = Math.max(0, currentScore - 0.3);
|
|
193
|
+
this.trajectoryStore.setOutcomeScore(
|
|
194
|
+
previousTrajectoryId,
|
|
195
|
+
newScore,
|
|
196
|
+
"user",
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Propagate a trajectory's feedback signal to Instinct and Evolution.
|
|
204
|
+
* Call this after scoring is finalized.
|
|
205
|
+
* @param {string} trajectoryId
|
|
206
|
+
*/
|
|
207
|
+
propagateFeedback(trajectoryId) {
|
|
208
|
+
const traj = this.trajectoryStore.getTrajectory(trajectoryId);
|
|
209
|
+
if (!traj || traj.outcomeScore == null) return;
|
|
210
|
+
|
|
211
|
+
const score = traj.outcomeScore;
|
|
212
|
+
const tools = (traj.toolChain || []).map((t) => t.tool);
|
|
213
|
+
const uniqueTools = [...new Set(tools)];
|
|
214
|
+
|
|
215
|
+
// Propagate to Instinct
|
|
216
|
+
try {
|
|
217
|
+
if (score >= 0.8 && uniqueTools.length > 0) {
|
|
218
|
+
// High score → record tool preference
|
|
219
|
+
const toolPattern = uniqueTools.join(" → ");
|
|
220
|
+
recordInstinct(
|
|
221
|
+
this.db,
|
|
222
|
+
INSTINCT_CATEGORIES.TOOL_PREFERENCE,
|
|
223
|
+
toolPattern,
|
|
224
|
+
);
|
|
225
|
+
} else if (score <= 0.3 && uniqueTools.length > 0) {
|
|
226
|
+
// Low score → record as workflow to avoid
|
|
227
|
+
const toolPattern = `avoid: ${uniqueTools.join(" → ")}`;
|
|
228
|
+
recordInstinct(this.db, INSTINCT_CATEGORIES.WORKFLOW, toolPattern);
|
|
229
|
+
}
|
|
230
|
+
} catch (_err) {
|
|
231
|
+
// Instinct propagation failure is non-critical
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Propagate to Evolution
|
|
235
|
+
try {
|
|
236
|
+
for (const tool of uniqueTools) {
|
|
237
|
+
assessCapability(this.db, tool, score, "tool");
|
|
238
|
+
}
|
|
239
|
+
} catch (_err) {
|
|
240
|
+
// Evolution propagation failure is non-critical
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|