@sema-agent/core
Version:
Stateless, task-oriented AI agent core
192 lines (191 loc) • 9.46 kB
JavaScript
import { createHash, randomUUID } from "node:crypto";
import { Type } from "typebox";
import { delimitUntrusted } from "../core/untrusted-text.js";
import { OUTPUT_TOOL_NAME } from "../core/runner/synthetic-tools.js";
import { isDurablePause, pauseFamily } from "../agents/suspend-guard.js";
import { toolFace } from "../core/tool-catalog-entries.js";
export const DECLARE_DONE_TOOL_NAME = "DeclareDone";
function createDeclareDoneTool(ref) {
return {
name: DECLARE_DONE_TOOL_NAME,
...toolFace("declare-done"),
description: "Call this ONLY when you believe the goal is FULLY achieved — verified against evidence, not just " +
"attempted. Provide a concise summary of what was achieved and how you verified it. Calling this " +
"declares completion; a verification check then runs. If it passes, the goal stops and surfaces for " +
"human review. If it does NOT pass, you will be asked to keep working with the verification feedback.",
parameters: Type.Object({ summary: Type.String() }),
execute: (args) => {
ref.declared = true;
ref.summary = String(args.summary ?? "").trim();
return { content: "Completion declared. Verifying…", terminate: true };
},
};
}
const TIMEOUT_FEEDBACK = "Your previous attempt ran out of steps before declaring completion. Keep going.";
const NOT_DECLARED_FEEDBACK = "Your previous attempt ended without declaring completion. If the goal is truly done, declare it; otherwise keep working.";
function continuationPrompt(goalObjective, feedback, hasOutputSchema) {
const feedbackBlock = typeof feedback === "string"
? feedback
: `A verification check found the goal is NOT yet complete:\n${delimitUntrusted("VERIFICATION FEEDBACK", feedback.reason)}`;
const declareHow = hasOutputSchema ? `call ${OUTPUT_TOOL_NAME} with your final result` : `call the ${DECLARE_DONE_TOOL_NAME} tool`;
return `The goal is NOT yet complete. Continue working toward it.\n\n${feedbackBlock}\n\nGoal: ${goalObjective}\n\nWhen you are truly done, ${declareHow} to declare completion.`;
}
function raceAbortReject(p, signal) {
if (signal.aborted)
return Promise.reject(new Error("aborted"));
return new Promise((resolve, reject) => {
let settled = false;
const onAbort = () => {
if (settled)
return;
settled = true;
reject(new Error("aborted"));
};
signal.addEventListener("abort", onAbort, { once: true });
p.then((v) => {
if (settled)
return;
settled = true;
signal.removeEventListener("abort", onAbort);
resolve(v);
}, (e) => {
if (settled)
return;
settled = true;
signal.removeEventListener("abort", onAbort);
reject(e);
});
});
}
export async function runGoal(runner, spec) {
if (!Number.isFinite(spec.maxIterations) || spec.maxIterations < 1) {
throw new Error(`runGoal: maxIterations must be a finite number ≥ 1 (got ${spec.maxIterations})`);
}
if (spec.totalTimeoutMs !== undefined && (!Number.isFinite(spec.totalTimeoutMs) || spec.totalTimeoutMs < 0 || spec.totalTimeoutMs > 2_147_483_647)) {
throw new Error(`runGoal: totalTimeoutMs must be a finite number in [0, 2147483647] (got ${spec.totalTimeoutMs}) — a host timer clamps anything else and fires immediately. Omit it for no deadline.`);
}
const hasOutputSchema = spec.taskSpec.outputSchema !== undefined;
if (!hasOutputSchema && (spec.taskSpec.tools ?? []).some((t) => t.name === DECLARE_DONE_TOOL_NAME)) {
const e = new Error(`Tool name "${DECLARE_DONE_TOOL_NAME}" is reserved in goal mode.`);
e.code = "config.reserved_tool_name";
throw e;
}
const sessionId = spec.taskSpec.sessionId ?? randomUUID();
const verificationKind = spec.verificationKind;
let cumulativeTokens = 0;
let lastVerdict;
let lastResult;
const startedAt = Date.now();
const objectiveHash = createHash("sha256").update(spec.objective).digest("hex").slice(0, 16);
let spentCostMicroUsd = 0;
let spentTurns = 0;
let doneCheckRuns = 0;
let oracleHadRedRun = false;
const deadlineCtl = new AbortController();
let timer;
if (spec.totalTimeoutMs !== undefined) {
timer = setTimeout(() => deadlineCtl.abort(), spec.totalTimeoutMs);
timer.unref?.();
}
const goalSignal = spec.signal
? AbortSignal.any([spec.signal, deadlineCtl.signal])
: deadlineCtl.signal;
const classifyStop = () => spec.signal?.aborted ? { status: "aborted" } : deadlineCtl.signal.aborted ? { status: "budget", budgetCause: "walltime" } : null;
const mk = (status, iterations, over) => {
const r = {
status,
iterations,
result: over?.result ?? lastResult,
lastVerdict,
verificationKind,
accepted: false,
cumulativeTokens,
checkpointToken: over?.checkpointToken,
errorCode: over?.errorCode,
...(status === "budget" && over?.budgetCause !== undefined ? { budgetCause: over.budgetCause } : {}),
};
if (verificationKind === "mechanical" && status !== "suspended" && status !== "needs_review") {
runner.emitTaskOutcome?.({
taskSignature: spec.taskSignature ?? `goal:${objectiveHash}`,
runId: r.result?.taskId ?? sessionId,
model: r.result?.model,
verificationKind: "mechanical",
status,
errorCode: r.errorCode,
oracle: { green: status === "achieved", checks: doneCheckRuns },
oracleHadRedRun,
signatureInputs: {
toolset: (spec.taskSpec.tools ?? []).map((t) => t.name).sort(),
objectiveHash,
},
tokens: cumulativeTokens,
costMicroUsd: spentCostMicroUsd,
turns: spentTurns,
wallMs: Date.now() - startedAt,
});
}
return r;
};
try {
let lastFeedback;
for (let iteration = 1; iteration <= spec.maxIterations; iteration++) {
const preStop = classifyStop();
if (preStop)
return mk(preStop.status, iteration - 1, { budgetCause: preStop.budgetCause });
if (spec.budgetTokens !== undefined && cumulativeTokens >= spec.budgetTokens)
return mk("budget", iteration - 1, { budgetCause: "tokens" });
const objective = iteration === 1 ? spec.objective : continuationPrompt(spec.objective, lastFeedback, hasOutputSchema);
const doneRef = {};
const tools = hasOutputSchema
? spec.taskSpec.tools
: [...(spec.taskSpec.tools ?? []), createDeclareDoneTool(doneRef)];
const result = await runner.runTask({ ...spec.taskSpec, sessionId, objective, tools, signal: goalSignal }, { goalMode: true });
lastResult = result;
cumulativeTokens += (result.stats.tokens ?? 0) + (result.stats.nested?.tokens ?? 0);
spentCostMicroUsd += (result.stats.costMicroUsd ?? 0) + (result.stats.nested?.costMicroUsd ?? 0);
spentTurns += (result.stats.turns ?? 0) + (result.stats.nested?.turns ?? 0);
if (isDurablePause(result.terminal)) {
return mk(pauseFamily(result.terminal), iteration, { result, checkpointToken: result.terminal.token });
}
const midStop = classifyStop();
if (midStop)
return mk(midStop.status, iteration, { result, budgetCause: midStop.budgetCause });
if (result.terminal.kind === "blocked")
return mk("blocked", iteration, { result });
if (result.terminal.kind === "failed") {
const code = result.terminal.code;
if (code !== undefined && code.startsWith("limits.max_")) {
lastFeedback = TIMEOUT_FEEDBACK;
continue;
}
return mk("failed", iteration, { result, errorCode: code });
}
const gate1 = hasOutputSchema ? result.structuredOutput !== undefined : doneRef.declared === true;
if (!gate1) {
lastFeedback = NOT_DECLARED_FEEDBACK;
continue;
}
let verdict;
try {
verdict = await raceAbortReject(spec.doneCheck({ result, iteration, sessionId, cumulativeTokens }, goalSignal), goalSignal);
}
catch {
const s = classifyStop();
if (s)
return mk(s.status, iteration, { result, budgetCause: s.budgetCause });
return mk("failed", iteration, { result, errorCode: "goal.donecheck_error" });
}
lastVerdict = verdict;
doneCheckRuns++;
if (verdict.done)
return mk("achieved", iteration, { result });
oracleHadRedRun = true;
lastFeedback = { reason: verdict.reason };
}
return mk("max_iterations", spec.maxIterations);
}
finally {
if (timer)
clearTimeout(timer);
}
}