UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

370 lines (369 loc) • 20.4 kB
import { observeThenableRejection } from "../safe-notify.js"; import { forwardSafely } from "./forward-sink.js"; import { emitTrace } from "../trace.js"; import { primaryActivityArg } from "../arg-summary.js"; import { redactSecrets } from "../untrusted-egress.js"; import { readDegradation } from "../../brain/degrading.js"; import { OUTPUT_TOOL_NAME } from "./synthetic-tools.js"; import { usageCostMicroUsd } from "./usage-accounting.js"; import { advanceCadenceClock, reduceToolEnd } from "./turn-attachments.js"; import { gatedCallIdOf } from "./park-commit.js"; import { hasVerifiableStructureSignal } from "./grounding-signal.js"; import { batchResponseDigest, toolEndBodyFrom } from "./tool-end-body.js"; import { createResponseModelSeat } from "./response-model-seat.js"; import { createToolCallIdFoldSeat } from "./tool-call-id-fold-seat.js"; import { createToolTurnIncompleteSeat } from "./tool-turn-repair-seat.js"; import { wireOf } from "../tool-name-readings.js"; import { rosterToolName } from "../tool-roster.js"; export function createHarnessHandlers(input) { const { prepared, stats, rs, deps } = input; const responseModelSeat = createResponseModelSeat(deps.runnerHooks.onNotice, { sessionId: prepared.sessionId, runId: prepared.runId }); const toolCallIdFoldSeat = createToolCallIdFoldSeat(deps.runnerHooks.onNotice, { sessionId: prepared.sessionId, runId: prepared.runId }); const toolTurnIncompleteSeat = createToolTurnIncompleteSeat(deps.runnerHooks.onNotice, { sessionId: prepared.sessionId, runId: prepared.runId }); const { spec, queue, internals, ident, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, toolProgress, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs, internalsNotifier } = deps; let lastWorkspaceCwd = prepared.cwdRef?.current; const announceWorkspaceMove = () => { const cwdNow = prepared.cwdRef?.current; if (cwdNow !== undefined && cwdNow !== lastWorkspaceCwd) { lastWorkspaceCwd = cwdNow; queue.push({ type: "workspace_changed", cwd: cwdNow, ...ident() }); } }; const onMessageUpdate = (event) => { const ev = event.assistantMessageEvent; if (rs.turn.callStartAt === undefined) rs.turn.callStartAt = Date.now(); if (ev.type === "text_delta" && ev.delta) { if (rs.turn.firstTokenAt === undefined) rs.turn.firstTokenAt = Date.now(); pushContent({ type: "text_delta", delta: ev.delta, ...ident() }); } else if (ev.type === "text_end" && typeof ev.content === "string" && ev.content.length !== 0) { pushContent({ type: "text_end", content: ev.content, ...ident() }); } else if (ev.type === "thinking_delta" && ev.delta) { pushContent({ type: "reasoning_delta", delta: ev.delta, ...ident() }); } if (rs.budget.streamCancel && !rs.limits.budgetHit && (ev.type === "text_delta" || ev.type === "thinking_delta") && ev.delta) { rs.budget.callOutputChars += ev.delta.length; if (rs.budget.callOutputChars - rs.budget.lastStreamBudgetCheck >= 256) { rs.budget.lastStreamBudgetCheck = rs.budget.callOutputChars; const projectedAxis = rs.budget.projectedOverBudget(); if (projectedAxis !== undefined) { rs.limits.budgetHit = "exceeded"; rs.limits.budgetAxis = projectedAxis; void prepared.harness.abort(); prepared.abortController.abort(); } } } }; const onMessageEnd = (event) => { const m = event.message; if (event.entryId !== undefined && (m.role === "user" || m.role === "assistant" || m.role === "toolResult")) { emitCommitted(event.entryId, m.role, m.role === "toolResult" ? m.toolCallId : undefined); } if (event.entryId !== undefined && m.role === "user") prepared.fileHistoryBoundary?.begin(event.entryId); if (event.entryId !== undefined && m.role === "user" && prepared.frameReceipts.pending.length > 0) { const c = m.content; const text = typeof c === "string" ? c : Array.isArray(c) && c.length >= 1 && c[0].type === "text" ? c[0].text : undefined; if (text !== undefined) { const settled = prepared.frameReceipts.pending.filter((r) => r.text === text); if (settled.length > 0) { prepared.frameReceipts.pending = prepared.frameReceipts.pending.filter((r) => r.text !== text); for (const r of settled) r.commit(event.entryId); } } } if (rs.degrade.degraded === undefined && m.role === "assistant") { const deg = readDegradation(m); if (deg) rs.degrade.recordDegraded({ from: deg.from, to: deg.to, reason: deg.reason, atTurn: stats.turns + 1, ...(deg.chain && { chain: deg.chain }) }); } if (m.role === "assistant" && "usage" in m && m.usage) { if (m.usageMissing) { rs.turn.turnUsageMissing = true; stats.usageMissing = true; } const u = m.usage; stats.tokens += u.totalTokens || 0; const { totalInputTokens: turnTotal, uncachedInputTokens: turnUncached, costMicroUsd: turnCostMicroUsd, } = usageCostMicroUsd(rs.telemetry.cacheFamily, u, rs.telemetry.pricing); if (!rs.telemetry.pricingConfigured) rs.telemetry.unpricedSpend = true; stats.promptTokens += turnUncached; stats.totalInputTokens += turnTotal; stats.cachedTokens += u.cacheRead || 0; stats.cacheWriteTokens += u.cacheWrite || 0; stats.outputTokens += u.output || 0; stats.costMicroUsd += turnCostMicroUsd; rs.turn.turnUsage ??= { inputTokens: 0, totalInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costMicroUsd: 0 }; rs.turn.turnUsage.inputTokens += turnUncached; rs.turn.turnUsage.totalInputTokens += turnTotal; rs.turn.turnUsage.outputTokens += u.output || 0; rs.turn.turnUsage.cacheReadTokens += u.cacheRead || 0; rs.turn.turnUsage.cacheWriteTokens += u.cacheWrite || 0; rs.turn.turnUsage.costMicroUsd += turnCostMicroUsd; const callEndAt = Date.now(); const callIssuedAt = prepared.callIssuedAtRef.current; prepared.callIssuedAtRef.current = undefined; emitTrace(rs.telemetry.tracer, () => ({ kind: "brain.call", version: 1, taskId: rs.telemetry.taskId, model: m.model, provider: m.provider, turn: stats.turns + 1, promptTokens: turnUncached, totalInputTokens: turnTotal, completionTokens: u.output || 0, cacheRead: u.cacheRead || 0, cacheWrite: u.cacheWrite || 0, latencyMs: rs.turn.callStartAt !== undefined ? callEndAt - rs.turn.callStartAt : 0, firstTokenMs: rs.turn.callStartAt !== undefined && rs.turn.firstTokenAt !== undefined ? rs.turn.firstTokenAt - rs.turn.callStartAt : undefined, ...(callIssuedAt !== undefined ? { callStartedAt: callIssuedAt } : {}), ...(rs.telemetry.pricingConfigured ? { costMicroUsd: turnCostMicroUsd } : {}), ...(m.role === "assistant" && typeof m.stopReason === "string" ? { stopReason: m.stopReason } : {}), ts: callEndAt, })); const cacheRowUnknown = m.usageMissing === true || m.stopReason === "aborted"; if (prepared.cacheBreakDetector && prepared.cacheFingerprint && !cacheRowUnknown) { const finding = prepared.cacheBreakDetector.observe({ turn: stats.turns + 1, systemPrompt: prepared.cacheFingerprint.systemPrompt, tools: prepared.cacheFingerprint.tools, modelKey: `${m.provider}:${m.model}`, cacheRead: u.cacheRead || 0, }); if (finding) { rs.telemetry.cacheBreakReported = true; deps.runnerHooks.onError?.(new Error(`prompt-cache: break at turn ${finding.turn} — cache_read ${finding.cacheReadBefore}→${finding.cacheReadAfter}; ${finding.detail}. See design/31.`), { phase: "prompt-cache", sessionId: prepared.sessionId, classification: finding.cause }); } } } if (m.role === "assistant" && typeof m.stopReason === "string") { rs.turn.turnStopReason = m.stopReason; } if (m.role === "assistant") { rs.turn.turnResponseModel = responseModelSeat.observe(m); toolCallIdFoldSeat.observe(m); toolTurnIncompleteSeat.observe(m); } if (m.role === "assistant") { const rep = m.repetition; if (rep !== undefined) { const observed = [ ...(rep.cut !== undefined ? [{ action: "cut", e: rep.cut }] : []), ...(rep.spared ?? []).map((e) => ({ action: "spared", e })), ]; for (const { action, e } of observed) { if (action === "cut") rs.counters.repetitionCuts += 1; else rs.counters.repetitionSpared += 1; const segment = redactSecrets(e.segment); if (rs.counters.repetitionEvents.length < rs.counters.REPETITION_EVENTS_CAP) { rs.counters.repetitionEvents.push({ turn: stats.turns + 1, action, rule: e.rule, period: e.period, reps: e.reps, segment }); } emitTrace(rs.telemetry.tracer, () => ({ kind: "repetition.detected", version: 1, taskId: rs.telemetry.taskId, turn: stats.turns + 1, action, rule: e.rule, period: e.period, reps: e.reps, segment, ts: Date.now(), })); } } } if (m.role === "assistant") { const content = m.content; if (rs.attach.attachState !== undefined) rs.counters.cadenceTurns = advanceCadenceClock(rs.attach.attachState, rs.counters.cadenceTurns, content, writeFamilyOf); rs.turn.lastTurnHadToolCalls = Array.isArray(content) && content.some((c) => c?.type === "toolCall"); } rs.turn.callStartAt = undefined; rs.turn.firstTokenAt = undefined; rs.budget.callOutputChars = 0; rs.budget.lastStreamBudgetCheck = 0; }; const onToolStart = (event) => { const startAt = Date.now(); toolStartAt.set(event.toolCallId, startAt); startedToolCallIds.add(event.toolCallId); toolProgress.noteStart(event.toolCallId, event.toolName); batchArgs?.set(event.toolCallId, event.args); stats.toolCalls += 1; if (spec.finalVerification === true && !(rs.counters.finalVerifyInjections === 0 ? rs.counters.groundingSignalPreR9 : rs.counters.groundingSignalPostR9) && hasVerifiableStructureSignal(event.args)) { if (rs.counters.finalVerifyInjections === 0) rs.counters.groundingSignalPreR9 = true; else rs.counters.groundingSignalPostR9 = true; } const activityArg = primaryActivityArg(event.args); internalsNotifier.notify(() => observeThenableRejection(internals?.onActivity?.({ phase: "start", toolCallId: event.toolCallId, toolName: event.toolName, at: startAt, ...(activityArg !== undefined ? { arg: activityArg } : {}) }), internalsNotifier, "runtask.onActivity.start"), "runtask.onActivity.start"); pushContent({ type: "tool_start", toolCallId: event.toolCallId, toolName: event.toolName, ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}), args: event.args, ...ident(), }); }; const onToolEnd = (event) => { const toolStarted = toolStartAt.get(event.toolCallId); toolStartAt.delete(event.toolCallId); toolProgress.noteEnd(event.toolCallId); const toolNow = Date.now(); if (!rs.counters.wroteThisRun && (prepared.toolEffects.get(wireOf("verify.wroteThisRun", { toolName: rosterToolName(prepared.toolRosterDeltas.current, event.toolName) })) ?? "write") !== "read") { rs.counters.wroteThisRun = true; } if (rs.attach.attachState !== undefined && !event.isError) { const det = event.result?.details; if (det !== undefined) reduceToolEnd(rs.attach.attachState, det); } if (postToolBatchHook !== undefined) { if (prepared.blockedToolCalls.delete(event.toolCallId) || event.notExecuted === true) { batchArgs?.delete(event.toolCallId); } else { const digest = batchResponseDigest(event.result); rs.turn.toolBatch.push({ toolName: event.toolName, input: batchArgs?.get(event.toolCallId), toolCallId: event.toolCallId, ...(digest !== undefined ? { response: digest } : {}), isError: event.isError, }); batchArgs?.delete(event.toolCallId); } } emitTrace(rs.telemetry.tracer, () => ({ kind: "tool.call", version: 1, taskId: rs.telemetry.taskId, name: event.toolName, toolCallId: event.toolCallId, turn: stats.turns + 1, durationMs: toolStarted !== undefined ? toolNow - toolStarted : 0, ok: !event.isError, ts: toolNow, })); internalsNotifier.notify(() => observeThenableRejection(internals?.onActivity?.({ phase: "end", toolCallId: event.toolCallId, toolName: event.toolName, isError: event.isError, at: toolNow }), internalsNotifier, "runtask.onActivity.end"), "runtask.onActivity.end"); if (prepared.lspDiagnostics && !event.isError) { const name = event.toolName; if (name === "Edit" || name === "Write" || name === "NotebookEdit") { const d = event.result?.details; const p = typeof d?.filePath === "string" ? d.filePath : typeof d?.notebookPath === "string" ? d.notebookPath : undefined; if (p) prepared.lspDiagnostics.nudge(p); } } const gate = prepared.gateOutcomes.get(event.toolCallId); if (gate !== undefined) prepared.gateOutcomes.delete(event.toolCallId); pushContent({ type: "tool_end", toolCallId: event.toolCallId, toolName: event.toolName, ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}), isError: event.isError, ...toolEndBodyFrom(event.result, event.isError, prepared.structuredProjector, gate, prepared.abortController.signal.aborted ? gatedCallIdOf(prepared) : undefined), ...ident(), }); announceWorkspaceMove(); if (spec.outputSchema && event.toolName === OUTPUT_TOOL_NAME) { if (event.isError) { rs.degrade.outputErrorStreak += 1; if (rs.degrade.outputErrorStreak > rs.limits.outputRetryCap) { rs.degrade.outputInvalid = true; void prepared.harness.abort(); prepared.abortController.abort(); } } else { rs.degrade.outputErrorStreak = 0; } } }; const onTurnEnd = () => { stats.turns += 1; if (prepared.abortController.signal.aborted) prepared.workCutRef.abortedBeforeTurnEnd = true; const stopExtra = rs.turn.turnStopReason !== undefined ? { stopReason: rs.turn.turnStopReason } : {}; const servedExtra = rs.turn.turnResponseModel !== undefined ? { responseModel: rs.turn.turnResponseModel } : {}; const usage = rs.turn.turnUsage ?? { inputTokens: 0, totalInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costMicroUsd: 0 }; const turnUsageUnknown = rs.turn.turnUsage === undefined || rs.turn.turnUsageMissing; if (turnUsageUnknown) stats.usageMissing = true; queue.push({ type: "turn_end", usage, ...(turnUsageUnknown ? { usageMissing: true } : {}), ...stopExtra, ...servedExtra, ...ident() }); rs.turn.turnUsageMissing = false; rs.turn.turnStopReason = undefined; rs.turn.turnResponseModel = undefined; if (parentToolCallId !== undefined) { const progress = { type: "task_progress", taskId: rs.telemetry.taskId, ...(internals?.delegationTaskType !== undefined ? { taskType: internals.delegationTaskType } : {}), ...(internals?.cycleSeq !== undefined ? { seq: internals.cycleSeq } : {}), ...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}), ...(subagentName ? { name: subagentName } : {}), ...(internals?.explicitAgentName !== undefined ? { spawnName: internals.explicitAgentName } : {}), model: prepared.model.id, usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart }, status: "running", ...ident(), }; queue.push(progress); forwardSafely(internals, progress); } emitTrace(rs.telemetry.tracer, () => ({ kind: "turn.end", version: 1, taskId: rs.telemetry.taskId, turn: stats.turns, ts: Date.now() })); rs.turn.turnUsage = undefined; rs.turn.callStartAt = undefined; rs.turn.firstTokenAt = undefined; rs.budget.callOutputChars = 0; rs.budget.lastStreamBudgetCheck = 0; toolStartAt.clear(); toolProgress.clear(); if (rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns) { rs.limits.turnsExceeded = true; if (prepared.suspendForResource === undefined) { void prepared.harness.abort(); prepared.abortController.abort(); } } else { if (rs.degrade.degraded === undefined && spec.limits?.degrade && rs.degrade.degradeToModel && rs.budget.maxCostMicroUsd !== undefined && stats.costMicroUsd >= spec.limits.degrade.atCostFraction * rs.budget.maxCostMicroUsd) { void prepared.harness.setModel(rs.degrade.degradeToModel, spec.limits.degrade.to); rs.degrade.recordDegraded({ from: prepared.model.id, to: rs.degrade.degradeToModel.id, reason: "budget", atTurn: stats.turns }, rs.degrade.degradeToModel); } const overAxis = rs.limits.budgetHit ? undefined : rs.budget.overBudget(); if (overAxis !== undefined) { rs.limits.budgetHit = "exceeded"; rs.limits.budgetAxis = overAxis; if (prepared.suspendForResource === undefined) { void prepared.harness.abort(); prepared.abortController.abort(); } } } }; return { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, announceWorkspaceMove }; }