clementine-agent 1.1.19 → 1.1.20
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/dist/agent/self-improve.js +22 -6
- package/dist/cli/index.js +163 -0
- package/dist/tools/memory-tools.js +47 -2
- package/dist/tools/tool-meta.js +7 -2
- package/dist/types.d.ts +8 -1
- package/package.json +1 -1
|
@@ -23,6 +23,7 @@ const DEFAULT_CONFIG = {
|
|
|
23
23
|
iterationBudgetMs: 300_000, // 5 min
|
|
24
24
|
maxDurationMs: 3_600_000, // 1 hour
|
|
25
25
|
acceptThreshold: 0.7,
|
|
26
|
+
surfaceThreshold: 0.85,
|
|
26
27
|
plateauLimit: 3,
|
|
27
28
|
// 'source' deprecated — self-improvement produces data, not engine TS edits.
|
|
28
29
|
// 'advisor-rule' writes YAML to ~/.clementine/advisor-rules/user/.
|
|
@@ -300,12 +301,25 @@ export class SelfImproveLoop {
|
|
|
300
301
|
const score = evaluation?.score ?? 0;
|
|
301
302
|
const normalizedScore = score / 10; // Convert 0-10 to 0-1
|
|
302
303
|
const accepted = normalizedScore >= this.config.acceptThreshold;
|
|
304
|
+
// Surface gate: even when accepted, only score >= surfaceThreshold
|
|
305
|
+
// reaches the user's pending-changes inbox. Below that floor we
|
|
306
|
+
// keep the experiment in the trend log but don't ping the user.
|
|
307
|
+
const surfaceFloor = this.config.surfaceThreshold ?? this.config.acceptThreshold;
|
|
308
|
+
const surfaced = normalizedScore >= surfaceFloor;
|
|
303
309
|
const priorScores = history
|
|
304
310
|
.filter(e => e.area === proposal.area && e.target === proposal.target && e.score > 0)
|
|
305
311
|
.map(e => e.score);
|
|
306
312
|
const baselineScore = priorScores.length > 0
|
|
307
313
|
? priorScores.reduce((a, b) => a + b, 0) / priorScores.length
|
|
308
314
|
: 0.5;
|
|
315
|
+
const initialStatus = accepted
|
|
316
|
+
? (surfaced ? 'pending' : 'unsurfaced')
|
|
317
|
+
: 'denied';
|
|
318
|
+
const reason = accepted
|
|
319
|
+
? (surfaced
|
|
320
|
+
? `Score ${score}/10 exceeds surface threshold — pending approval`
|
|
321
|
+
: `Score ${score}/10 accepted but below surface floor (${surfaceFloor * 10}/10) — kept in trend log only`)
|
|
322
|
+
: `Score ${score}/10 below accept threshold (${this.config.acceptThreshold * 10}/10)`;
|
|
309
323
|
const experiment = {
|
|
310
324
|
id,
|
|
311
325
|
iteration: i,
|
|
@@ -319,17 +333,19 @@ export class SelfImproveLoop {
|
|
|
319
333
|
baselineScore,
|
|
320
334
|
score: normalizedScore,
|
|
321
335
|
accepted,
|
|
322
|
-
approvalStatus:
|
|
323
|
-
reason
|
|
324
|
-
? `Score ${score}/10 exceeds threshold — pending approval`
|
|
325
|
-
: `Score ${score}/10 below threshold (${this.config.acceptThreshold * 10}/10)`,
|
|
336
|
+
approvalStatus: initialStatus,
|
|
337
|
+
reason,
|
|
326
338
|
};
|
|
327
339
|
// Step 7: Log
|
|
328
340
|
this.appendExperimentLog(experiment);
|
|
329
341
|
history.push(experiment);
|
|
330
342
|
state.totalExperiments++;
|
|
331
|
-
|
|
332
|
-
|
|
343
|
+
if (accepted && !surfaced) {
|
|
344
|
+
logger.info({ id, area: proposal.area, target: proposal.target, score: score, surfaceFloor: surfaceFloor * 10 }, 'Proposal accepted but unsurfaced — below noise floor, not added to review queue');
|
|
345
|
+
}
|
|
346
|
+
// Step 6: Gate — save pending change + notify (tiered by risk).
|
|
347
|
+
// Only proposals that ALSO clear the surface floor reach the inbox.
|
|
348
|
+
if (accepted && surfaced) {
|
|
333
349
|
const risk = classifyRisk(proposal.area);
|
|
334
350
|
if (this.config.autoApply && risk === 'low') {
|
|
335
351
|
// Low-risk + auto-apply enabled: apply immediately without approval
|
package/dist/cli/index.js
CHANGED
|
@@ -2090,6 +2090,169 @@ configCmd
|
|
|
2090
2090
|
console.error(` Failed to open editor: ${editor}`);
|
|
2091
2091
|
}
|
|
2092
2092
|
});
|
|
2093
|
+
// ── Agent commands ──────────────────────────────────────────────────
|
|
2094
|
+
const agentCmd = program
|
|
2095
|
+
.command('agent')
|
|
2096
|
+
.description('Hire, list, and manage specialist agents');
|
|
2097
|
+
agentCmd
|
|
2098
|
+
.command('list')
|
|
2099
|
+
.description('List all agents with status and tier')
|
|
2100
|
+
.option('--json', 'Emit machine-readable JSON')
|
|
2101
|
+
.action(async (opts) => {
|
|
2102
|
+
const BOLD = '\x1b[1m';
|
|
2103
|
+
const DIM = '\x1b[0;90m';
|
|
2104
|
+
const GREEN = '\x1b[0;32m';
|
|
2105
|
+
const YELLOW = '\x1b[0;33m';
|
|
2106
|
+
const RED = '\x1b[0;31m';
|
|
2107
|
+
const RESET = '\x1b[0m';
|
|
2108
|
+
try {
|
|
2109
|
+
const { AgentManager } = await import('../agent/agent-manager.js');
|
|
2110
|
+
const AGENTS_DIR = path.join(BASE_DIR, 'agents');
|
|
2111
|
+
const mgr = new AgentManager(AGENTS_DIR);
|
|
2112
|
+
const agents = mgr.listAll();
|
|
2113
|
+
if (opts.json) {
|
|
2114
|
+
console.log(JSON.stringify(agents.map(a => ({
|
|
2115
|
+
slug: a.slug, name: a.name, status: a.status, tier: a.tier,
|
|
2116
|
+
description: a.description, hasChannel: !!a.team?.channelName,
|
|
2117
|
+
})), null, 2));
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
if (agents.length === 0) {
|
|
2121
|
+
console.log(`\n No agents found in ${AGENTS_DIR}.`);
|
|
2122
|
+
console.log(` Hire one: ${BOLD}clementine agent new <slug>${RESET}\n`);
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
console.log();
|
|
2126
|
+
console.log(` ${BOLD}${'SLUG'.padEnd(28)}${'NAME'.padEnd(24)}${'STATUS'.padEnd(12)}${'TIER'.padEnd(6)}${RESET}`);
|
|
2127
|
+
console.log(` ${DIM}${'─'.repeat(70)}${RESET}`);
|
|
2128
|
+
for (const a of agents) {
|
|
2129
|
+
const statusColor = a.status === 'active' ? GREEN : a.status === 'paused' ? YELLOW : RED;
|
|
2130
|
+
const statusStr = `${statusColor}${(a.status ?? 'active').padEnd(10)}${RESET}`;
|
|
2131
|
+
console.log(` ${a.slug.padEnd(28)}${(a.name ?? '').slice(0, 22).padEnd(24)}${statusStr} ${String(a.tier).padEnd(6)}`);
|
|
2132
|
+
}
|
|
2133
|
+
console.log();
|
|
2134
|
+
}
|
|
2135
|
+
catch (err) {
|
|
2136
|
+
console.error(` Error listing agents: ${err}`);
|
|
2137
|
+
process.exit(1);
|
|
2138
|
+
}
|
|
2139
|
+
});
|
|
2140
|
+
agentCmd
|
|
2141
|
+
.command('new <slug>')
|
|
2142
|
+
.description('Scaffold a new agent at agents/<slug>/agent.md (does not overwrite)')
|
|
2143
|
+
.option('-n, --name <name>', 'Display name (default: title-case of slug)')
|
|
2144
|
+
.option('-d, --description <text>', 'One-line description of what the agent does')
|
|
2145
|
+
.option('-r, --role <role>', 'Role template — auto-scaffolds CRON.md / PLAYBOOK.md (sdr, researcher)')
|
|
2146
|
+
.option('-t, --tier <tier>', 'Security tier — 1 = vault-only, 2 = bash/git allowed', '1')
|
|
2147
|
+
.option('-m, --model <model>', 'Default model (sonnet, haiku, opus). Inherits global default if omitted.')
|
|
2148
|
+
.option('--channel <name>', 'Discord/Slack channel name the agent listens in (enables team mode)')
|
|
2149
|
+
.action(async (slug, opts) => {
|
|
2150
|
+
const BOLD = '\x1b[1m';
|
|
2151
|
+
const DIM = '\x1b[0;90m';
|
|
2152
|
+
const GREEN = '\x1b[0;32m';
|
|
2153
|
+
const RED = '\x1b[0;31m';
|
|
2154
|
+
const RESET = '\x1b[0m';
|
|
2155
|
+
// Validate slug — lowercase, dashes, alphanumeric, no leading/trailing dash.
|
|
2156
|
+
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(slug) || slug.length < 3) {
|
|
2157
|
+
console.error(` ${RED}Invalid slug${RESET}: must be 3+ chars, lowercase letters/digits/dashes, no leading/trailing dash. Got: "${slug}"`);
|
|
2158
|
+
process.exit(1);
|
|
2159
|
+
}
|
|
2160
|
+
if (slug === 'clementine') {
|
|
2161
|
+
console.error(` ${RED}Reserved slug${RESET}: "clementine" is the master assistant. Pick a different name.`);
|
|
2162
|
+
process.exit(1);
|
|
2163
|
+
}
|
|
2164
|
+
const tier = Math.max(1, Math.min(2, parseInt(opts.tier ?? '1', 10) || 1));
|
|
2165
|
+
const name = opts.name ?? slug.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
2166
|
+
const description = opts.description ?? `${name} — specialist agent.`;
|
|
2167
|
+
try {
|
|
2168
|
+
const { AgentManager } = await import('../agent/agent-manager.js');
|
|
2169
|
+
const AGENTS_DIR = path.join(BASE_DIR, 'agents');
|
|
2170
|
+
const mgr = new AgentManager(AGENTS_DIR);
|
|
2171
|
+
// Refuse if already taken (createAgent throws but we want a friendly error)
|
|
2172
|
+
if (mgr.get(slug)) {
|
|
2173
|
+
console.error(` ${RED}Agent "${slug}" already exists${RESET}. Edit ${path.join(AGENTS_DIR, slug, 'agent.md')} or pick a different slug.`);
|
|
2174
|
+
process.exit(1);
|
|
2175
|
+
}
|
|
2176
|
+
const config = {
|
|
2177
|
+
name,
|
|
2178
|
+
description,
|
|
2179
|
+
tier,
|
|
2180
|
+
};
|
|
2181
|
+
if (opts.model)
|
|
2182
|
+
config.model = opts.model;
|
|
2183
|
+
if (opts.channel)
|
|
2184
|
+
config.channelName = opts.channel;
|
|
2185
|
+
if (opts.role)
|
|
2186
|
+
config.role = opts.role;
|
|
2187
|
+
const profile = mgr.createAgent(config);
|
|
2188
|
+
const agentMdPath = path.join(profile.agentDir ?? path.join(AGENTS_DIR, slug), 'agent.md');
|
|
2189
|
+
console.log();
|
|
2190
|
+
console.log(` ${GREEN}✓${RESET} Created agent ${BOLD}${profile.name}${RESET} (${profile.slug})`);
|
|
2191
|
+
console.log(` ${DIM}File: ${agentMdPath}${RESET}`);
|
|
2192
|
+
if (opts.role) {
|
|
2193
|
+
console.log(` ${DIM}Role scaffold "${opts.role}" wrote CRON.md / PLAYBOOK.md / SEQUENCES.md if applicable.${RESET}`);
|
|
2194
|
+
}
|
|
2195
|
+
console.log();
|
|
2196
|
+
console.log(` Next steps:`);
|
|
2197
|
+
console.log(` 1. Edit ${BOLD}agent.md${RESET} to refine personality / standing instructions`);
|
|
2198
|
+
if (!opts.channel) {
|
|
2199
|
+
console.log(` 2. Add a ${BOLD}channelName${RESET} (and DISCORD/SLACK token via 'clementine config set') to give them their own bot`);
|
|
2200
|
+
}
|
|
2201
|
+
console.log(` 3. Edit ${BOLD}~/.clementine/agents/${slug}/CRON.md${RESET} to schedule autonomous work`);
|
|
2202
|
+
console.log(` 4. Restart the daemon: ${BOLD}clementine restart${RESET}`);
|
|
2203
|
+
console.log();
|
|
2204
|
+
}
|
|
2205
|
+
catch (err) {
|
|
2206
|
+
console.error(` ${RED}Failed to create agent${RESET}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2207
|
+
process.exit(1);
|
|
2208
|
+
}
|
|
2209
|
+
});
|
|
2210
|
+
agentCmd
|
|
2211
|
+
.command('show <slug>')
|
|
2212
|
+
.description('Show an agent\'s file path and parsed profile summary')
|
|
2213
|
+
.action(async (slug) => {
|
|
2214
|
+
const BOLD = '\x1b[1m';
|
|
2215
|
+
const DIM = '\x1b[0;90m';
|
|
2216
|
+
const RED = '\x1b[0;31m';
|
|
2217
|
+
const RESET = '\x1b[0m';
|
|
2218
|
+
try {
|
|
2219
|
+
const { AgentManager } = await import('../agent/agent-manager.js');
|
|
2220
|
+
const AGENTS_DIR = path.join(BASE_DIR, 'agents');
|
|
2221
|
+
const mgr = new AgentManager(AGENTS_DIR);
|
|
2222
|
+
const profile = mgr.get(slug);
|
|
2223
|
+
if (!profile) {
|
|
2224
|
+
console.error(` ${RED}Agent "${slug}" not found${RESET}.`);
|
|
2225
|
+
console.error(` List agents: ${BOLD}clementine agent list${RESET}`);
|
|
2226
|
+
process.exit(1);
|
|
2227
|
+
}
|
|
2228
|
+
const agentMdPath = path.join(profile.agentDir ?? path.join(AGENTS_DIR, slug), 'agent.md');
|
|
2229
|
+
console.log();
|
|
2230
|
+
console.log(` ${BOLD}${profile.name}${RESET} ${DIM}(${profile.slug})${RESET}`);
|
|
2231
|
+
console.log(` ${DIM}${agentMdPath}${RESET}`);
|
|
2232
|
+
console.log();
|
|
2233
|
+
console.log(` Status: ${profile.status ?? 'active'}`);
|
|
2234
|
+
console.log(` Tier: ${profile.tier}`);
|
|
2235
|
+
if (profile.model)
|
|
2236
|
+
console.log(` Model: ${profile.model}`);
|
|
2237
|
+
console.log(` Description: ${profile.description || DIM + '(none)' + RESET}`);
|
|
2238
|
+
if (profile.team?.channelName) {
|
|
2239
|
+
const ch = Array.isArray(profile.team.channelName) ? profile.team.channelName.join(', ') : profile.team.channelName;
|
|
2240
|
+
console.log(` Channel: ${ch}`);
|
|
2241
|
+
}
|
|
2242
|
+
if (profile.activeHours) {
|
|
2243
|
+
console.log(` Active hours: ${profile.activeHours.start.toFixed(2)}–${profile.activeHours.end.toFixed(2)} (decimal hours)`);
|
|
2244
|
+
}
|
|
2245
|
+
if (profile.budgetMonthlyCents != null && profile.budgetMonthlyCents > 0) {
|
|
2246
|
+
console.log(` Budget/mo: $${(profile.budgetMonthlyCents / 100).toFixed(2)}`);
|
|
2247
|
+
}
|
|
2248
|
+
console.log(` Strict mem: ${profile.strictMemoryIsolation === false ? 'no (soft boost)' : 'yes (default)'}`);
|
|
2249
|
+
console.log();
|
|
2250
|
+
}
|
|
2251
|
+
catch (err) {
|
|
2252
|
+
console.error(` Error reading agent: ${err}`);
|
|
2253
|
+
process.exit(1);
|
|
2254
|
+
}
|
|
2255
|
+
});
|
|
2093
2256
|
// ── Memory commands ─────────────────────────────────────────────────
|
|
2094
2257
|
const memoryCmd = program
|
|
2095
2258
|
.command('memory')
|
|
@@ -305,7 +305,7 @@ export function registerMemoryTools(server) {
|
|
|
305
305
|
return textResult(results.join('\n'));
|
|
306
306
|
});
|
|
307
307
|
// ── 4. memory_recall ───────────────────────────────────────────────────
|
|
308
|
-
server.tool('memory_recall', getToolDescription('memory_recall') ?? 'Context retrieval combining FTS5 relevance + recency search
|
|
308
|
+
server.tool('memory_recall', getToolDescription('memory_recall') ?? 'Context retrieval combining FTS5 relevance + recency search, scoped to your memory + global. For cross-agent synthesis use brain_recall.', {
|
|
309
309
|
query: z.string().describe('Natural language search query'),
|
|
310
310
|
category: z.enum(['facts', 'events', 'discoveries', 'preferences', 'advice']).optional().describe('Filter by category'),
|
|
311
311
|
topic: z.string().optional().describe('Filter by topic'),
|
|
@@ -321,11 +321,56 @@ export function registerMemoryTools(server) {
|
|
|
321
321
|
store.recordAccess(chunkIds);
|
|
322
322
|
const lines = results.map(r => {
|
|
323
323
|
const label = `[${r.matchType}]`;
|
|
324
|
+
const agentTag = r.agentSlug ? ` [agent: ${r.agentSlug}]` : '';
|
|
324
325
|
const preview = r.content.slice(0, 300).replace(/\n/g, ' ');
|
|
325
|
-
return `**${r.sourceFile} > ${r.section}** ${label} (score: ${r.score.toFixed(3)})\n${preview}\n`;
|
|
326
|
+
return `**${r.sourceFile} > ${r.section}** ${label}${agentTag} (score: ${r.score.toFixed(3)})\n${preview}\n`;
|
|
326
327
|
});
|
|
327
328
|
return textResult(lines.join('\n'));
|
|
328
329
|
});
|
|
330
|
+
// ── 4b. brain_recall ──────────────────────────────────────────────────
|
|
331
|
+
//
|
|
332
|
+
// Cross-agent unified recall. Differs from memory_recall in two ways:
|
|
333
|
+
// 1. No agentSlug scope — pulls from every agent's memory + global.
|
|
334
|
+
// 2. Always tags each result with [agent: <slug>] so the caller can
|
|
335
|
+
// see provenance (which agent's memory the chunk came from).
|
|
336
|
+
//
|
|
337
|
+
// Intended caller: Clementine herself. Specialist agents normally stay in
|
|
338
|
+
// memory_recall (which respects strict isolation). brain_recall is the
|
|
339
|
+
// "single brain" view that lets the master assistant synthesize across
|
|
340
|
+
// the whole team.
|
|
341
|
+
server.tool('brain_recall', getToolDescription('brain_recall') ?? 'Cross-agent unified recall — searches across all agents with source-agent attribution. Use for synthesis questions or when you need the full picture, not just your own scope.', {
|
|
342
|
+
query: z.string().describe('Natural language query — what to find across all agents'),
|
|
343
|
+
category: z.enum(['facts', 'events', 'discoveries', 'preferences', 'advice']).optional().describe('Filter by category'),
|
|
344
|
+
topic: z.string().optional().describe('Filter by topic'),
|
|
345
|
+
limit: z.number().optional().describe('Max results across all agents (default 12)'),
|
|
346
|
+
}, async ({ query, category, topic, limit }) => {
|
|
347
|
+
const store = await getStore();
|
|
348
|
+
// Intentionally omit agentSlug — we want the unscoped, cross-agent view.
|
|
349
|
+
const results = store.searchContext(query, { category, topic, limit: limit ?? 12 });
|
|
350
|
+
if (!results.length) {
|
|
351
|
+
return textResult(`No results for: ${query}`);
|
|
352
|
+
}
|
|
353
|
+
const chunkIds = results.map(r => r.chunkId).filter(Boolean);
|
|
354
|
+
if (chunkIds.length)
|
|
355
|
+
store.recordAccess(chunkIds);
|
|
356
|
+
// Group attribution counts so the agent gets a quick summary of the spread.
|
|
357
|
+
const perAgent = new Map();
|
|
358
|
+
for (const r of results) {
|
|
359
|
+
const key = r.agentSlug ?? 'global';
|
|
360
|
+
perAgent.set(key, (perAgent.get(key) ?? 0) + 1);
|
|
361
|
+
}
|
|
362
|
+
const spread = Array.from(perAgent.entries())
|
|
363
|
+
.sort((a, b) => b[1] - a[1])
|
|
364
|
+
.map(([slug, n]) => `${slug}:${n}`)
|
|
365
|
+
.join(', ');
|
|
366
|
+
const lines = results.map(r => {
|
|
367
|
+
const agent = r.agentSlug ?? 'global';
|
|
368
|
+
const label = `[${r.matchType}]`;
|
|
369
|
+
const preview = r.content.slice(0, 300).replace(/\n/g, ' ');
|
|
370
|
+
return `**${r.sourceFile} > ${r.section}** ${label} [agent: ${agent}] (score: ${r.score.toFixed(3)})\n${preview}\n`;
|
|
371
|
+
});
|
|
372
|
+
return textResult(`Cross-agent spread: ${spread}\n\n${lines.join('\n')}`);
|
|
373
|
+
});
|
|
329
374
|
// ── 10. memory_connections ─────────────────────────────────────────────
|
|
330
375
|
server.tool('memory_connections', 'Query the wikilink graph — find all notes connected to/from a given note.', {
|
|
331
376
|
note_name: z.string().describe('Note name (without .md) to find connections for'),
|
package/dist/tools/tool-meta.js
CHANGED
|
@@ -22,10 +22,15 @@ const TOOL_META = {
|
|
|
22
22
|
paginationNote: 'Default limit is 20 results. For broad queries, start with limit=5 and increase only if needed.',
|
|
23
23
|
},
|
|
24
24
|
memory_recall: {
|
|
25
|
-
description: 'Context retrieval combining text relevance + recency
|
|
26
|
-
exampleUsage: 'Use before responding to questions about people, projects, or topics the user has discussed before.',
|
|
25
|
+
description: 'Context retrieval combining text relevance + recency, scoped to YOUR memory + global memory. Best as your default "what do I know about X" tool when the question lives in your domain. For cross-agent synthesis (what your team as a whole knows), use brain_recall instead.',
|
|
26
|
+
exampleUsage: 'Use before responding to questions about people, projects, or topics the user has discussed with you before.',
|
|
27
27
|
returnHint: 'Ranked chunks with source file, category, and content preview.',
|
|
28
28
|
},
|
|
29
|
+
brain_recall: {
|
|
30
|
+
description: 'CROSS-AGENT unified recall — searches the entire memory store across YOU and every team agent, with source-agent attribution per result. Use for synthesis questions ("what does my team know about X?", "have any of my agents discussed Y?"), or whenever you need the full picture instead of just your own scope. Specialist agents normally stay in memory_recall (which respects their isolation); brain_recall is the single-brain view, primarily for Clementine.',
|
|
31
|
+
exampleUsage: 'When the user asks "what have we collectively learned about Acme this quarter", brain_recall returns chunks from every agent that touched the topic.',
|
|
32
|
+
returnHint: 'Ranked chunks tagged [agent: <slug>] or [agent: global], plus source file and content preview.',
|
|
33
|
+
},
|
|
29
34
|
memory_read: {
|
|
30
35
|
description: "Read a note from the Obsidian vault. Shortcuts: 'today' (daily note), 'yesterday', 'memory' (MEMORY.md), 'tasks' (TASKS.md), 'heartbeat', 'cron', 'soul'. Or pass a relative path like '03-Projects/my-project.md'.",
|
|
31
36
|
exampleUsage: "memory_read('today') to check what happened today before making plans.",
|
package/dist/types.d.ts
CHANGED
|
@@ -508,7 +508,7 @@ export interface SelfImproveExperiment {
|
|
|
508
508
|
baselineScore: number;
|
|
509
509
|
score: number;
|
|
510
510
|
accepted: boolean;
|
|
511
|
-
approvalStatus: 'pending' | 'approved' | 'denied' | 'expired';
|
|
511
|
+
approvalStatus: 'pending' | 'approved' | 'denied' | 'expired' | 'unsurfaced';
|
|
512
512
|
reason: string;
|
|
513
513
|
error?: string;
|
|
514
514
|
}
|
|
@@ -548,6 +548,13 @@ export interface SelfImproveConfig {
|
|
|
548
548
|
iterationBudgetMs: number;
|
|
549
549
|
maxDurationMs: number;
|
|
550
550
|
acceptThreshold: number;
|
|
551
|
+
/**
|
|
552
|
+
* Default: 0.85. Stricter floor for what reaches the user's pending-changes
|
|
553
|
+
* inbox. Proposals scoring >= acceptThreshold but < surfaceThreshold are
|
|
554
|
+
* marked 'unsurfaced' — kept in the experiment log for trend analysis but
|
|
555
|
+
* NOT written to PENDING_DIR. Cuts noise without losing signal data.
|
|
556
|
+
*/
|
|
557
|
+
surfaceThreshold?: number;
|
|
551
558
|
plateauLimit: number;
|
|
552
559
|
areas: ('soul' | 'cron' | 'workflow' | 'memory' | 'agent' | 'source' | 'communication' | 'goal' | 'advisor-rule' | 'prompt-override')[];
|
|
553
560
|
/** Enable tiered auto-apply: low-risk changes apply without approval. Default: false. */
|