pi-continuous-learning 0.6.0 → 0.8.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.
- package/README.md +78 -0
- package/dist/agents-md.d.ts +23 -2
- package/dist/agents-md.d.ts.map +1 -1
- package/dist/agents-md.js +58 -3
- package/dist/agents-md.js.map +1 -1
- package/dist/analysis-event-log.d.ts +50 -0
- package/dist/analysis-event-log.d.ts.map +1 -0
- package/dist/analysis-event-log.js +120 -0
- package/dist/analysis-event-log.js.map +1 -0
- package/dist/analysis-notification.d.ts +20 -0
- package/dist/analysis-notification.d.ts.map +1 -0
- package/dist/analysis-notification.js +63 -0
- package/dist/analysis-notification.js.map +1 -0
- package/dist/cli/analyze-single-shot.d.ts +20 -2
- package/dist/cli/analyze-single-shot.d.ts.map +1 -1
- package/dist/cli/analyze-single-shot.js +109 -5
- package/dist/cli/analyze-single-shot.js.map +1 -1
- package/dist/cli/analyze.js +132 -16
- package/dist/cli/analyze.js.map +1 -1
- package/dist/command-scaffold.d.ts +25 -0
- package/dist/command-scaffold.d.ts.map +1 -0
- package/dist/command-scaffold.js +77 -0
- package/dist/command-scaffold.js.map +1 -0
- package/dist/confidence.d.ts +12 -1
- package/dist/confidence.d.ts.map +1 -1
- package/dist/confidence.js +37 -9
- package/dist/confidence.js.map +1 -1
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +31 -0
- package/dist/config.js.map +1 -1
- package/dist/graduation.d.ts +63 -0
- package/dist/graduation.d.ts.map +1 -0
- package/dist/graduation.js +155 -0
- package/dist/graduation.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/instinct-cleanup.d.ts +57 -0
- package/dist/instinct-cleanup.d.ts.map +1 -0
- package/dist/instinct-cleanup.js +150 -0
- package/dist/instinct-cleanup.js.map +1 -0
- package/dist/instinct-graduate.d.ts +43 -0
- package/dist/instinct-graduate.d.ts.map +1 -0
- package/dist/instinct-graduate.js +253 -0
- package/dist/instinct-graduate.js.map +1 -0
- package/dist/instinct-parser.d.ts.map +1 -1
- package/dist/instinct-parser.js +18 -0
- package/dist/instinct-parser.js.map +1 -1
- package/dist/instinct-tools.d.ts.map +1 -1
- package/dist/instinct-tools.js +19 -0
- package/dist/instinct-tools.js.map +1 -1
- package/dist/instinct-validator.d.ts +61 -0
- package/dist/instinct-validator.d.ts.map +1 -0
- package/dist/instinct-validator.js +235 -0
- package/dist/instinct-validator.js.map +1 -0
- package/dist/observation-signal.d.ts +34 -0
- package/dist/observation-signal.d.ts.map +1 -0
- package/dist/observation-signal.js +66 -0
- package/dist/observation-signal.js.map +1 -0
- package/dist/prompts/analyzer-system-single-shot.d.ts.map +1 -1
- package/dist/prompts/analyzer-system-single-shot.js +82 -3
- package/dist/prompts/analyzer-system-single-shot.js.map +1 -1
- package/dist/prompts/analyzer-user-single-shot.d.ts.map +1 -1
- package/dist/prompts/analyzer-user-single-shot.js +5 -3
- package/dist/prompts/analyzer-user-single-shot.js.map +1 -1
- package/dist/skill-scaffold.d.ts +23 -0
- package/dist/skill-scaffold.d.ts.map +1 -0
- package/dist/skill-scaffold.js +62 -0
- package/dist/skill-scaffold.js.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agents-md.ts +73 -3
- package/src/analysis-event-log.ts +171 -0
- package/src/analysis-notification.ts +79 -0
- package/src/cli/analyze-single-shot.ts +131 -5
- package/src/cli/analyze.ts +157 -15
- package/src/command-scaffold.ts +105 -0
- package/src/confidence.ts +35 -8
- package/src/config.ts +40 -0
- package/src/graduation.ts +243 -0
- package/src/index.ts +16 -0
- package/src/instinct-cleanup.ts +204 -0
- package/src/instinct-graduate.ts +377 -0
- package/src/instinct-parser.ts +18 -0
- package/src/instinct-tools.ts +26 -0
- package/src/instinct-validator.ts +287 -0
- package/src/observation-signal.ts +80 -0
- package/src/prompts/analyzer-system-single-shot.ts +82 -3
- package/src/prompts/analyzer-user-single-shot.ts +11 -2
- package/src/skill-scaffold.ts +90 -0
- package/src/types.ts +11 -0
package/src/cli/analyze.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
writeFileSync,
|
|
7
7
|
unlinkSync,
|
|
8
8
|
} from "node:fs";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
9
10
|
import { join } from "node:path";
|
|
10
11
|
import { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
11
12
|
import { getModel } from "@mariozechner/pi-ai";
|
|
@@ -22,13 +23,21 @@ import {
|
|
|
22
23
|
} from "../storage.js";
|
|
23
24
|
import { countObservations } from "../observations.js";
|
|
24
25
|
import { runDecayPass } from "../instinct-decay.js";
|
|
26
|
+
import { runCleanupPass } from "../instinct-cleanup.js";
|
|
25
27
|
import { tailObservationsSince } from "../prompts/analyzer-user.js";
|
|
26
28
|
import { buildSingleShotSystemPrompt } from "../prompts/analyzer-system-single-shot.js";
|
|
27
29
|
import { buildSingleShotUserPrompt } from "../prompts/analyzer-user-single-shot.js";
|
|
28
30
|
import {
|
|
29
31
|
runSingleShot,
|
|
30
32
|
buildInstinctFromChange,
|
|
33
|
+
estimateTokens,
|
|
31
34
|
} from "./analyze-single-shot.js";
|
|
35
|
+
import { isLowSignalBatch } from "../observation-signal.js";
|
|
36
|
+
import {
|
|
37
|
+
appendAnalysisEvent,
|
|
38
|
+
type InstinctChangeSummary,
|
|
39
|
+
type AnalysisEvent,
|
|
40
|
+
} from "../analysis-event-log.js";
|
|
32
41
|
import {
|
|
33
42
|
loadProjectInstincts,
|
|
34
43
|
loadGlobalInstincts,
|
|
@@ -106,9 +115,31 @@ function startGlobalTimeout(timeoutMs: number, logger: AnalyzeLogger): void {
|
|
|
106
115
|
// Per-project analysis
|
|
107
116
|
// ---------------------------------------------------------------------------
|
|
108
117
|
|
|
118
|
+
/** Max estimated tokens before fallback strategies are applied. */
|
|
119
|
+
const PROMPT_TOKEN_BUDGET = 40_000;
|
|
120
|
+
|
|
109
121
|
interface ProjectMeta {
|
|
110
122
|
last_analyzed_at?: string;
|
|
111
123
|
last_observation_line_count?: number;
|
|
124
|
+
/** SHA-256 hash of the last AGENTS.md content sent for this project (project-level file). */
|
|
125
|
+
agents_md_project_hash?: string;
|
|
126
|
+
/** SHA-256 hash of the last AGENTS.md content sent (global file). */
|
|
127
|
+
agents_md_global_hash?: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function hashContent(content: string): string {
|
|
131
|
+
return createHash("sha256").update(content).digest("hex");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Truncates AGENTS.md content to section headers only (lines starting with #).
|
|
136
|
+
* Used as a fallback when the prompt is over the token budget.
|
|
137
|
+
*/
|
|
138
|
+
function truncateAgentsMdToHeaders(content: string): string {
|
|
139
|
+
return content
|
|
140
|
+
.split("\n")
|
|
141
|
+
.filter((line) => line.startsWith("#"))
|
|
142
|
+
.join("\n");
|
|
112
143
|
}
|
|
113
144
|
|
|
114
145
|
function loadProjectsRegistry(baseDir: string): Record<string, ProjectEntry> {
|
|
@@ -178,6 +209,10 @@ async function analyzeProject(
|
|
|
178
209
|
return { ran: false, skippedReason: "no new observation lines after preprocessing" };
|
|
179
210
|
}
|
|
180
211
|
|
|
212
|
+
if (isLowSignalBatch(newObsLines)) {
|
|
213
|
+
return { ran: false, skippedReason: "low-signal batch (no errors, corrections, or user redirections)" };
|
|
214
|
+
}
|
|
215
|
+
|
|
181
216
|
const obsCount = countObservations(project.id, baseDir);
|
|
182
217
|
if (obsCount < config.min_observations_to_analyze) {
|
|
183
218
|
return { ran: false, skippedReason: `below threshold (${obsCount}/${config.min_observations_to_analyze})` };
|
|
@@ -186,6 +221,7 @@ async function analyzeProject(
|
|
|
186
221
|
const startTime = Date.now();
|
|
187
222
|
logger.projectStart(project.id, project.name, rawLineCount, obsCount);
|
|
188
223
|
|
|
224
|
+
runCleanupPass(project.id, config, baseDir);
|
|
189
225
|
runDecayPass(project.id, baseDir);
|
|
190
226
|
|
|
191
227
|
// Load current instincts inline - no tool calls needed
|
|
@@ -193,8 +229,21 @@ async function analyzeProject(
|
|
|
193
229
|
const globalInstincts = loadGlobalInstincts(baseDir);
|
|
194
230
|
const allInstincts = [...projectInstincts, ...globalInstincts];
|
|
195
231
|
|
|
196
|
-
|
|
197
|
-
const
|
|
232
|
+
// Load AGENTS.md, skipping if content hash is unchanged since last run.
|
|
233
|
+
const rawAgentsMdProject = readAgentsMd(join(project.root, "AGENTS.md"));
|
|
234
|
+
const rawAgentsMdGlobal = readAgentsMd(join(homedir(), ".pi", "agent", "AGENTS.md"));
|
|
235
|
+
|
|
236
|
+
const projectMdHash = rawAgentsMdProject ? hashContent(rawAgentsMdProject) : null;
|
|
237
|
+
const globalMdHash = rawAgentsMdGlobal ? hashContent(rawAgentsMdGlobal) : null;
|
|
238
|
+
|
|
239
|
+
const agentsMdProject =
|
|
240
|
+
rawAgentsMdProject && projectMdHash !== meta.agents_md_project_hash
|
|
241
|
+
? rawAgentsMdProject
|
|
242
|
+
: null;
|
|
243
|
+
const agentsMdGlobal =
|
|
244
|
+
rawAgentsMdGlobal && globalMdHash !== meta.agents_md_global_hash
|
|
245
|
+
? rawAgentsMdGlobal
|
|
246
|
+
: null;
|
|
198
247
|
|
|
199
248
|
let installedSkills: InstalledSkill[] = [];
|
|
200
249
|
try {
|
|
@@ -208,9 +257,51 @@ async function analyzeProject(
|
|
|
208
257
|
// Skills loading is best-effort - continue without them
|
|
209
258
|
}
|
|
210
259
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
260
|
+
let promptObsLines = newObsLines;
|
|
261
|
+
let promptAgentsMdProject = agentsMdProject;
|
|
262
|
+
let promptAgentsMdGlobal = agentsMdGlobal;
|
|
263
|
+
|
|
264
|
+
const userPrompt = buildSingleShotUserPrompt(project, allInstincts, promptObsLines, {
|
|
265
|
+
agentsMdProject: promptAgentsMdProject,
|
|
266
|
+
agentsMdGlobal: promptAgentsMdGlobal,
|
|
267
|
+
installedSkills,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Estimate token budget and apply fallbacks if over limit.
|
|
271
|
+
const systemPromptTokens = estimateTokens(buildSingleShotSystemPrompt());
|
|
272
|
+
let estimatedTotal = systemPromptTokens + estimateTokens(userPrompt);
|
|
273
|
+
|
|
274
|
+
if (estimatedTotal > PROMPT_TOKEN_BUDGET) {
|
|
275
|
+
logger.warn(
|
|
276
|
+
`Prompt over budget (${estimatedTotal} est. tokens > ${PROMPT_TOKEN_BUDGET}). Applying fallbacks.`
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
// Fallback 1: truncate AGENTS.md to headers only.
|
|
280
|
+
if (promptAgentsMdProject) {
|
|
281
|
+
promptAgentsMdProject = truncateAgentsMdToHeaders(promptAgentsMdProject);
|
|
282
|
+
}
|
|
283
|
+
if (promptAgentsMdGlobal) {
|
|
284
|
+
promptAgentsMdGlobal = truncateAgentsMdToHeaders(promptAgentsMdGlobal);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Fallback 2: reduce observation lines to fit budget.
|
|
288
|
+
// Use binary-search-like reduction: keep halving until under budget.
|
|
289
|
+
while (promptObsLines.length > 1) {
|
|
290
|
+
const trimmedPrompt = buildSingleShotUserPrompt(
|
|
291
|
+
project,
|
|
292
|
+
allInstincts,
|
|
293
|
+
promptObsLines,
|
|
294
|
+
{ agentsMdProject: promptAgentsMdProject, agentsMdGlobal: promptAgentsMdGlobal, installedSkills }
|
|
295
|
+
);
|
|
296
|
+
estimatedTotal = systemPromptTokens + estimateTokens(trimmedPrompt);
|
|
297
|
+
if (estimatedTotal <= PROMPT_TOKEN_BUDGET) break;
|
|
298
|
+
promptObsLines = promptObsLines.slice(Math.floor(promptObsLines.length / 2));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const finalUserPrompt = buildSingleShotUserPrompt(project, allInstincts, promptObsLines, {
|
|
303
|
+
agentsMdProject: promptAgentsMdProject,
|
|
304
|
+
agentsMdGlobal: promptAgentsMdGlobal,
|
|
214
305
|
installedSkills,
|
|
215
306
|
});
|
|
216
307
|
|
|
@@ -226,7 +317,7 @@ async function analyzeProject(
|
|
|
226
317
|
const context = {
|
|
227
318
|
systemPrompt: buildSingleShotSystemPrompt(),
|
|
228
319
|
messages: [
|
|
229
|
-
{ role: "user" as const, content:
|
|
320
|
+
{ role: "user" as const, content: finalUserPrompt, timestamp: Date.now() },
|
|
230
321
|
],
|
|
231
322
|
};
|
|
232
323
|
|
|
@@ -235,6 +326,9 @@ async function analyzeProject(
|
|
|
235
326
|
const timeoutHandle = setTimeout(() => abortController.abort(), timeoutMs);
|
|
236
327
|
|
|
237
328
|
const instinctCounts = { created: 0, updated: 0, deleted: 0 };
|
|
329
|
+
const createdSummaries: InstinctChangeSummary[] = [];
|
|
330
|
+
const updatedSummaries: InstinctChangeSummary[] = [];
|
|
331
|
+
const deletedSummaries: InstinctChangeSummary[] = [];
|
|
238
332
|
const projectInstinctsDir = getProjectInstinctsDir(project.id, "personal", baseDir);
|
|
239
333
|
const globalInstinctsDir = getGlobalInstinctsDir("personal", baseDir);
|
|
240
334
|
|
|
@@ -243,6 +337,10 @@ async function analyzeProject(
|
|
|
243
337
|
const result = await runSingleShot(context, model, apiKey, abortController.signal);
|
|
244
338
|
singleShotMessage = result.message;
|
|
245
339
|
|
|
340
|
+
// Enforce creation rate limit: only the first N create actions per run are applied.
|
|
341
|
+
const maxNewInstincts = config.max_new_instincts_per_run ?? DEFAULT_CONFIG.max_new_instincts_per_run;
|
|
342
|
+
let createsRemaining = maxNewInstincts;
|
|
343
|
+
|
|
246
344
|
for (const change of result.changes) {
|
|
247
345
|
if (change.action === "delete") {
|
|
248
346
|
const id = change.id;
|
|
@@ -252,21 +350,47 @@ async function analyzeProject(
|
|
|
252
350
|
if (existsSync(filePath)) {
|
|
253
351
|
unlinkSync(filePath);
|
|
254
352
|
instinctCounts.deleted++;
|
|
353
|
+
deletedSummaries.push({
|
|
354
|
+
id,
|
|
355
|
+
title: id,
|
|
356
|
+
scope: change.scope ?? "project",
|
|
357
|
+
});
|
|
255
358
|
}
|
|
256
|
-
} else {
|
|
257
|
-
//
|
|
359
|
+
} else if (change.action === "create") {
|
|
360
|
+
if (createsRemaining <= 0) continue; // rate limit reached
|
|
258
361
|
const existing = allInstincts.find((i) => i.id === change.instinct?.id) ?? null;
|
|
259
|
-
const instinct = buildInstinctFromChange(change, existing, project.id);
|
|
362
|
+
const instinct = buildInstinctFromChange(change, existing, project.id, allInstincts);
|
|
260
363
|
if (!instinct) continue;
|
|
261
364
|
|
|
262
365
|
const dir = instinct.scope === "global" ? globalInstinctsDir : projectInstinctsDir;
|
|
263
366
|
saveInstinct(instinct, dir);
|
|
367
|
+
instinctCounts.created++;
|
|
368
|
+
createsRemaining--;
|
|
369
|
+
createdSummaries.push({
|
|
370
|
+
id: instinct.id,
|
|
371
|
+
title: instinct.title,
|
|
372
|
+
scope: instinct.scope,
|
|
373
|
+
trigger: instinct.trigger,
|
|
374
|
+
action: instinct.action,
|
|
375
|
+
});
|
|
376
|
+
} else {
|
|
377
|
+
// update
|
|
378
|
+
const existing = allInstincts.find((i) => i.id === change.instinct?.id) ?? null;
|
|
379
|
+
const instinct = buildInstinctFromChange(change, existing, project.id, allInstincts);
|
|
380
|
+
if (!instinct) continue;
|
|
264
381
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
382
|
+
const dir = instinct.scope === "global" ? globalInstinctsDir : projectInstinctsDir;
|
|
383
|
+
saveInstinct(instinct, dir);
|
|
384
|
+
instinctCounts.updated++;
|
|
385
|
+
const delta = existing
|
|
386
|
+
? instinct.confidence - existing.confidence
|
|
387
|
+
: undefined;
|
|
388
|
+
updatedSummaries.push({
|
|
389
|
+
id: instinct.id,
|
|
390
|
+
title: instinct.title,
|
|
391
|
+
scope: instinct.scope,
|
|
392
|
+
...(delta !== undefined ? { confidence_delta: delta } : {}),
|
|
393
|
+
});
|
|
270
394
|
}
|
|
271
395
|
}
|
|
272
396
|
} finally {
|
|
@@ -296,9 +420,27 @@ async function analyzeProject(
|
|
|
296
420
|
|
|
297
421
|
logger.projectComplete(stats);
|
|
298
422
|
|
|
423
|
+
// Write analysis event for extension notification
|
|
424
|
+
const analysisEvent: AnalysisEvent = {
|
|
425
|
+
timestamp: new Date().toISOString(),
|
|
426
|
+
project_id: project.id,
|
|
427
|
+
project_name: project.name,
|
|
428
|
+
created: createdSummaries,
|
|
429
|
+
updated: updatedSummaries,
|
|
430
|
+
deleted: deletedSummaries,
|
|
431
|
+
};
|
|
432
|
+
appendAnalysisEvent(analysisEvent, baseDir);
|
|
433
|
+
|
|
299
434
|
saveProjectMeta(
|
|
300
435
|
project.id,
|
|
301
|
-
{
|
|
436
|
+
{
|
|
437
|
+
...meta,
|
|
438
|
+
last_analyzed_at: new Date().toISOString(),
|
|
439
|
+
last_observation_line_count: totalLineCount,
|
|
440
|
+
// Update AGENTS.md hashes only when the content was actually sent.
|
|
441
|
+
...(agentsMdProject && projectMdHash ? { agents_md_project_hash: projectMdHash } : {}),
|
|
442
|
+
...(agentsMdGlobal && globalMdHash ? { agents_md_global_hash: globalMdHash } : {}),
|
|
443
|
+
},
|
|
302
444
|
baseDir
|
|
303
445
|
);
|
|
304
446
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command scaffolding from instinct clusters.
|
|
3
|
+
*
|
|
4
|
+
* When 3+ related instincts in the same domain form an actionable workflow,
|
|
5
|
+
* generates a Pi slash command scaffold that codifies the pattern.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Instinct } from "./types.js";
|
|
9
|
+
import type { DomainCluster } from "./graduation.js";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Types
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export interface CommandScaffold {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
domain: string;
|
|
19
|
+
content: string;
|
|
20
|
+
sourceInstinctIds: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
function toCommandName(domain: string): string {
|
|
28
|
+
return domain.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatInstinctAsStep(instinct: Instinct, index: number): string {
|
|
32
|
+
return [
|
|
33
|
+
`${index + 1}. **${instinct.title}**`,
|
|
34
|
+
` - Trigger: ${instinct.trigger}`,
|
|
35
|
+
` - Action: ${instinct.action}`,
|
|
36
|
+
"",
|
|
37
|
+
].join("\n");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Public API
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generates a command scaffold from a domain cluster of instincts.
|
|
46
|
+
* The scaffold describes a slash command that encodes the workflow
|
|
47
|
+
* distilled from the instinct cluster.
|
|
48
|
+
*/
|
|
49
|
+
export function generateCommandScaffold(cluster: DomainCluster): CommandScaffold {
|
|
50
|
+
const name = toCommandName(cluster.domain);
|
|
51
|
+
const sortedInstincts = [...cluster.instincts].sort(
|
|
52
|
+
(a, b) => b.confidence - a.confidence
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const description = `Learned ${cluster.domain} workflow from coding sessions. ` +
|
|
56
|
+
`Encodes ${sortedInstincts.length} steps distilled from instinct observations.`;
|
|
57
|
+
|
|
58
|
+
const steps = sortedInstincts.map((inst, i) => formatInstinctAsStep(inst, i));
|
|
59
|
+
|
|
60
|
+
const content = [
|
|
61
|
+
`# /${name} Command`,
|
|
62
|
+
"",
|
|
63
|
+
`> Auto-generated from ${sortedInstincts.length} graduated instincts in the "${cluster.domain}" domain.`,
|
|
64
|
+
"",
|
|
65
|
+
`## Description`,
|
|
66
|
+
"",
|
|
67
|
+
description,
|
|
68
|
+
"",
|
|
69
|
+
`## Command: \`/${name}\``,
|
|
70
|
+
"",
|
|
71
|
+
`When invoked, this command should guide the agent through these steps:`,
|
|
72
|
+
"",
|
|
73
|
+
...steps,
|
|
74
|
+
`## Implementation Notes`,
|
|
75
|
+
"",
|
|
76
|
+
`Register this command in your Pi extension's \`index.ts\`:`,
|
|
77
|
+
"",
|
|
78
|
+
"```typescript",
|
|
79
|
+
`pi.registerCommand("${name}", {`,
|
|
80
|
+
` description: "${description.replace(/"/g, '\\"')}",`,
|
|
81
|
+
` handler: async (args, ctx) => {`,
|
|
82
|
+
` // TODO: Implement ${name} workflow`,
|
|
83
|
+
` },`,
|
|
84
|
+
`});`,
|
|
85
|
+
"```",
|
|
86
|
+
"",
|
|
87
|
+
].join("\n");
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
name,
|
|
91
|
+
description,
|
|
92
|
+
domain: cluster.domain,
|
|
93
|
+
content,
|
|
94
|
+
sourceInstinctIds: sortedInstincts.map((i) => i.id),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generates command scaffolds for all qualifying clusters.
|
|
100
|
+
*/
|
|
101
|
+
export function generateAllCommandScaffolds(
|
|
102
|
+
clusters: DomainCluster[]
|
|
103
|
+
): CommandScaffold[] {
|
|
104
|
+
return clusters.map(generateCommandScaffold);
|
|
105
|
+
}
|
package/src/confidence.ts
CHANGED
|
@@ -21,12 +21,19 @@ const OBS_BRACKET_MED_MAX = 5;
|
|
|
21
21
|
const OBS_BRACKET_HIGH_MAX = 10;
|
|
22
22
|
|
|
23
23
|
// adjustConfidence deltas
|
|
24
|
-
|
|
24
|
+
// Confirmation uses diminishing returns to prevent runaway confidence on trivially easy-to-confirm instincts.
|
|
25
|
+
const DELTA_CONFIRMED_TIER1 = 0.05; // 1st–3rd confirmation
|
|
26
|
+
const DELTA_CONFIRMED_TIER2 = 0.03; // 4th–6th confirmation
|
|
27
|
+
const DELTA_CONFIRMED_TIER3 = 0.01; // 7th+ confirmation
|
|
25
28
|
const DELTA_CONTRADICTED = -0.15;
|
|
26
29
|
const DELTA_INACTIVE = 0;
|
|
27
30
|
|
|
31
|
+
const CONFIRMED_TIER1_MAX = 3;
|
|
32
|
+
const CONFIRMED_TIER2_MAX = 6;
|
|
33
|
+
|
|
28
34
|
// applyPassiveDecay
|
|
29
|
-
|
|
35
|
+
// Increased from 0.02 to 0.05: at 0.5 confidence, reaches 0.1 in ~8 weeks instead of 20.
|
|
36
|
+
const DECAY_PER_WEEK = 0.05;
|
|
30
37
|
const MS_PER_WEEK = 7 * 24 * 60 * 60 * 1000;
|
|
31
38
|
|
|
32
39
|
// ---------------------------------------------------------------------------
|
|
@@ -35,6 +42,16 @@ const MS_PER_WEEK = 7 * 24 * 60 * 60 * 1000;
|
|
|
35
42
|
|
|
36
43
|
export type FeedbackOutcome = "confirmed" | "contradicted" | "inactive";
|
|
37
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Returns the confirmation confidence delta using diminishing returns.
|
|
47
|
+
* Higher confirmed_count yields smaller increments to prevent runaway scores.
|
|
48
|
+
*/
|
|
49
|
+
export function confirmationDelta(confirmedCount: number): number {
|
|
50
|
+
if (confirmedCount <= CONFIRMED_TIER1_MAX) return DELTA_CONFIRMED_TIER1;
|
|
51
|
+
if (confirmedCount <= CONFIRMED_TIER2_MAX) return DELTA_CONFIRMED_TIER2;
|
|
52
|
+
return DELTA_CONFIRMED_TIER3;
|
|
53
|
+
}
|
|
54
|
+
|
|
38
55
|
export interface ConfidenceResult {
|
|
39
56
|
confidence: number;
|
|
40
57
|
flaggedForRemoval: boolean;
|
|
@@ -70,18 +87,28 @@ export function initialConfidence(observationCount: number): number {
|
|
|
70
87
|
|
|
71
88
|
/**
|
|
72
89
|
* Adjusts confidence based on a feedback outcome from the observer loop.
|
|
90
|
+
* For "confirmed" outcomes, applies diminishing returns based on how many
|
|
91
|
+
* times the instinct has already been confirmed (higher count = smaller delta).
|
|
73
92
|
* Returns the clamped confidence and a flag indicating if removal is warranted.
|
|
93
|
+
*
|
|
94
|
+
* @param current - Current confidence value
|
|
95
|
+
* @param outcome - Feedback outcome type
|
|
96
|
+
* @param confirmedCount - Current confirmed_count (used for diminishing returns on confirmations)
|
|
74
97
|
*/
|
|
75
98
|
export function adjustConfidence(
|
|
76
99
|
current: number,
|
|
77
100
|
outcome: FeedbackOutcome,
|
|
101
|
+
confirmedCount = 0,
|
|
78
102
|
): ConfidenceResult {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
103
|
+
let delta: number;
|
|
104
|
+
if (outcome === "confirmed") {
|
|
105
|
+
delta = confirmationDelta(confirmedCount);
|
|
106
|
+
} else if (outcome === "contradicted") {
|
|
107
|
+
delta = DELTA_CONTRADICTED;
|
|
108
|
+
} else {
|
|
109
|
+
delta = DELTA_INACTIVE;
|
|
110
|
+
}
|
|
111
|
+
const raw = current + delta;
|
|
85
112
|
return toResult(raw);
|
|
86
113
|
}
|
|
87
114
|
|
package/src/config.ts
CHANGED
|
@@ -38,6 +38,34 @@ export const CONFIG_PATH = path.join(
|
|
|
38
38
|
"config.json"
|
|
39
39
|
);
|
|
40
40
|
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Graduation maturity criteria
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/** Minimum age in days before an instinct is eligible for graduation. */
|
|
46
|
+
export const GRADUATION_MIN_AGE_DAYS = 7;
|
|
47
|
+
|
|
48
|
+
/** Minimum confidence to qualify for graduation. */
|
|
49
|
+
export const GRADUATION_MIN_CONFIDENCE = 0.75;
|
|
50
|
+
|
|
51
|
+
/** Minimum confirmed_count to qualify for graduation. */
|
|
52
|
+
export const GRADUATION_MIN_CONFIRMED = 3;
|
|
53
|
+
|
|
54
|
+
/** Maximum contradicted_count allowed for graduation. */
|
|
55
|
+
export const GRADUATION_MAX_CONTRADICTED = 1;
|
|
56
|
+
|
|
57
|
+
/** Minimum related instincts in same domain to propose a skill scaffold. */
|
|
58
|
+
export const GRADUATION_SKILL_CLUSTER_SIZE = 3;
|
|
59
|
+
|
|
60
|
+
/** Minimum related instincts in same domain to propose a command scaffold. */
|
|
61
|
+
export const GRADUATION_COMMAND_CLUSTER_SIZE = 3;
|
|
62
|
+
|
|
63
|
+
/** Maximum instinct age in days before TTL cull (aggressive decay / deletion). */
|
|
64
|
+
export const GRADUATION_TTL_MAX_DAYS = 28;
|
|
65
|
+
|
|
66
|
+
/** Confidence threshold below which TTL-expired instincts are deleted outright. */
|
|
67
|
+
export const GRADUATION_TTL_CULL_CONFIDENCE = 0.3;
|
|
68
|
+
|
|
41
69
|
export const DEFAULT_CONFIG: Config = {
|
|
42
70
|
run_interval_minutes: 5,
|
|
43
71
|
min_observations_to_analyze: 20,
|
|
@@ -49,6 +77,12 @@ export const DEFAULT_CONFIG: Config = {
|
|
|
49
77
|
active_hours_start: 8,
|
|
50
78
|
active_hours_end: 23,
|
|
51
79
|
max_idle_seconds: 1800,
|
|
80
|
+
// Volume control defaults
|
|
81
|
+
max_total_instincts_per_project: 30,
|
|
82
|
+
max_total_instincts_global: 20,
|
|
83
|
+
max_new_instincts_per_run: 3,
|
|
84
|
+
flagged_cleanup_days: 7,
|
|
85
|
+
instinct_ttl_days: 28,
|
|
52
86
|
};
|
|
53
87
|
|
|
54
88
|
// ---------------------------------------------------------------------------
|
|
@@ -68,6 +102,12 @@ const PartialConfigSchema = Type.Partial(
|
|
|
68
102
|
active_hours_end: Type.Number(),
|
|
69
103
|
max_idle_seconds: Type.Number(),
|
|
70
104
|
log_path: Type.String(),
|
|
105
|
+
// Volume control
|
|
106
|
+
max_total_instincts_per_project: Type.Number(),
|
|
107
|
+
max_total_instincts_global: Type.Number(),
|
|
108
|
+
max_new_instincts_per_run: Type.Number(),
|
|
109
|
+
flagged_cleanup_days: Type.Number(),
|
|
110
|
+
instinct_ttl_days: Type.Number(),
|
|
71
111
|
})
|
|
72
112
|
);
|
|
73
113
|
|