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
103 lines (102 loc) • 4.02 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/.
/**
* Summary-validation gate for workflow step/workflow completion (#506).
*
* Takes a step's `completionCriteria` and the summary of work the agent claims
* to have done, asks the configured LLM to judge whether the summary
* demonstrates each criterion is met, and returns either a pass or structured
* corrective feedback steering the agent on what to finish/fix.
*
* The judge call is injected so the gate is unit-testable without a live
* endpoint. A declared gate is fail-closed: only a well-formed affirmative
* verdict advances the step.
*
* @module workflows/validate-summary
*/
import validateSummaryJudgePrompt from "../assets/prompts/validate-summary-judge.md" with { type: "text" };
import { parseJsonResponse } from "../core/parse.js";
/**
* The one verdict parser: a well-formed verdict, or `undefined` when malformed
* (which the engine treats as a verifier failure, never a rejection).
*/
export function parseJudgeVerdict(raw) {
const parsed = parseJsonResponse(raw);
if (!parsed || typeof parsed.complete !== "boolean")
return undefined;
return parsed;
}
const JUDGE_SYSTEM = validateSummaryJudgePrompt;
function buildUserPrompt(input) {
const criteria = input.completionCriteria.map((c, i) => `${i + 1}. ${c}`).join("\n");
return [
`Step: ${input.stepTitle}`,
"",
"Completion criteria:",
criteria,
"",
"Agent's summary of work done:",
input.summary.trim(),
"",
"Return the JSON verdict now.",
].join("\n");
}
/**
* Run the summary-validation gate.
*
* No criteria skips verification. A missing, failing, or malformed judge is a
* rejection: declared criteria are never silently bypassed.
*/
export async function validateStepSummary(input, judge, signal) {
const criteria = input.completionCriteria.filter((c) => c.trim().length > 0);
if (criteria.length === 0) {
return { complete: true, missing: [], skipped: true };
}
if (!judge) {
return {
complete: false,
missing: criteria,
feedback: "This step declares completion criteria but no verification judge is available.",
};
}
let raw;
try {
if (signal?.aborted)
throw interruptionReason(signal);
raw = await judge({ system: JUDGE_SYSTEM, user: buildUserPrompt({ ...input, completionCriteria: criteria }) });
if (signal?.aborted)
throw interruptionReason(signal);
}
catch {
if (signal?.aborted)
throw interruptionReason(signal);
return {
complete: false,
missing: criteria,
feedback: "The verification judge failed. Retry after fixing the verifier configuration or service.",
};
}
const parsed = parseJudgeVerdict(raw);
if (!parsed) {
return {
complete: false,
missing: criteria,
feedback: "The verification judge returned a malformed verdict instead of the required JSON result.",
};
}
if (parsed.complete) {
return { complete: true, missing: [] };
}
const missing = Array.isArray(parsed.missing)
? parsed.missing.filter((m) => typeof m === "string" && m.trim().length > 0)
: [];
const feedback = typeof parsed.feedback === "string" && parsed.feedback.trim().length > 0
? parsed.feedback.trim()
: "The summary does not yet demonstrate every completion criterion is met. " +
"Finish the outstanding work and resubmit with a summary that addresses each criterion.";
return { complete: false, missing, feedback };
}
function interruptionReason(signal) {
return signal.reason instanceof Error ? signal.reason : new Error("Workflow verification interrupted.");
}