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
871 lines (870 loc) • 39.5 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/.
/**
* `akm distill <ref>` — distil an asset and its feedback into a lesson (or,
* for a reinforced memory, a knowledge) proposal. One bounded LLM call, then
* the shared judge → mint path in `./stage`; the proposal queue is the only
* way to a live asset. Every invocation emits one `distill_invoked` event
* carrying its `outcome` (config-disabled runs emit none).
*
* Lesson refs: a nested input keeps its first scope segment
* (`memories/project-a/deploy` → `lessons/project-a/memory-deploy-lesson`); an
* unscoped input stays flat.
*/
import fs from "node:fs";
import distillKnowledgeSystemPrompt from "../../assets/prompts/distill-knowledge-system.md" with { type: "text" };
import distillLessonSystemPrompt from "../../assets/prompts/distill-lesson-system.md" with { type: "text" };
import { assembleAsset, assembleAssetFromString, serializeFrontmatterQuoted } from "../../core/asset/asset-serialize.js";
import { parseFrontmatter, writeSalienceToFrontmatter } from "../../core/asset/frontmatter.js";
import { stripMarkdownFences } from "../../core/asset/markdown.js";
import { conceptIdFromTypeName, parseRefInput } from "../../core/asset/resolve-ref.js";
import { authoringRulesForType } from "../../core/authoring-rules.js";
import { isWithin, resolveStashDir } from "../../core/common.js";
import { getImproveProcessConfig, loadConfig } from "../../core/config/config.js";
import { UsageError } from "../../core/errors.js";
import { appendEvent, readEvents } from "../../core/events.js";
import { lintLessonContent } from "../../core/lesson-lint.js";
import { parseEmbeddedJsonResponse } from "../../core/parse.js";
import { getDbPath } from "../../core/paths.js";
import { resolveStandardsContext } from "../../core/standards/resolve-standards-context.js";
import { withStateDb } from "../../core/state-db.js";
import { warn, warnVerbose } from "../../core/warn.js";
import { recordWrittenPath } from "../../core/write-provenance.js";
import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
import { assertRunnerCredentials } from "../../integrations/agent/runner-dispatch.js";
import { closeDatabase, openReadonlyExistingDatabase } from "../../storage/repositories/index-connection.js";
import { getAllEntries } from "../../storage/repositories/index-entries-repository.js";
import { listProposals } from "../proposal/repository.js";
import { detectDoubleFrontmatter, isValidDescription } from "../proposal/validators/proposal-quality-validators.js";
import { akmSearch } from "../read/search.js";
import { stripFrontmatterBody } from "./content-hash.js";
import { autoRepairLessonFrontmatter, autoSwapDescriptionWhenToUse, collectLessonQualityFindings, repairLessonDescriptionTruncation, } from "./distill/content-repair.js";
import { buildClsContext, checkDistillFidelity, DEFAULT_CLS_ADJACENT_COUNT } from "./distill-guards.js";
import { assessMemoryKnowledgePromotionCandidate, deriveKnowledgeRef } from "./distill-promotion-policy.js";
import { buildRefVocabulary, scoreEncodingSalience } from "./encoding-salience.js";
import { resolveImproveStrategy, resolveProcessEnabled } from "./improve-strategies.js";
import { recordLedgerAttempt } from "./ledger.js";
import { computeSalience, upsertAssetSalience } from "./salience.js";
import { callStage, mintProposal, noticeSet, rejectedProposalContext, resolveQualityGateJudge, runLessonQualityJudge, stageRunner, } from "./stage.js";
/**
* Input types distill structurally refuses: a lesson is the distilled form
* (distilling one would mint `lessons/lesson-…-lesson`), and env/secret bytes
* must never reach the model. The improve planner skips these before queuing.
*/
export const DISTILL_REFUSED_INPUT_TYPES = new Set(["lesson", "env", "secret"]);
export function isDistillRefusedInputType(type) {
return DISTILL_REFUSED_INPUT_TYPES.has(type);
}
/** Derive the proposed lesson ref from the input ref. */
export function deriveLessonRef(inputRef) {
const parsed = parseRefInput(inputRef);
const parts = parsed.name.split("/");
const scope = parts.length > 1 ? parts.shift() : undefined;
const clean = (value) => value
.toLowerCase()
.replace(/[^a-z0-9-]+/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
const safeScope = scope ? clean(scope) : "";
return `lessons/${safeScope ? `${safeScope}/` : ""}${clean(`${parsed.type}-${parts.join("-")}`)}-lesson`;
}
// ── Output contract ──────────────────────────────────────────────────────────
export const DISTILL_LESSON_JSON_SCHEMA = {
type: "object",
required: ["description", "when_to_use", "body"],
additionalProperties: false,
properties: {
description: {
type: "string",
minLength: 10,
description: "Single complete sentence (80-200 chars) summarising what the lesson teaches. No markdown, no leading 'When'/'If'.",
},
when_to_use: {
type: "string",
minLength: 10,
description: "Single complete sentence describing the concrete trigger condition for the lesson.",
},
body: {
type: "string",
minLength: 1,
description: "Lesson body — plain markdown, 1-3 short paragraphs of practical guidance.",
},
tags: {
type: "array",
items: { type: "string" },
description: "Optional tag list. Empty array is allowed; the post-processor drops it if empty.",
},
},
};
export const DISTILL_KNOWLEDGE_JSON_SCHEMA = {
type: "object",
required: ["description", "body"],
additionalProperties: false,
properties: {
description: { type: "string", minLength: 1, description: "One-line summary of the knowledge asset." },
body: {
type: "string",
minLength: 1,
description: "Knowledge body — structured markdown with a `# Title` heading and durable facts only.",
},
tags: {
type: "array",
items: { type: "string" },
description: "Optional tag list. Empty array is allowed; the post-processor drops it if empty.",
},
sources: {
type: "array",
items: { type: "string" },
description: "Optional list of source refs the knowledge was distilled from.",
},
},
};
/**
* Assemble markdown from a structured-output payload, or `null` when a
* required field is empty (the caller then treats the response as markdown).
*/
export function assembleStructuredDistillMarkdown(payload, kind) {
if (payload === null || typeof payload !== "object")
return null;
const text = (value) => (typeof value === "string" ? value.trim() : "");
const list = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string" && v.trim().length > 0) : [];
const description = text(payload.description);
const body = text(payload.body);
if (!description || !body)
return null;
const fm = { description };
if (kind === "lesson") {
const whenToUse = text(payload.when_to_use);
if (!whenToUse)
return null;
fm.when_to_use = whenToUse;
}
const tags = list(payload.tags);
if (tags.length > 0)
fm.tags = tags;
const sources = kind === "knowledge" ? list(payload.sources) : [];
if (sources.length > 0)
fm.xrefs = sources;
return assembleAssetFromString(serializeFrontmatterQuoted(fm), body);
}
function validateKnowledgeContent(content, inputRef) {
const findings = [];
const parsed = parseFrontmatter(content);
if (parsed.content.trim().length === 0) {
findings.push({
kind: "missing-body",
field: "body",
message: `Distilled knowledge for ${inputRef} must include a non-empty markdown body.`,
});
}
// A present description must be a real summary (not `---` or a heading fragment).
const description = parsed.data?.description;
if (description !== undefined) {
const check = isValidDescription(description, inputRef, { skipRefTailCheck: true });
if (!check.ok) {
findings.push({
kind: "invalid-description",
field: "description",
message: `Distilled knowledge for ${inputRef} has an invalid description: ${check.reason}.`,
});
}
}
const doubled = detectDoubleFrontmatter(content);
if (doubled) {
findings.push({
kind: doubled.kind,
field: "body",
message: `Distilled knowledge for ${inputRef}: ${doubled.message}`,
});
}
return findings;
}
/**
* The distill user prompt. Feedback is rendered as "What worked" / "What
* failed" contrast when it carries signals, else as a flat event list.
*/
export function buildDistillPrompt(input) {
const lines = [`Asset ref: ${input.inputRef}`, ""];
if (input.standardsContext?.trim()) {
lines.push("Standards to follow (the rulebook for this target):", input.standardsContext.trim(), "");
}
const authoringRules = authoringRulesForType(input.proposalKind ?? "lesson");
if (authoringRules)
lines.push(authoringRules, "");
lines.push("Asset content:");
if (input.assetContent) {
// Source frontmatter is not evidence; fed verbatim, models copied it into the body.
lines.push("```", parseFrontmatter(input.assetContent).content.trim().slice(0, 3000), "```");
}
else {
lines.push("(asset is not currently indexed; distil from feedback signal alone)");
}
lines.push("");
const flat = (event) => `- ${event.ts} ${event.eventType}${event.metadata ? ` ${JSON.stringify(event.metadata)}` : ""}`;
if (input.feedback.length === 0) {
lines.push("Recent feedback: (no feedback events recorded — distil from the asset itself)");
}
else {
const worked = [];
const failed = [];
const other = [];
for (const event of input.feedback) {
const meta = event.metadata ?? {};
const detail = (typeof meta.reason === "string" ? meta.reason : "") || (typeof meta.note === "string" ? meta.note : "");
const line = `- ${event.ts}: ${detail || "feedback received"}`;
if (meta.signal === "positive")
worked.push(line);
else if (meta.signal === "negative")
failed.push(line);
else
other.push(flat(event));
}
if (worked.length > 0 || failed.length > 0) {
for (const [heading, section] of [
["## What worked", worked],
["## What failed", failed],
["## Other signals", other],
]) {
if (section.length > 0)
lines.push(heading, ...section, "");
}
}
else {
lines.push("Recent feedback events (most recent last):", ...input.feedback.map(flat), "");
}
}
if (input.rejectedProposals && input.rejectedProposals.length > 0) {
lines.push("", "Previously rejected proposals for this ref (Reflexion context):", "The following proposals were already reviewed and rejected. " +
"Your new proposal MUST differ meaningfully in approach, framing, or evidence.");
for (const rp of input.rejectedProposals) {
lines.push(`- Rejection reason: ${rp.reason}`);
if (rp.contentPreview)
lines.push(` Content preview: ${rp.contentPreview.slice(0, 200).replace(/\n/g, " ")}`);
}
}
lines.push(input.proposalKind === "knowledge"
? "Produce the knowledge markdown file now. Start your response with `---` on the first line, followed by a `description:` field whose value is a 1-sentence summary (20–400 chars). Never use placeholder values like `---`, `tbd`, `n/a`, or a single dash. If the source has nothing meaningful to summarize, do NOT produce a proposal — return an empty response instead. The frontmatter block ends with a second `---` line; do not emit any additional `---` fences in the body."
: "Produce the lesson markdown file now. Start your response with `---` on the first line, followed by `description:` and `when_to_use:` fields. Both must be real one-sentence summaries (20–400 chars) — never placeholder values like `---`, `tbd`, or `n/a`. The frontmatter block ends with a second `---` line; do not emit any additional `---` fences in the body.");
return lines.join("\n");
}
// ── Invocation ───────────────────────────────────────────────────────────────
const DISABLED_MESSAGE = "distill is disabled in config; enable processes.distill.enabled to activate.";
function emitDistill(run, meta) {
appendEvent({ eventType: "distill_invoked", ref: run.ledgerRef, metadata: { ...meta, ...run.eligMeta } }, run.options.eventsCtx);
}
/** The exclusion diagnostics for an event (count only) or a result (count + fully-filtered). */
function exclusionMeta(run, forResult) {
if (!run.exclusion)
return {};
return forResult ? { ...run.exclusion } : { filteredFeedbackCount: run.exclusion.filteredFeedbackCount };
}
export async function akmDistill(options) {
const inputRef = options.ref.trim();
if (!inputRef) {
throw new UsageError("Asset ref is required. Usage: akm distill <ref>", "MISSING_REQUIRED_ARGUMENT");
}
const parsedInputRef = parseRefInput(inputRef);
const config = options.config ?? loadConfig();
const profile = options.improveProfile ?? resolveImproveStrategy(undefined, config).config;
options = { ...options, improveProfile: profile };
const targetKind = options.proposalKind ?? "lesson";
const kind = targetKind === "knowledge" ? "knowledge" : "lesson";
const outputRef = kind === "knowledge" ? deriveKnowledgeRef(inputRef) : deriveLessonRef(inputRef);
if (!resolveProcessEnabled("distill", profile)) {
return {
schemaVersion: 1,
ok: true,
outcome: "config_disabled",
inputRef,
proposalRef: outputRef,
proposalKind: kind,
message: DISABLED_MESSAGE,
};
}
const ledgerRef = options.itemRef ?? inputRef;
const eligMeta = options.eligibilitySource ? { eligibilitySource: options.eligibilitySource } : {};
if (isDistillRefusedInputType(parsedInputRef.type)) {
const secret = parsedInputRef.type === "env" || parsedInputRef.type === "secret";
const proposalRef = secret ? inputRef : conceptIdFromTypeName("lesson", parsedInputRef.name);
const skipReason = secret ? "refused_secret_input" : "recursive_lesson_input";
const message = secret
? `Distill refuses ${parsedInputRef.type} inputs — secret material must never be sent to the LLM.`
: "Distill refuses lesson inputs — lessons are the distilled form, not a source.";
emitDistill({ ledgerRef, eligMeta, options }, { outcome: "skipped", proposalRef, message, skipReason });
return { schemaVersion: 1, ok: true, outcome: "skipped", inputRef, proposalRef, skipReason, message };
}
const stash = options.stashDir ?? resolveStashDir();
const notices = noticeSet();
const lookup = options.lookupFn ?? ((ref) => defaultLookup(ref, stash));
const asset = await loadInput(lookup, inputRef);
const run = {
options,
inputRef,
ledgerRef,
stash,
config,
profile,
runner: stageRunner(options, config, profile, "distill", notices.add),
judgeRunner: resolveQualityGateJudge(config, profile, "distill", notices.add),
notices,
eligMeta,
asset,
vocabulary: loadRefVocabulary(),
outcomeWeightEnabled: config.improve?.salience?.outcomeWeightEnabled !== false,
similar: options.fetchSimilarLessonsFn ?? fetchTopSimilarLessons,
lookup,
};
const feedbackEvents = readDistillFeedback(run);
const result = await distill(run, targetKind, kind, outputRef, feedbackEvents);
return { ...result, ...notices.fields() };
}
async function distill(run, targetKind, kind, outputRef, feedbackEvents) {
// A reinforced memory graduates to knowledge without a generation call.
const promotion = targetKind === "lesson" ? null : await planPromotion(run, feedbackEvents);
if (promotion) {
if (run.runner && promotion.existing)
assertRunnerCredentials(run.runner);
const judge = run.judgeRunner ?? run.runner;
if (judge && qualityGateEnabled(run))
assertRunnerCredentials(judge);
const promoted = await promoteToKnowledge(run, promotion);
stampInputSalience(run);
return promoted;
}
const feedback = feedbackEvents.slice(-20).map((event) => ({
ts: event.ts,
eventType: event.eventType,
...(event.metadata !== undefined ? { metadata: event.metadata } : {}),
}));
const { system, prompt } = await buildDistillMessages(run, feedback, kind, outputRef);
const call = run.runner
? await callStage({
feature: "distill",
runner: run.runner,
system,
prompt,
gate: { config: run.config, enabled: true },
// The injected test transport never sees the schema.
request: {
...(run.options.chat === undefined
? { responseSchema: kind === "knowledge" ? DISTILL_KNOWLEDGE_JSON_SCHEMA : DISTILL_LESSON_JSON_SCHEMA }
: { chat: run.options.chat }),
...(run.options.signal ? { signal: run.options.signal } : {}),
},
onNotices: run.notices.add,
})
: { ok: false, reason: "error" };
// Durable input salience waits until the credential-bearing dispatch returned.
stampInputSalience(run);
if (!call.ok || call.raw.trim() === "") {
if (!call.ok)
warnVerbose(`[akm] LLM fallback for distill: ${call.reason}`);
emitDistill(run, {
outcome: "llm_failed",
proposalRef: outputRef,
proposalKind: kind,
...exclusionMeta(run, false),
});
return {
schemaVersion: 1,
ok: true,
outcome: "llm_failed",
inputRef: run.inputRef,
proposalRef: outputRef,
proposalKind: kind,
message: "LLM call returned no usable output (timeout, empty, or error).",
...exclusionMeta(run, true),
};
}
const assembled = assembleDistilledContent(run, call.raw, kind, outputRef);
if ("rejection" in assembled)
return assembled.rejection;
return judgeAndQueue(run, {
ref: outputRef,
kind,
content: assembled.content,
source: run.asset.content,
descriptionSwapped: assembled.descriptionSwapped,
});
}
/** Turn the response into validated content: structured JSON or markdown, then lesson repairs and lint. */
function assembleDistilledContent(run, raw, kind, outputRef) {
const structured = parseEmbeddedJsonResponse(raw);
let content = (structured && !Array.isArray(structured) ? assembleStructuredDistillMarkdown(structured, kind) : null) ??
stripMarkdownFences(raw);
let descriptionSwapped = 0;
if (kind === "lesson") {
content = autoRepairLessonFrontmatter(content, run.inputRef);
({ content, swapped: descriptionSwapped } = autoSwapDescriptionWhenToUse(content, run.inputRef));
content = repairLessonDescriptionTruncation(content);
}
// Required structure missing means there is no asset to write: a hard reject.
const structural = kind === "knowledge"
? validateKnowledgeContent(content, run.inputRef)
: lintLessonContent(content, `distill:${run.inputRef}`).findings;
if (structural.length > 0) {
emitDistill(run, {
outcome: "validation_failed",
proposalRef: outputRef,
proposalKind: kind,
findingKinds: structural.map((f) => f.kind),
...exclusionMeta(run, false),
});
throw new UsageError(`Distilled ${kind} failed validation:\n${structural.map((f) => f.message).join("\n")}`, "MISSING_REQUIRED_ARGUMENT", kind === "knowledge"
? "Knowledge proposals require a non-empty markdown body."
: "Lessons require non-empty `description` and `when_to_use` frontmatter fields. See v1 spec §13.");
}
// Heuristic quality findings go to a human, not the bin.
const quality = kind === "lesson" ? collectLessonQualityFindings(content, run.inputRef) : [];
if (quality.length > 0) {
return {
rejection: rejectDistilled(run, outputRef, content, 2.0, quality.map((f) => f.message).join("\n"), {
reviewNeeded: true,
proposalKind: kind,
findingKinds: quality.map((f) => f.kind),
}),
};
}
return { content, descriptionSwapped };
}
function qualityGateEnabled(run) {
return run.profile.processes?.distill?.qualityGate?.enabled ?? true;
}
/**
* Judge the distilled content, then queue it. A rejected, uncertain or
* source-contradicting result is recorded instead (see {@link writeQualityRejection}).
*/
async function judgeAndQueue(run, out) {
let content = out.content;
let confidence;
let judged;
if (qualityGateEnabled(run)) {
const similarLessons = await run.similar(content.slice(0, 500), 3);
// The judge reads what the generator read: the source body, without its frontmatter (buildDistillPrompt).
const source = out.source ? parseFrontmatter(out.source).content.trim() : "";
const verdict = await runLessonQualityJudge(run.config, content, source, run.options.chat, {
...(similarLessons.length > 0 ? { similarLessons } : {}),
...((run.judgeRunner ?? run.runner) ? { llmRunner: run.judgeRunner ?? run.runner } : {}),
...(run.options.signal ? { signal: run.options.signal } : {}),
onNotices: run.notices.add,
});
if (!verdict.pass) {
return rejectDistilled(run, out.ref, content, verdict.score, verdict.reason, {
...(verdict.reviewNeeded ? { reviewNeeded: true } : {}),
...(verdict.criteria ? { criteria: verdict.criteria } : {}),
});
}
if (verdict.score > 0)
confidence = verdict.score / 5;
judged = verdict;
}
let frontmatter;
if (out.promotion) {
const data = parseFrontmatter(content).data;
if (Object.keys(data).length > 0)
frontmatter = data;
}
else {
// Optional check against the cited source; a contradiction goes to a human.
const fidelity = getImproveProcessConfig("distill", run.profile)?.fidelityCheck ?? {};
if (fidelity.enabled && out.source) {
try {
const verdict = checkDistillFidelity(stripFrontmatterBody(content), [stripFrontmatterBody(out.source)], fidelity);
if (verdict.contradictionDetected) {
return rejectDistilled(run, out.ref, content, 2.0, verdict.reason ?? "Proposal may contradict cited source memories.", { reviewNeeded: true, fidelityContradiction: true });
}
}
catch {
// The fidelity check is supplemental.
}
}
// Canonical provenance goes into the content promotion writes.
const parsed = parseFrontmatter(content);
const xrefs = Array.isArray(parsed.data.xrefs) ? parsed.data.xrefs.map(String) : [];
frontmatter = { ...parsed.data, xrefs: [...new Set([...xrefs, run.inputRef])] };
delete frontmatter.sources;
content = assembleAsset(frontmatter, parsed.content);
}
const proposal = mintProposal(run.stash, run.options.ctx, {
ref: out.ref,
source: "distill",
...(run.options.sourceRun !== undefined ? { sourceRun: run.options.sourceRun } : {}),
payload: { content, ...(frontmatter ? { frontmatter } : {}) },
...(confidence !== undefined ? { confidence } : {}),
...(run.options.eligibilitySource ? { eligibilitySource: run.options.eligibilitySource } : {}),
// The ledger keys the attempt by the input, not the output.
attemptedRefs: [run.ledgerRef],
}, { judged });
persistOutputEncodingSalience(run, out.ref, content);
const swapped = out.descriptionSwapped ? { descriptionSwapped: out.descriptionSwapped } : {};
emitDistill(run, {
outcome: "queued",
proposalRef: out.ref,
proposalKind: out.kind,
proposalId: proposal.id,
...(confidence !== undefined ? { judgeConfidence: confidence } : {}),
...(run.options.sourceRun !== undefined ? { sourceRun: run.options.sourceRun } : {}),
...exclusionMeta(run, false),
...swapped,
});
return {
schemaVersion: 1,
ok: true,
outcome: "queued",
inputRef: run.inputRef,
proposalRef: out.ref,
proposalKind: out.kind,
proposalId: proposal.id,
proposal,
...exclusionMeta(run, true),
...swapped,
};
}
function rejectDistilled(run, proposalRef, content, score, reason, meta) {
return writeQualityRejection({
stash: run.stash,
inputRef: run.inputRef,
proposalRef,
content,
score,
reason,
meta: { ...meta, ...exclusionMeta(run, true) },
eligibilitySource: run.options.eligibilitySource,
eventsCtx: run.options.eventsCtx,
proposalsCtx: run.options.ctx,
sourceRun: run.options.sourceRun,
ledgerRef: run.ledgerRef,
});
}
/**
* Record a distill quality-gate outcome and return its envelope.
* `quality_rejected` lands in the improve ledger under the input's key (its
* rejection window keeps selection from regenerating it); `review_needed`
* mints a pending proposal for a human, stamped `deferred`/`quality-gate` so
* the triage drain leaves it alone. Content the mint refuses still records
* the attempt.
*/
export function writeQualityRejection(args) {
const meta = args.meta ?? {};
const outcome = meta.reviewNeeded ? "review_needed" : "quality_rejected";
const ledgerRef = args.ledgerRef ?? args.inputRef;
const access = { proposalsCtx: args.proposalsCtx, eventsCtx: args.eventsCtx };
const attempt = { stashDir: args.stash, ref: ledgerRef, source: "distill", detail: args.reason };
let proposal;
if (outcome === "quality_rejected") {
recordLedgerAttempt(access, { ...attempt, outcome: "quality_rejected" });
}
else {
try {
proposal = mintProposal(args.stash, args.proposalsCtx, {
ref: args.proposalRef,
source: "distill",
...(args.sourceRun !== undefined ? { sourceRun: args.sourceRun } : {}),
payload: { content: args.content },
attemptedRefs: [ledgerRef],
...(args.eligibilitySource ? { eligibilitySource: args.eligibilitySource } : {}),
}, { review: { reason: "quality-review", gate: "quality-gate" } });
}
catch (error) {
warn(`[akm] writeQualityRejection: failed to queue ${args.proposalRef} for review: ${error instanceof Error ? error.message : String(error)}`);
recordLedgerAttempt(access, { ...attempt, outcome: "review_needed" });
}
}
const eligMeta = args.eligibilitySource ? { eligibilitySource: args.eligibilitySource } : {};
appendEvent({
eventType: "distill_invoked",
ref: ledgerRef,
metadata: {
outcome,
proposalRef: args.proposalRef,
score: args.score,
reason: args.reason,
...meta,
...eligMeta,
},
}, args.eventsCtx);
return {
schemaVersion: 1,
ok: true,
outcome,
inputRef: args.inputRef,
proposalRef: args.proposalRef,
score: args.score,
reason: args.reason,
...(proposal ? { proposalId: proposal.id, proposal } : {}),
...meta,
};
}
async function planPromotion(run, feedbackEvents) {
const assessment = assessMemoryKnowledgePromotionCandidate({
inputRef: run.inputRef,
assetContent: run.asset.content,
feedbackEvents,
});
if (!assessment.promote || !assessment.content)
return null;
const existingPath = await run.lookup(assessment.knowledgeRef);
let existing = null;
try {
// The promotion is filed in run.stash, so only that bundle's doc is its destination (#1000).
if (existingPath && fs.existsSync(existingPath) && isWithin(existingPath, run.stash)) {
existing = fs.readFileSync(existingPath, "utf8");
}
}
catch {
existing = null;
}
return { knowledgeRef: assessment.knowledgeRef, content: assessment.content, existing };
}
/**
* Promote a reinforced memory to knowledge. An existing destination is
* reconciled by the model (ADD/UPDATE swap content in, NOOP keeps what is
* there); without a model the existing content is appended for the reviewer.
*/
async function promoteToKnowledge(run, plan) {
let content = plan.content;
if (plan.existing && run.runner) {
const merged = await callStage({
feature: "distill",
runner: run.runner,
system: "Return only valid JSON. No prose.",
prompt: [
"You are merging two versions of a knowledge document.",
"Existing content is already committed; new content comes from a memory distillation run.",
"Choose one of: ADD (combine both), UPDATE (replace existing with new), NOOP (keep existing unchanged).",
'Return ONLY valid JSON: {"action": "ADD"|"UPDATE"|"NOOP", "content": "<merged markdown if ADD/UPDATE, empty string if NOOP>"}',
"",
"## Existing knowledge content",
"```",
plan.existing.slice(0, 3000),
"```",
"",
"## New content from distillation",
"```",
plan.content.slice(0, 3000),
"```",
].join("\n"),
request: {
...(run.options.signal ? { signal: run.options.signal } : {}),
...(run.options.chat ? { chat: run.options.chat } : {}),
},
onNotices: run.notices.add,
});
const decision = merged.ok
? parseEmbeddedJsonResponse(merged.raw)
: undefined;
if (decision?.action === "NOOP") {
emitDistill(run, {
outcome: "skipped",
proposalRef: plan.knowledgeRef,
message: "D-1: LLM resolved destination conflict as NOOP — existing content kept",
});
return {
schemaVersion: 1,
ok: true,
outcome: "skipped",
inputRef: run.inputRef,
proposalRef: plan.knowledgeRef,
skipReason: "conflict_noop",
message: "Existing knowledge content unchanged (contradiction resolution: NOOP)",
};
}
if ((decision?.action === "ADD" || decision?.action === "UPDATE") && decision.content?.trim()) {
content = decision.content;
}
}
else if (plan.existing) {
content = [
plan.content,
"",
"---",
"<!-- D-1 / #369: Existing knowledge content is shown below for reviewer reference. -->",
"<!-- Review: decide whether to ADD (merge), UPDATE (replace), or NOOP (keep existing). -->",
"",
"## Existing content (for reviewer reference)",
"",
plan.existing,
].join("\n");
}
return judgeAndQueue(run, {
ref: plan.knowledgeRef,
kind: "knowledge",
content,
source: run.asset.content,
promotion: true,
});
}
// ── Inputs ───────────────────────────────────────────────────────────────────
/** Read the input asset (best-effort: an unindexed asset distils from feedback alone). */
async function loadInput(lookup, inputRef) {
try {
const filePath = await lookup(inputRef);
if (filePath && fs.existsSync(filePath))
return { path: filePath, content: fs.readFileSync(filePath, "utf8") };
}
catch {
// An index miss is not fatal.
}
return { path: null, content: null };
}
/** The index's ref bigram vocabulary, for the novelty term of encoding salience. */
function loadRefVocabulary() {
try {
const db = openReadonlyExistingDatabase(getDbPath(), { isolatedSnapshot: true });
if (!db)
return new Set();
try {
return buildRefVocabulary(getAllEntries(db).map((e) => e.itemRef));
}
finally {
closeDatabase(db);
}
}
catch {
return new Set();
}
}
/**
* Score the input's encoding salience and mirror it to the asset frontmatter
* and `asset_salience` (keyed by the ledger ref). Best-effort throughout.
*/
function stampInputSalience(run) {
const { content, path: filePath } = run.asset;
if (!content || !filePath)
return;
try {
const type = parseRefInput(run.inputRef).type;
let revisionCount = 0;
try {
// Revisions so far: every proposal raised against this ref.
revisionCount = listProposals(run.stash, { ref: run.inputRef, includeArchive: true }).length;
}
catch {
// Unknown history scores as a first encounter.
}
const scored = scoreEncodingSalience({ body: content, type, existingRefVocabulary: run.vocabulary, revisionCount });
const updated = writeSalienceToFrontmatter(content, scored.score, scored);
if (updated !== content) {
fs.writeFileSync(filePath, updated, "utf8");
recordWrittenPath(filePath);
run.asset.content = updated;
}
try {
withStateDb((stateDb) => upsertAssetSalience(stateDb, run.ledgerRef, computeSalience({
ref: run.inputRef,
type,
retrievalFreq: 0,
encodingSalience: scored.score,
outcomeWeightEnabled: run.outcomeWeightEnabled,
})));
}
catch {
// The frontmatter mirror is the only persistence then.
}
}
catch {
// Scoring never blocks distillation.
}
}
/**
* Content-score a distilled output so it carries a real encoding salience from
* creation — lessons are refused as inputs, so this is their only chance.
*/
function persistOutputEncodingSalience(run, ref, body) {
try {
const type = parseRefInput(ref).type;
const scored = scoreEncodingSalience({ body, type, existingRefVocabulary: run.vocabulary, revisionCount: 0 });
withStateDb((stateDb) => upsertAssetSalience(stateDb, ref, computeSalience({
ref,
type,
retrievalFreq: 0,
encodingSalience: scored.score,
outcomeWeightEnabled: run.outcomeWeightEnabled,
})));
}
catch {
// Scoring never blocks proposal creation.
}
}
/** The ref's feedback events, minus any `excludeFeedbackFromRefs` matches. */
function readDistillFeedback(run) {
const read = run.options.readEventsFn ??
((readOptions) => readEvents(readOptions, { readOnly: true }));
const { events } = read({
ref: run.ledgerRef,
type: "feedback",
excludeTags: run.options.excludeTags,
includeTags: run.options.includeTags,
});
const excluded = new Set((run.options.excludeFeedbackFromRefs ?? []).map((ref) => ref.trim()).filter((ref) => ref.length > 0));
if (excluded.size === 0)
return events;
const kept = events.filter((e) => !(e.ref !== undefined && excluded.has(e.ref)));
run.exclusion = {
filteredFeedbackCount: events.length - kept.length,
feedbackFullyFiltered: events.length > 0 && kept.length === 0,
};
return kept;
}
/** System + user prompt: rejected-proposal context, optional CLS neighbours, stash standards. */
async function buildDistillMessages(run, feedback, kind, outputRef) {
const rejectedProposals = rejectedProposalContext(run.stash, run.inputRef, run.options.ctx, run.options.eventsCtx);
// CLS interleaving (default off): show related lessons so the model does not overwrite them.
const cls = getImproveProcessConfig("distill", run.profile)?.cls ?? {};
let clsContext = "";
if (cls.enabled) {
try {
const query = run.asset.content ? run.asset.content.slice(0, 500) : run.inputRef;
clsContext = buildClsContext(await run.similar(query, cls.adjacentCount ?? DEFAULT_CLS_ADJACENT_COUNT), cls);
}
catch {
// CLS context is supplemental.
}
}
const standardsContext = resolveStandardsContext(outputRef, run.stash);
const prompt = buildDistillPrompt({
inputRef: run.inputRef,
assetContent: run.asset.content,
feedback,
proposalKind: kind,
...(rejectedProposals.length > 0 ? { rejectedProposals } : {}),
...(standardsContext.trim() ? { standardsContext } : {}),
});
return {
system: kind === "knowledge" ? distillKnowledgeSystemPrompt : distillLessonSystemPrompt,
prompt: `${prompt}${clsContext}`,
};
}
async function defaultLookup(ref, stashDir) {
return resolveAssetPath(ref, {
stashDir,
mode: "disk-only",
directoryIndexNames: ["SKILL.md"],
preserveDirectNameFallback: true,
honorOrigin: false,
});
}
/** Top-N existing lessons similar to `query` (empty when search is unavailable). */
async function fetchTopSimilarLessons(query, n) {
try {
const result = await akmSearch({ query, type: "lesson", limit: n, skipLogging: true, eventSource: "improve" });
return (result?.hits ?? [])
.filter((h) => "path" in h && typeof h.path === "string")
.slice(0, n)
.map((h) => {
let content = "";
try {
if (h.path && fs.existsSync(h.path))
content = fs.readFileSync(h.path, "utf8");
}
catch {
// best-effort
}
return { ref: h.ref, content };
});
}
catch {
return [];
}
}