From 6138c89a289660b69008c61760775aa5ef48750a Mon Sep 17 00:00:00 2001 From: benpankow Date: Wed, 29 Jul 2026 14:04:12 -0700 Subject: [PATCH 1/2] fix(eve): emit candidate outcomes after persistence Signed-off-by: benpankow --- .../eve/src/harness/approval-candidates.ts | 20 ++++++++++ packages/eve/src/harness/tool-loop.ts | 40 +++++++++++-------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/packages/eve/src/harness/approval-candidates.ts b/packages/eve/src/harness/approval-candidates.ts index 218595531..57494b3ba 100644 --- a/packages/eve/src/harness/approval-candidates.ts +++ b/packages/eve/src/harness/approval-candidates.ts @@ -19,6 +19,7 @@ export interface ApprovalCandidateAuditRecord { readonly status: ApprovalCandidateStatus; readonly createdAt: number; readonly completedAt?: number; + readonly eventEmitted?: boolean; readonly expiresAt?: number; readonly provider?: string; readonly runtimeRevision?: string; @@ -135,6 +136,25 @@ export function markApprovalCandidatePendingEventEmitted(input: { }); } +/** Marks a terminal candidate history event as emitted. */ +export function markApprovalCandidateHistoryEventEmitted(input: { + readonly candidateId: string; + readonly state: SessionStateMap | undefined; +}): SessionStateMap | undefined { + const approvalState = readApprovalState(input.state); + let changed = false; + const candidateHistory = approvalState.candidateHistory.map((candidate) => { + if (candidate.candidateId !== input.candidateId || candidate.eventEmitted === true) { + return candidate; + } + changed = true; + return { ...candidate, eventEmitted: true }; + }); + return changed + ? writeApprovalState(input.state, { ...approvalState, candidateHistory }) + : input.state; +} + /** Marks a terminal settlement event as emitted. */ export function markApprovalSettlementEventEmitted(input: { readonly requestId: string; diff --git a/packages/eve/src/harness/tool-loop.ts b/packages/eve/src/harness/tool-loop.ts index f0884537f..d5c959d54 100644 --- a/packages/eve/src/harness/tool-loop.ts +++ b/packages/eve/src/harness/tool-loop.ts @@ -106,6 +106,7 @@ import { createToolResultMessagePartFromToolError } from "#harness/action-result import { buildTelemetryRuntimeContext } from "#harness/instrumentation-runtime-context.js"; import { getApprovalAuditState, + markApprovalCandidateHistoryEventEmitted, markApprovalCandidatePendingEventEmitted, markApprovalSettlementEventEmitted, } from "#harness/approval-candidates.js"; @@ -606,29 +607,36 @@ export function createToolLoopHarness(config: ToolLoopHarnessConfig): StepFn { }; } - if ( - emit && - authorized.kind !== "continue" && - authorized.kind !== "duplicate" && - authorized.kind !== "authorization-required" - ) { + const pendingCandidateHistoryEvent = getApprovalAuditState(session.state).candidateHistory.find( + (candidate) => + candidate.eventEmitted !== true && + candidate.status !== "allowed" && + candidate.status !== "authorization-required", + ); + if (emit && pendingCandidateHistoryEvent !== undefined) { await emit( createApprovalCandidateEvent({ - candidateId: authorized.candidateId ?? `stale:${authorized.requestId}`, - outcome: authorized.kind, - requestId: authorized.requestId, - responderPrincipalId: - [ - ...getApprovalAuditState(session.state).activeCandidates, - ...getApprovalAuditState(session.state).candidateHistory, - ].find((candidate) => candidate.candidateId === authorized.candidateId)?.responder - .principalId ?? "unknown", - safeReason: "safeReason" in authorized ? authorized.safeReason : undefined, + candidateId: pendingCandidateHistoryEvent.candidateId, + outcome: pendingCandidateHistoryEvent.status as Exclude< + typeof pendingCandidateHistoryEvent.status, + "allowed" | "authorization-required" + >, + requestId: pendingCandidateHistoryEvent.requestId, + responderPrincipalId: pendingCandidateHistoryEvent.responder.principalId, + safeReason: pendingCandidateHistoryEvent.safeReason, sequence: emissionState.sequence, stepIndex: emissionState.stepIndex, turnId: emissionState.turnId, }), ); + session = { + ...session, + state: markApprovalCandidateHistoryEventEmitted({ + candidateId: pendingCandidateHistoryEvent.candidateId, + state: session.state, + }), + }; + return { next: runStep, session }; } if (authorized.kind === "authorization-required") { const { challenges } = authorized.authorization; From e26b7863e7a0d266ab2a71f347a169610cf6d18a Mon Sep 17 00:00:00 2001 From: benpankow Date: Wed, 29 Jul 2026 14:07:30 -0700 Subject: [PATCH 2/2] fix(eve): continue after durable candidate feedback Signed-off-by: benpankow --- packages/eve/src/harness/tool-loop.ts | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/eve/src/harness/tool-loop.ts b/packages/eve/src/harness/tool-loop.ts index d5c959d54..952a280d2 100644 --- a/packages/eve/src/harness/tool-loop.ts +++ b/packages/eve/src/harness/tool-loop.ts @@ -586,18 +586,20 @@ export function createToolLoopHarness(config: ToolLoopHarnessConfig): StepFn { const pendingCandidateEvent = getApprovalAuditState(session.state).activeCandidates.find( (candidate) => candidate.pendingEventEmitted !== true, ); - if (emit && pendingCandidateEvent !== undefined) { - await emit( - createApprovalCandidateEvent({ - candidateId: pendingCandidateEvent.candidateId, - outcome: "pending", - requestId: pendingCandidateEvent.requestId, - responderPrincipalId: pendingCandidateEvent.responder.principalId, - sequence: emissionState.sequence, - stepIndex: emissionState.stepIndex, - turnId: emissionState.turnId, - }), - ); + if (pendingCandidateEvent !== undefined) { + if (emit) { + await emit( + createApprovalCandidateEvent({ + candidateId: pendingCandidateEvent.candidateId, + outcome: "pending", + requestId: pendingCandidateEvent.requestId, + responderPrincipalId: pendingCandidateEvent.responder.principalId, + sequence: emissionState.sequence, + stepIndex: emissionState.stepIndex, + turnId: emissionState.turnId, + }), + ); + } session = { ...session, state: markApprovalCandidatePendingEventEmitted({ @@ -605,6 +607,7 @@ export function createToolLoopHarness(config: ToolLoopHarnessConfig): StepFn { state: session.state, }), }; + return { next: runStep, session }; } const pendingCandidateHistoryEvent = getApprovalAuditState(session.state).candidateHistory.find(