mirror of
https://github.com/lukaszraczylo/claude-adam.git
synced 2026-06-08 23:09:16 +00:00
feat(adam): smarter signals & clustering
- New signal types in hooks/adam-observe.mjs: - silent_drift: 5 consecutive read-only PostToolUse without an action tool - error_after_recovery: same error fingerprint returns within 5 events of clean_recovery - Severity-weighted scoring in adam/scripts/adam-score.mjs: - SEVERITY_DIVISORS exported per struggle signal type - Per-session severity_sum + severity_by_type added to JSON output - Skill-attribution clustering in agents/adam.md: - Sub-cluster struggle signals on active_skills[0] - New struggle-driven skill_edit variant (always queues, never auto-applies) - Rubric updates: - +1 for cluster severity-sum >= 10, additional +1 for >= 32 - +1 for skill-attributed sub-cluster naming an existing skill - silent_drift + error_after_recovery added to struggle signal list - Window: silent_drift 14d, error_after_recovery 30d - Tests: 94 passing (78-82 new) Backward compat: entries without count default to severity 1. Existing win-driven skill_edit gate untouched. No journal migration.
This commit is contained in:
@@ -43,10 +43,32 @@ export const NEGATIVE_SIGNAL_TYPES = new Set([
|
||||
"retry_loop",
|
||||
"build_loop",
|
||||
"weak_agent",
|
||||
"silent_drift",
|
||||
"error_after_recovery",
|
||||
]);
|
||||
|
||||
export const REINFORCEMENT_THRESHOLD = 3;
|
||||
|
||||
// Severity divisor per struggle signal type. Severity = max(1, floor(count / divisor)).
|
||||
// Entries without `count` default to severity 1. Source of truth — referenced by
|
||||
// agents/adam.md (Confidence rubric → severity-sum bullets).
|
||||
export const SEVERITY_DIVISORS = {
|
||||
dead_end: 8,
|
||||
edit_churn: 4,
|
||||
tool_error_loop: 3,
|
||||
retry_loop: 3,
|
||||
weak_agent: 2,
|
||||
build_loop: 1,
|
||||
};
|
||||
|
||||
export function entrySeverity(entry) {
|
||||
if (!entry || typeof entry !== "object") return 1;
|
||||
const divisor = SEVERITY_DIVISORS[entry.type];
|
||||
if (!divisor) return 1;
|
||||
const count = typeof entry.count === "number" && entry.count > 0 ? entry.count : 1;
|
||||
return Math.max(1, Math.floor(count / divisor));
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = { home: null, input: null, help: false };
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
@@ -84,11 +106,22 @@ export function computeSessionScores(entries) {
|
||||
const sid = e.session || e.session_id || "";
|
||||
if (!sid) continue;
|
||||
if (!bySession.has(sid)) {
|
||||
bySession.set(sid, { session_id: sid, negative_count: 0, task_completed_count: 0 });
|
||||
bySession.set(sid, {
|
||||
session_id: sid,
|
||||
negative_count: 0,
|
||||
task_completed_count: 0,
|
||||
severity_sum: 0,
|
||||
severity_by_type: {},
|
||||
});
|
||||
}
|
||||
const slot = bySession.get(sid);
|
||||
if (e.type === "task_completed") slot.task_completed_count++;
|
||||
else if (NEGATIVE_SIGNAL_TYPES.has(e.type)) slot.negative_count++;
|
||||
else if (NEGATIVE_SIGNAL_TYPES.has(e.type)) {
|
||||
slot.negative_count++;
|
||||
const sev = entrySeverity(e);
|
||||
slot.severity_sum += sev;
|
||||
slot.severity_by_type[e.type] = (slot.severity_by_type[e.type] || 0) + sev;
|
||||
}
|
||||
}
|
||||
const out = [];
|
||||
for (const slot of bySession.values()) {
|
||||
|
||||
@@ -29,6 +29,8 @@ export const SIGNAL_WINDOWS_DAYS = {
|
||||
build_loop: 30,
|
||||
weak_agent: 30,
|
||||
subagent_dispatch_pattern: 30,
|
||||
silent_drift: 14,
|
||||
error_after_recovery: 30,
|
||||
correction_free_streak: 60,
|
||||
clean_recovery: 60,
|
||||
task_completed: 60,
|
||||
|
||||
Reference in New Issue
Block a user