akm-cli
Version:
akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur
76 lines (75 loc) • 3.61 kB
JavaScript
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
/**
* MemRL feedback → utility policy, extracted from indexer/db/db.ts.
*
* This is the domain/policy math (arXiv:2601.03192) that decides how a batch of
* positive/negative feedback signals moves an asset's utility score. It is pure
* — no database access — so the bounded-step behaviour is unit-testable in
* isolation; the DB read/write stays with `applyFeedbackToUtilityScore` in db.ts.
*/
/**
* MemRL learning rate for feedback-driven utility updates (F-5 / #386).
*
* Follows the bounded-step formula from MemRL (arXiv:2601.03192):
* next = clamp(current + lr × (reward − current), 0, 1)
*
* This replaces the unbounded `-0.03 × negativeCount` delta that could
* silently remove high-utility assets from the improvement loop.
*/
export const FEEDBACK_LR = 0.1;
/**
* Positive reward signal for a single positive feedback event.
* Reward 1.0 means "fully correct / helpful".
*/
const FEEDBACK_REWARD_POSITIVE = 1.0;
/**
* Negative reward signal for a single negative feedback event.
* Reward 0.0 means "not helpful" (lowest MemRL signal).
*/
const FEEDBACK_REWARD_NEGATIVE = 0.0;
/**
* Utility threshold below which a review-needed escalation is triggered.
* When a previously high-utility asset (≥ HIGH_UTILITY_THRESHOLD) drops
* below this value, the caller should create an escalation proposal.
*/
export const UTILITY_REVIEW_THRESHOLD = 0.5;
/**
* Utility level considered "high" — assets above this are tracked for
* threshold-crossing escalation.
*/
export const HIGH_UTILITY_THRESHOLD = 0.5;
/**
* Compute the next utility from accumulated feedback counts using the MemRL
* bounded-step EMA formula (F-5 / #386, arXiv:2601.03192):
*
* reward = weighted average of positive and negative signals
* nextUtil = clamp(currentUtil + lr × (reward − currentUtil), 0, 1)
*
* The step is inherently bounded: reward ∈ [0, 1] and currentUtil ∈ [0, 1], so
* a single call moves utility by at most {@link FEEDBACK_LR} in either
* direction. `reward` is a proportion of the counts, not their magnitude, so
* with no positive signals the number of negatives is irrelevant (reward is 0
* whether there is 1 negative or 100). Mixing in positives shifts reward and so
* the step, but never past the learning-rate bound.
*
* Pure: no DB access. When both counts are zero, utility is unchanged.
*/
export function computeNextUtility(previousUtility, positiveCount, negativeCount) {
if (positiveCount === 0 && negativeCount === 0) {
return { previousUtility, nextUtility: previousUtility, crossedReviewThreshold: false };
}
const total = positiveCount + negativeCount;
// Weighted reward: proportion of positive signals.
const reward = positiveCount > 0 && negativeCount === 0
? FEEDBACK_REWARD_POSITIVE
: negativeCount > 0 && positiveCount === 0
? FEEDBACK_REWARD_NEGATIVE
: (positiveCount * FEEDBACK_REWARD_POSITIVE + negativeCount * FEEDBACK_REWARD_NEGATIVE) / total;
// MemRL bounded-step EMA: lr × (reward − current). |delta| ≤ FEEDBACK_LR.
const delta = FEEDBACK_LR * (reward - previousUtility);
const nextUtility = Math.max(0, Math.min(1, previousUtility + delta));
const crossedReviewThreshold = previousUtility >= HIGH_UTILITY_THRESHOLD && nextUtility < UTILITY_REVIEW_THRESHOLD;
return { previousUtility, nextUtility, crossedReviewThreshold };
}