UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

156 lines (155 loc) • 11.1 kB
import { openSystemReminder } from "../reminder-mint.js"; import { formatHookFeedback } from "../hooks.js"; import { classicEventBase, dispatchClassic, stopEventOf, stringMembersAnswer, stringMembersOf } from "../mods/stations.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 stopEvent = stopEventOf(prepared.hookIdentity); const stopArmed = prepared.modBus.hasHandlers(stopEvent); 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 (stopArmed || 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 (!stopArmed) return []; const outcome = await dispatchClassic(prepared.modBus, stopEvent, { ...classicEventBase(prepared.hookIdentity, prepared.permissionMode.observed), stopHookActive: consecutiveBlocks > 0, consecutiveBlocks, getBranch: () => prepared.session.getBranch(), backgroundTasks: () => prepared.listBackgroundTasks(), }, { signal: prepared.abortController.signal, checkAnswer: stringMembersAnswer(stopEvent, ["block", "additionalContext"]) }).catch((err) => { runner.deps.onError?.(asThrownError(err), { phase: "hook", sessionId: prepared.sessionId }); return undefined; }); if (outcome?.kind !== "answered") { consecutiveBlocks = 0; return []; } const result = stringMembersOf(outcome.answer, ["block", "additionalContext"]); 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 {}; }