@sema-agent/core
Version:
Stateless, task-oriented AI agent core
163 lines (162 loc) • 11.4 kB
JavaScript
import { openSystemReminder } from "../reminder-mint.js";
import { formatHookFeedback, hookSeatExpiredError, runHookSeat } from "../hooks.js";
import { asThrownError } from "../thrown-value.js";
const STOP_HOOK_BLOCK_CAP = 8;
export function runStopAndFinalVerify(input) {
const { spec, prepared, queue, rs, stats, ident, walltimeMonotonicDeadline, runner } = input;
const stopHook = (spec.hooks ?? runner.deps.hooks)?.stop;
const finalVerificationOn = spec.finalVerification === true;
const finalVerifyBudgetFill = () => {
let worst = 0;
if (rs.budget.maxTokensWindow !== undefined && rs.budget.maxTokensWindow > 0)
worst = Math.max(worst, stats.tokens / rs.budget.maxTokensWindow);
if (rs.budget.maxCostMicroUsd !== undefined && rs.budget.maxCostMicroUsd > 0)
worst = Math.max(worst, stats.costMicroUsd / rs.budget.maxCostMicroUsd);
if (walltimeMonotonicDeadline !== undefined && prepared.suspendForResource === undefined) {
const windowMs = walltimeMonotonicDeadline - rs.telemetry.taskStartMonotonic;
if (windowMs > 0)
worst = Math.max(worst, (performance.now() - rs.telemetry.taskStartMonotonic) / windowMs);
}
return worst;
};
const emitFinalVerifyEcho = (body) => {
queue.push({ type: "steering_injected", source: "final_verification", preview: body.slice(0, 220), ...ident() });
};
if (stopHook || finalVerificationOn) {
let consecutiveBlocks = 0;
prepared.harness.setStopGate(async () => {
if (prepared.abortController.signal.aborted || prepared.pausedRef.current !== undefined || prepared.batchHaltRef.current !== undefined)
return [];
if (finalVerificationOn &&
(rs.counters.finalVerifyInjections === 0 || (rs.counters.finalVerifyInjections === 1 && rs.counters.groundingSignalPreR9 && !rs.counters.groundingSignalPostR9)) &&
rs.counters.wroteThisRun &&
prepared.outputRef.set !== true &&
!(rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns - 1) &&
finalVerifyBudgetFill() < 0.9) {
rs.counters.finalVerifyInjections += 1;
if (rs.counters.finalVerifyInjections === 2) {
const reentryBody = openSystemReminder(prepared.reminderMark) +
"[final verification] Your tool calls in this run worked with raw bytes, structural parsing, " +
"or checksum/digest computation — the deliverable very likely embeds verifiable structure (structural fields, an " +
"embedded checksum-family value, reference data it must match, or a replayable deterministic path). You MUST " +
"execute the grounding check that structure supports — recompute the embedded value and compare it against the " +
"declared one, re-parse the structure from the raw bytes and reconcile it with your output, compare against the " +
"reference data, or replay the deterministic path — and REPORT the check's concrete result before finishing. " +
"A closing statement without a reported check result is not verification. If you already ran such a check, state " +
"its concrete result now; if the check mismatches, fix the deliverable first. This is the final reminder from " +
"this verification gate — it will not intervene again.</system-reminder>";
emitFinalVerifyEcho(reentryBody);
return [
{
role: "user",
engineMinted: true,
content: reentryBody,
timestamp: Date.now(),
},
];
}
const nudgeBody = openSystemReminder(prepared.reminderMark) +
"[final verification] Before finishing: re-verify the FINAL deliverable through its REAL entry point, " +
"exactly as the acceptance criteria would exercise it — execute the binary/function/endpoint directly and read the ACTUAL " +
"output and exit code. Do NOT rely on earlier self-tests, shell redirections, or assumptions (a program that prints to " +
"stdout is not a program that writes the required file). If anything mismatches the task's requirements, fix it before " +
"finishing. " +
"Treat verification writes as state-harmless: when the deliverable itself is a persisted final " +
"state (for example a committed or pushed file, a deployed artifact, or a required output file), " +
"do not change that state merely to test it. This constrains HOW you verify — it is never a " +
"license to skip the real acceptance path or to check a substitute of your own making: expected " +
"values must come from the task's requirements, never from content you generated. If the real " +
"acceptance path requires a write, use disposable inputs or an isolated target, end in the exact " +
"required final state, and verify that final state before finishing. " +
"If the work relied on a third-party API, library, or model, check the usage contract the object itself declares " +
"(docstrings, metadata, configuration — e.g. prompt conventions shipped with a model) and confirm your calls follow " +
"it rather than a default symmetric usage. Verify not only that the deliverable EXISTS but that the METHOD that " +
"produced it matches the task's requirements. " +
"Choose the verification SURFACE deliberately: check against the reference data, oracle, or evaluation tooling the " +
"task itself provides — re-running your own implementation and getting the same answer is self-consistency, not " +
"correctness — and cross-check through an independent second path where the task or environment offers one " +
"(checksums, runtime artifacts). Verify the PERSISTED artifact — re-read what is actually on disk or committed, " +
"not in-memory state — against every hard constraint from the original task text (numeric bounds, allowed-value " +
"lists, naming semantics, required files), reconciling whole-set completeness: nothing missing, nothing duplicated. " +
"If the deliverable embeds verifiable structure — structural fields, an embedded checksum-family value, " +
"reference data it must match, or a replayable deterministic path — you MUST execute the grounding check " +
"that structure supports and REPORT its concrete result in your closing summary: for such a deliverable, " +
"no reported check result means the work is not finished. " +
"If the task produced neither an executable deliverable nor any verifiable structure or acceptance " +
"oracle to check against, briefly confirm completion and stop. " +
"Residue YOUR OWN testing created (scratch files, running processes, generated outputs the task does not ask for) " +
"is not protected state — if the task's required final state is a clean target, removing your own residue is part " +
"of delivering it. " +
"Verification must never LAUNDER uncertainty: if part of your conclusion was uncertain before this check, keep " +
"reporting it as uncertain unless the check you actually ran resolved it — a re-stated conclusion is not new " +
"evidence.</system-reminder>";
emitFinalVerifyEcho(nudgeBody);
return [
{
role: "user",
engineMinted: true,
content: nudgeBody,
timestamp: Date.now(),
},
];
}
if (!stopHook)
return [];
let result;
try {
const stopSeat = await runHookSeat("stop", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => stopHook({
stopHookActive: consecutiveBlocks > 0,
consecutiveBlocks,
getBranch: () => prepared.session.getBranch(),
backgroundTasks: () => prepared.listBackgroundTasks(),
identity: prepared.hookIdentity,
permissionMode: prepared.permissionMode.observed,
signal: sig,
}));
if (stopSeat.expired) {
if (stopSeat.cause === "timeout") {
runner.deps.onError?.(hookSeatExpiredError("stop", prepared.hookTimeoutMs, stopSeat.cause, "the run was allowed to END (the seat's own no-opinion answer); no pushback and no additional context were injected"), { phase: "hook", sessionId: prepared.sessionId });
}
consecutiveBlocks = 0;
return [];
}
result = stopSeat.value;
}
catch (err) {
runner.deps.onError?.(asThrownError(err), { phase: "hook", sessionId: prepared.sessionId });
return [];
}
const messages = [];
if (result?.additionalContext) {
messages.push({
role: "user",
engineMinted: true,
content: formatHookFeedback(`Stop hook additional context: ${result.additionalContext}`, prepared.reminderMark),
timestamp: Date.now(),
});
}
if (messages.length === 0 && !result?.block) {
consecutiveBlocks = 0;
return messages;
}
consecutiveBlocks++;
const cap = STOP_HOOK_BLOCK_CAP;
if (consecutiveBlocks > cap) {
runner.deps.onError?.(new Error(`a Stop hook kept the turn from ending ${consecutiveBlocks} consecutive times — overriding and ending the run. ` +
`Both a block and an additionalContext-only push-back count (CC 2.1.220). ` +
`Check ctx.stopHookActive in the hook and return success while it's true.`), { phase: "hook", sessionId: prepared.sessionId });
return [];
}
if (result?.block) {
messages.push({
role: "user",
engineMinted: true,
content: formatHookFeedback(`Stop hook stopped continuation: ${result.block}`, prepared.reminderMark),
timestamp: Date.now(),
});
}
return messages;
});
}
return {};
}