UNPKG

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

102 lines (101 loc) • 4.97 kB
// 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/. import { getAllEntries } from "../../storage/repositories/index-entries-repository.js"; import { getUtilityScoresByIds } from "../../storage/repositories/index-utility-repository.js"; import { WARM_START_CAP } from "./outcome-loop.js"; const DAY_MS = 86_400_000; const RECENCY_HALFLIFE_DAYS = 21; /** * The recency floor itself halves every 180 days, so an asset nobody uses keeps * drifting down instead of parking at the floor. */ const RECENCY_FLOOR_HALFLIFE_DAYS = 180; /** Keeps frequency ordinal for assets whose last use is unknown. */ const RECENCY_EPSILON = 0.01; const SIZE_FLOOR_BYTES = 200; /** Default weights: the outcome term is on (opt out with `improve.salience.outcomeWeightEnabled: false`). */ export const W_ENCODING = 0.25; export const W_OUTCOME = 0.15; export const W_RETRIEVAL = 0.6; /** Weights with the outcome term off. */ export const W_ENCODING_PARITY = 0.3; export const W_OUTCOME_PARITY = 0; export const W_RETRIEVAL_PARITY = 0.7; /** Encoding salience by type, for assets with no content-derived score yet. */ export const DEFAULT_TYPE_ENCODING_WEIGHTS = Object.freeze({ skill: 0.9, agent: 0.9, command: 0.8, workflow: 0.8, lesson: 0.75, knowledge: 0.7, script: 0.6, memory: 0.5, }); export const DEFAULT_ENCODING_SALIENCE = 0.5; const clamp01 = (value) => Math.min(1, Math.max(0, value)); /** The salience vector for one asset (pure). */ export function computeSalience(inputs) { const now = inputs.now ?? Date.now(); const encodingSource = inputs.encodingSalience !== undefined ? "content" : "type-stub"; const encoding = inputs.encodingSalience !== undefined ? clamp01(inputs.encodingSalience) : (DEFAULT_TYPE_ENCODING_WEIGHTS[inputs.type] ?? DEFAULT_ENCODING_SALIENCE); // Without an outcome row, utility (capped) seeds the outcome term — the same // seed the first outcome row gets — so it is not zero at launch. const outcome = inputs.outcomeSalience !== undefined ? clamp01(inputs.outcomeSalience) : Math.min(WARM_START_CAP, Math.max(0, inputs.utilityScore ?? 0)); // log(1 + freq) × recency, where recency halves every 21 days above a floor // that itself halves every 180 days; soft-capped to [0, 1). const lastUseMs = inputs.lastUseMs ?? 0; const useAgeDays = lastUseMs > 0 ? (now - lastUseMs) / DAY_MS : 9999; const recencyDecay = Math.max(RECENCY_EPSILON, 0.1 * 0.5 ** (useAgeDays / RECENCY_FLOOR_HALFLIFE_DAYS) + 0.5 ** (useAgeDays / RECENCY_HALFLIFE_DAYS)); const rawRetrieval = Math.log(1 + inputs.retrievalFreq) * recencyDecay; const retrieval = rawRetrieval / (rawRetrieval + 1); // Larger assets rank slightly lower. const sizePenalty = 1 / Math.log10(Math.max(SIZE_FLOOR_BYTES, inputs.sizeBytes ?? 0)); const [we, wo, wr] = inputs.outcomeWeightEnabled !== false ? [W_ENCODING, W_OUTCOME, W_RETRIEVAL] : [W_ENCODING_PARITY, W_OUTCOME_PARITY, W_RETRIEVAL_PARITY]; const rankScore = clamp01((we * encoding + wo * outcome + wr * retrieval) * sizePenalty); return { encoding, outcome, retrieval, rankScore, encodingSource }; } export { getAssetSalience, getConsecutiveNoOps, recordNoOp, resetConsecutiveNoOps, upsertAssetSalience, } from "../../storage/repositories/salience-repository.js"; /** Whether a stored row's encoding salience is content-derived (unknown provenance is not). */ export function isContentEncodingRow(row) { return row.encoding_source === "content"; } /** * Consolidation selection only: an asset whose last three runs were no-ops * sorts at half its score. The stored rank is untouched. */ export const SALIENCE_NO_OP_DAMPEN_THRESHOLD = 3; export const SALIENCE_NO_OP_DAMPEN_FACTOR = 0.5; /** `ref → last retrieval (ms)` from the index's utility scores; absent means never retrieved. */ export function getLastUseMsByRef(indexDb, candidates) { const result = new Map(); if (candidates.length === 0) return result; const refByItemRef = new Map(candidates.flatMap((candidate) => (candidate.itemRef ? [[candidate.itemRef, candidate.ref]] : []))); const idToRef = new Map(); for (const indexed of getAllEntries(indexDb)) { const ref = refByItemRef.get(indexed.itemRef); if (ref) idToRef.set(indexed.id, ref); } if (idToRef.size === 0) return result; const scores = getUtilityScoresByIds(indexDb, [...idToRef.keys()]); for (const [id, row] of scores) { const ref = idToRef.get(id); const lastUsedAt = row.lastUsedAt; if (!ref || !lastUsedAt) continue; const ms = typeof lastUsedAt === "number" ? lastUsedAt : Date.parse(lastUsedAt); if (ms > 0) result.set(ref, ms); } return result; }