UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

398 lines (397 loc) • 26.3 kB
import { uuidv7 } from "../../engine/session/uuid.js"; import { Value } from "typebox/value"; import { ASK_USER_QUESTION_TOOL_NAME, classifyQuestionOutcome, validateAskQuestions } from "../ask-question.js"; import { hasBackgroundShell, sweepBackgroundShells, sweepRunBoundaryBackgroundShells } from "../background-shell.js"; import { boundInputHashOf } from "../canonical-json.js"; import { buildRiskDescriptor, checkpointVersionFor, debitLedger, encodeAtFidelity, mintCheckpointId, mintCheckpointToken, resolveDeclaredFidelity, samePlainValue, } from "../checkpoint-store.js"; import { pauseOf } from "../pause-registry.js"; import { askCarryRowMembers } from "../hooks.js"; import { askMandateMember, ownedClearanceFacts } from "../ask-origin.js"; import { defaultTaskRegistry } from "../task-registry.js"; import { advertisedWritableRootsSeat, isLiveApproverSeat, carriesBidiControls, describeThrown, refuseOutOfContractDecision, tryCloneArgs } from "../tool-policy.js"; import { InMemoryToolResultStore, ScopedToolResultStore, isVolatileOffloadStore } from "../tool-result-store.js"; import { raceAbort, raceSettlementAgainstSignal } from "./abort-race.js"; import { checkpointScopeOf } from "./checkpoint-scope.js"; import { CONTENT_ASK_BINDING_CAP } from "./content-ask-bindings.js"; import { DEFAULT_UNATTENDED_APPROVAL_TTL_MS, publishCommittedSuspend, sanitizedTtlMs } from "./park-commit.js"; import { remoteEnvFailureNote } from "./remote-env-retry.js"; import { callFaceSeat, parkedToolIdentity, parkedToolFace } from "../tool-roster.js"; import { seatOf, wireOf } from "../tool-name-readings.js"; import { skillCallGrantDigest } from "../skill-grant.js"; import { SKILL_TOOL_NAME } from "./synthetic-tools.js"; class ParkRefusal extends Error { constructor(message, options) { super(message, options); this.name = "ParkRefusal"; } } const wordlessDemand = (toolName) => { throw new Error(`a real-approval demand for "${toolName}" reached the park with no origin word — the origin stamp precedes every park`); }; export function prepareParkAsk(input) { const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, skillSpecs, now, humanReviewRef, abortController, harness, pausedRef } = input; if (askLane === undefined || saga === undefined) return { parkAsk: undefined }; const { composedCallSignal, approvalPreviewOf } = askLane; const { inFlightSpendMicroUsd, serializeCheckpointState, compensatePausedVM, commitSuspendSaga, suspendLoopCapHit, suspendableEnv, parkOnlyRemoteEnv, parkOnlyHandle } = saga; const resolveContentAsk = async (req, callSignal) => { const contentAskSignal = composedCallSignal(callSignal); if (!contentAskRoutable(req.toolCallId) || liveQuestionFace === undefined || mountedQuestionTool === undefined) { return { kind: "unavailable", parkDeclined: false }; } const deliveryId = uuidv7(); const bindOutcome = (entry, questionsHash) => { if (contentAskBindings.size >= CONTENT_ASK_BINDING_CAP && !contentAskBindings.has(req.toolCallId)) return; contentAskBindings.set(req.toolCallId, { ...entry, questionsHash, deliveryId }); }; const refuseBeforeDelivery = (code, presentedInput) => ({ kind: "delivery_failure", code, ...(presentedInput !== undefined ? { presentedInput } : {}), }); let retained; let presented; try { retained = structuredClone(req.args); presented = structuredClone(retained.questions); } catch { return refuseBeforeDelivery("question.malformed_request"); } const retainedQuestionsHash = boundInputHashOf(retained.questions); if (!Value.Check(mountedQuestionTool.parameters, retained) || validateAskQuestions(presented) !== undefined) { return refuseBeforeDelivery("question.malformed_request", retained); } if (contentAskBindings.size >= CONTENT_ASK_BINDING_CAP && !contentAskBindings.has(req.toolCallId)) { return { kind: "unavailable", parkDeclined: true }; } if (contentAskSignal.aborted) { return { kind: "delivery_failure", code: "question.aborted", presentedInput: retained }; } try { const facePromise = (async () => liveQuestionFace({ toolCallId: req.toolCallId, questions: presented, ...(spec.principal !== undefined ? { principal: spec.principal } : {}), sourceTaskId: sessionId, boundInputHash: boundInputHashOf(retained), deliveryId, }, contentAskSignal))(); const settlement = await raceAbort(facePromise.then((outcome) => ({ tag: "outcome", outcome }), (error) => ({ tag: "threw", error })), contentAskSignal, () => ({ tag: "aborted" })); if (settlement.tag === "aborted") { void facePromise.then((late) => { if (classifyQuestionOutcome(late).shape !== "answered") return; lateStrandedAnswers.push({ deliveryId, toolCallId: req.toolCallId }); discloseStrandedAnswers([{ deliveryId, toolCallId: req.toolCallId }], "the task had already ended when the answer arrived"); }, () => undefined).catch(() => undefined); } if (settlement.tag === "outcome") { const outcome = settlement.outcome; const reading = classifyQuestionOutcome(outcome); if (reading.shape === "contradictory") { bindOutcome({ kind: "failed", error: new Error("the question channel returned a contradictory outcome (an unavailable answer)") }, retainedQuestionsHash); return { kind: "delivery_failure", code: "question.human_channel_failed", presentedInput: retained }; } if (reading.shape === "unavailable") return { kind: "unavailable", parkDeclined: true, presentedInput: retained }; const answerSnapshot = reading.answer; bindOutcome({ kind: "answered", answer: answerSnapshot }, retainedQuestionsHash); return { kind: "answered", presentedInput: retained }; } const error = settlement.tag === "threw" ? settlement.error : Object.assign(new Error("the task ended while this question was still out"), { name: "AbortError" }); bindOutcome({ kind: "failed", error }, retainedQuestionsHash); return { kind: "delivery_failure", code: settlement.tag === "aborted" || contentAskSignal.aborted ? "question.aborted" : "question.human_channel_failed", presentedInput: retained, }; } catch (err) { bindOutcome({ kind: "failed", error: err }, retainedQuestionsHash); return { kind: "delivery_failure", code: "question.human_channel_failed", presentedInput: retained }; } }; const suspendAsk = parkLaneArmed && checkpointStore !== undefined ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, allowRuleShadowed, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule, probeMandated, carry) => { const syncFirstEligible = wireOf("park.syncFirstRoute", req) === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk); if (syncFirstEligible && runtimeCaps?.forceDurableGate !== true && liveFaceUnavailable !== true && !inheritedUnavailableAsks.has(req.toolCallId)) { return undefined; } const cutSignal = composedCallSignal(carry?.signal); let token; let gate; let cp; let remoteHandle; const remoteEnv = suspendableEnv; try { const parked = tryCloneArgs(postHookArgs); if (!parked.ok) { throw new ParkRefusal(`the arguments of "${req.toolName}" cannot be captured onto an approval checkpoint (unclonable value: ${parked.reason}) — ` + `a durable approval row must hold an inert snapshot of exactly what a human approves and a resume executes`, { cause: parked.cause }); } let backendFidelity; try { backendFidelity = resolveDeclaredFidelity(checkpointStore, "checkpointStore"); } catch (err) { throw new ParkRefusal(`the checkpoint backend declares an unrecognized encoding fidelity, so the width of the snapshot this row may hold is unknown (${describeThrown(err)}) — ` + `refusing to park rather than guess which values would survive it`, { cause: err }); } const captured = encodeAtFidelity(backendFidelity, parked.value); if (!captured.ok) { throw new ParkRefusal(`the arguments of "${req.toolName}" cannot be encoded by this checkpoint backend (${describeThrown(captured.cause)}) — ` + `a durable approval row must hold an inert snapshot of exactly what a human approves and a resume executes`, { cause: captured.cause }); } const parkedArgs = captured.value; const projectionMovedArgs = backendFidelity !== "structured-clone" && !samePlainValue(parked.value, parkedArgs); if (projectionMovedArgs && basePolicyForResumeEdit === undefined) { throw new ParkRefusal(`the arguments of "${req.toolName}" change shape when stored by this checkpoint backend, and no policy is wired to re-adjudicate the stored shape ` + `(no basePolicyForResumeEdit override and no caller toolPolicy) — refusing to file a row whose executable form nothing adjudicated`); } let storedShapeFacts; if (projectionMovedArgs && basePolicyForResumeEdit !== undefined) { const presented = encodeAtFidelity(backendFidelity, parkedArgs); if (!presented.ok) { throw new ParkRefusal(`the stored form of "${req.toolName}"'s arguments could not be presented for re-adjudication (${describeThrown(presented.cause)})`, { cause: presented.cause }); } const reprojectedRaced = await raceSettlementAgainstSignal(Promise.resolve(basePolicyForResumeEdit.check({ toolName: req.toolName, args: presented.value, toolCallId: req.toolCallId, budget: budgetSnapshot, ...callFaceSeat(req.face), ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}), ...advertisedWritableRootsSeat(advertisedWritableDirs), }, cutSignal)), cutSignal); if (reprojectedRaced.tag === "aborted") return undefined; if (reprojectedRaced.tag === "threw") throw reprojectedRaced.error; const reprojected = refuseOutOfContractDecision(reprojectedRaced.value); const rewroteTheFiledValue = reprojected.updatedInput !== undefined && !samePlainValue(reprojected.updatedInput, parkedArgs); if (reprojected.action === "deny" || rewroteTheFiledValue) { throw new ParkRefusal(`the arguments of "${req.toolName}" change shape when stored by this checkpoint backend, and the deployment's tool policy does not accept the stored shape` + `${reprojected.action === "deny" && reprojected.message ? `: ${reprojected.message}` : ""} — ` + `refusing to file a row whose executable form nothing adjudicated`); } if (reprojected.action === "ask") storedShapeFacts = ownedClearanceFacts(reprojected); } const rowRealApproval = realApproval ?? (storedShapeFacts?.requiresRealApproval === true ? { origin: carry?.origin ?? wordlessDemand(req.toolName) } : undefined); if (offloadStore && isVolatileOffloadStore(offloadStore) && !offloadStore.isEmpty()) { throw new ParkRefusal("durable suspend requires a durable toolResultStore (the default InMemoryToolResultStore " + "holds offloaded results that are lost across a cross-replica resume); inject " + "RunnerDeps.toolResultStore or disable offload."); } if (ownedEnv !== undefined && remoteEnv === undefined && parkOnlyRemoteEnv === undefined) { throw new ParkRefusal(incompleteSuspendAdapter !== undefined ? `durable suspend is not supported with this per-task executionEnvFactory env: it declares capabilities.suspendable but its adapter does not implement ${incompleteSuspendAdapter.join(" or ")}, so a snapshot taken now could never be restored. Implement the full RemoteExecutionEnv restore surface, or declare capabilities.suspendable:false if the workspace is externally durable (the park-only lane).` : "durable suspend is not supported with a non-remote per-task executionEnvFactory env: the " + "minted env is destroyed on suspend, so a resumed file/shell tool would act on a fresh " + "(empty) env. Use a RemoteExecutionEnv factory (it is paused, not destroyed) or a static, " + "caller-owned RunnerDeps.executionEnv."); } const leafId = await session.getLeafId(); if (!leafId) { throw new ParkRefusal("cannot suspend: session has no committed leaf to resume from"); } const { messages } = await session.buildContext(); const { batchToolCallIds, completedCallIds } = batchContextAt(messages, req.toolCallId); if (cutSignal.aborted) { return undefined; } if (suspendLoopCapHit(suspendChainBase(), maxSuspends, ` for tool "${req.toolName}" — likely a resume/restart loop.`)) return undefined; if (remoteEnv !== undefined) { if (hasBackgroundShell(remoteEnv)) await sweepBackgroundShells(remoteEnv, defaultTaskRegistry); const snap = await remoteEnv.suspendVM({ abortSignal: cutSignal }); if (!snap.ok) { remoteEnvFailures.push(remoteEnvFailureNote("suspendVM", snap.error, 1)); throw new Error(`suspendVM failed (${snap.error.code}): ${snap.error.message}`, { cause: snap.error }); } remoteHandle = { ...remoteEnv.workspaceHandle(), snapshotId: snap.value }; if (cutSignal.aborted) { const back = await compensatePausedVM(remoteEnv, snap.value); if (!back.ok) { abortController.abort(); void harness.abort(); } return undefined; } } else if (parkOnlyRemoteEnv !== undefined) { if (hasBackgroundShell(parkOnlyRemoteEnv)) await sweepRunBoundaryBackgroundShells(parkOnlyRemoteEnv, defaultTaskRegistry, { task: spec.retainBackgroundProcesses === true }); remoteHandle = parkOnlyHandle(parkOnlyRemoteEnv); } token = mintCheckpointToken(); const tierSeat = seatOf("park.riskDescriptor", irreversibilityTier, req); const riskDescriptor = buildRiskDescriptor({ toolName: req.toolName, args: parkedArgs, safety, ...(allowRuleShadowed !== undefined ? { allowRuleShadowed } : {}), ...(probeReason !== undefined ? { probeReason } : {}), ...(probeCause !== undefined ? { probeCause } : {}), shellGated: tierSeat?.shellGated(shellGatedTools, toolMarkSources) === true, ...(effectiveShellGate !== undefined ? { shellGateDoctrine: effectiveShellGate } : {}), }); gate = safety !== undefined || rowRealApproval !== undefined ? { kind: "irreversible_ask", reason: safety !== undefined ? `human approval required before safety-tightened tool "${req.toolName}"` : `real human approval required for tool "${req.toolName}" (non-budgetable: ${rowRealApproval.origin})`, toolName: req.toolName, ...(safety !== undefined ? { safetyAxis: safety } : {}), ...(rowRealApproval !== undefined ? { realApproval: rowRealApproval } : {}), riskDescriptor, } : durableApproval ? { kind: "human", reason: `human approval required for tool "${req.toolName}"`, toolName: req.toolName, riskDescriptor, } : { kind: "irreversible_ask", reason: `human approval required before irreversible tool "${req.toolName}"`, toolName: req.toolName, riskDescriptor, }; const scope = durableApproval?.scope || checkpointScopeOf({ principal: spec.principal }); const ttlMs = sanitizedTtlMs(durableApproval?.ttlMs); const checkpointState = serializeCheckpointState(remoteHandle, inFlightSpendMicroUsd()); if (rowRealApproval !== undefined && checkpointState.readFace !== undefined) { checkpointState.readFace = { ...checkpointState.readFace, realApproval: true }; } const approvalLedger = debitLedger(priorLedger, liveSpendRef.get?.() ?? { costMicroUsd: 0, tokens: 0, turns: 0, walltimeMs: 0 }, resourceTotal, { countSlice: false }); const rowMatchedAskRule = matchedAskRule ?? storedShapeFacts?.matchedAskRule; const storedShapeMandated = askMandateMember(storedShapeFacts).mandated === true; const clearanceFacts = { ...(carry?.mandated === true || storedShapeMandated ? { mandated: true } : {}), ...(carry?.mandate !== undefined && !storedShapeMandated ? { mandate: carry.mandate } : {}), ...(carry?.authoredBy === "deployment" ? { authoredBy: "deployment" } : {}), ...(rowRealApproval !== undefined ? { requiresRealApproval: true } : {}), ...(allowRuleShadowed !== undefined ? { allowRuleShadowed } : {}), ...(askDecisionReason !== undefined ? { decisionReason: askDecisionReason } : {}), ...(rowMatchedAskRule !== undefined ? { matchedAskRule: rowMatchedAskRule } : {}), ...(probeMandated === true ? { probeMandated: true } : {}), ...(inheritedUnavailableAsks.has(req.toolCallId) ? { inheritedUnresolved: true } : {}), ...(segmentCoverage !== undefined ? { segmentCoverage } : {}), }; const mintedAt = Date.now(); cp = { token, checkpointId: mintCheckpointId(), version: checkpointVersionFor({ resource: false, binding: true, token: approvalLedger.totalTokens !== undefined, orgAdmission: orgAdmissionCheckpointState(), f012: f012CheckpointState(), realApproval: rowRealApproval !== undefined, face: faceCheckpointState() }), scope, sessionId, leafId, ...pauseOf(gate, { kind: "tool_approval", toolCallId: req.toolCallId, toolName: req.toolName, ...parkedToolIdentity(toolRosterDeltas.current, req.toolName), ...parkedToolFace(toolRosterDeltas.current, req), args: parkedArgs, ...(() => { const p = approvalPreviewOf(req.toolName, parkedArgs); const bidi = carriesBidiControls(parkedArgs) || carriesBidiControls(p.preview); return { ...(p.preview !== undefined ? { preview: p.preview } : {}), ...(p.withheld !== undefined ? { previewWithheld: p.withheld } : {}), ...(bidi ? { hasBidiControls: true } : {}), }; })(), ...(rowRealApproval !== undefined ? { requiresRealApproval: true } : {}), ...askMandateMember(clearanceFacts), ...askCarryRowMembers(carry), ...ruleOffersOf(req, parkedArgs, clearanceFacts), boundInputHash: boundInputHashOf(parkedArgs), ...((grantDigest) => (grantDigest !== undefined ? { boundGrantDigest: grantDigest } : {}))(skillCallGrantDigest(req, parkedArgs, skillSpecs, SKILL_TOOL_NAME)), batchToolCallIds, completedCallIds, }), state: checkpointState, deadline: ttlMs !== undefined ? mintedAt + ttlMs : durableApproval !== undefined ? undefined : mintedAt + DEFAULT_UNATTENDED_APPROVAL_TTL_MS, status: "pending", createdAt: mintedAt, suspendedAt: now(), humanReview: humanReviewRef.count > 0 ? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] } : undefined, suspendCount: suspendChainBase() + 1, resourceLedger: approvalLedger, sourceTaskId: sessionId, ...(spec.principal ? { principal: spec.principal } : {}), ...(durableApproval !== undefined ? { durableApproval: { ...durableApproval } } : {}), }; } catch (err) { const cutWon = cutSignal.aborted; const preselected = cutWon ? undefined : { parkFailed: err instanceof ParkRefusal ? err.message : "the durable approval park could not be prepared (the deployment's error face carries the exception)", }; try { deps.onError?.(err, { phase: "config", sessionId }); } catch { } return preselected; } const committed = await commitSuspendSaga(token, cp, remoteEnv, remoteHandle, cutSignal); if (committed.tag !== "committed") { if (committed.tag === "cut") return undefined; if (committed.tag === "absent") { if (cutSignal.aborted) return undefined; return committed.reason !== undefined ? { parkFailed: committed.reason } : undefined; } return { parkFailed: committed.reason }; } publishCommittedSuspend({ pausedRef }, token, gate, cp.scope, remoteHandle, cp.checkpointId, cp.pendingAction); try { await sessions.pin?.(sessionId); } catch (pinErr) { deps.onError?.(pinErr, { phase: "config", sessionId }); } abortController.abort(); void harness.abort(); return { token, gate }; } : undefined; return { parkAsk: { resolveContentAsk, suspendAsk } }; } export function batchContextAt(messages, currentId) { let batch = []; for (const m of messages) { if (m.role === "assistant") { const ids = [...new Set(m.content.filter((c) => c.type === "toolCall").map((c) => c.id))]; if (ids.includes(currentId)) batch = ids; } } const resolved = new Set(messages.filter((m) => m.role === "toolResult").map((m) => m.toolCallId)); const completedCallIds = batch.filter((id) => id !== currentId && resolved.has(id)); return { batchToolCallIds: batch, completedCallIds }; }