@@ -11,6 +11,7 @@ import {
1111 buildAgentCacheKeyInput ,
1212 createStubBudget ,
1313 type AgentIsolationMode ,
14+ type AgentCacheKeyInput ,
1415 type AgentOpts ,
1516 type AppendWorkflowEventInput ,
1617 type WorkflowMeta ,
@@ -64,10 +65,30 @@ export interface WorkflowReplayHit {
6465 responseText ?: string ;
6566 structuredJson ?: string ;
6667 providerSessionId ?: string ;
68+ replayMatch : "same_index" | "compatible_key" ;
69+ replayedFromRunId : string ;
70+ replayedFromCallIndex : number ;
6771}
6872
73+ export interface WorkflowReplayMiss {
74+ reason :
75+ | "no_compatible_call"
76+ | "prior_call_not_replayable"
77+ | "compatible_result_consumed"
78+ | "identity_changed" ;
79+ changedFields ?: Array < keyof AgentCacheKeyInput > ;
80+ }
81+
82+ export type WorkflowReplayDecision =
83+ | { hit : WorkflowReplayHit ; miss ?: never }
84+ | { hit ?: never ; miss : WorkflowReplayMiss } ;
85+
6986export interface WorkflowReplay {
70- match ( callIndex : number , cacheKey : string ) : WorkflowReplayHit | null ;
87+ decide (
88+ callIndex : number ,
89+ cacheKey : string ,
90+ input : AgentCacheKeyInput ,
91+ ) : WorkflowReplayDecision ;
7192}
7293
7394export interface WorkflowJournal {
@@ -78,13 +99,19 @@ export interface WorkflowJournal {
7899 runId : string ;
79100 callIndex : number ;
80101 cacheKey : string ;
102+ prompt : string ;
103+ schemaJson ?: string ;
81104 provider : LocalAgentProvider ;
82105 model ?: string ;
83106 effort ?: string ;
84107 label ?: string ;
85108 phase ?: string ;
86109 isolation ?: AgentIsolationMode ;
87110 worktreePath ?: string ;
111+ replayMatch ?: "same_index" | "compatible_key" ;
112+ replayedFromRunId ?: string ;
113+ replayedFromCallIndex ?: number ;
114+ replayReason ?: string ;
88115 } ) : unknown ;
89116 completeAgentCall ( input : {
90117 runId : string ;
@@ -100,6 +127,7 @@ export interface WorkflowJournal {
100127 runId : string ;
101128 callIndex : number ;
102129 error : string ;
130+ errorKind ?: import ( "./workflow-types.js" ) . WorkflowErrorKind ;
103131 worktreePath ?: string ;
104132 dirty ?: boolean ;
105133 } ) : unknown ;
@@ -233,19 +261,24 @@ export function createWorkflowApi(deps: WorkflowApiDeps): WorkflowApi {
233261 } ) ;
234262 const cacheKey = hashCacheKey ( cacheKeyInput ) ;
235263
236- if ( deps . replay ) {
237- const hit = deps . replay . match ( index , cacheKey ) ;
238- if ( hit ) {
264+ const replayDecision = deps . replay ?. decide ( index , cacheKey , cacheKeyInput ) ;
265+ if ( replayDecision ?. hit ) {
266+ const hit = replayDecision . hit ;
239267 deps . journal . beginAgentCall ( {
240268 runId : deps . runId ,
241269 callIndex : index ,
242270 cacheKey,
271+ prompt,
272+ schemaJson : agentOpts . schema ? JSON . stringify ( agentOpts . schema ) : undefined ,
243273 provider,
244274 model : agentOpts . model ,
245275 effort : agentOpts . effort ,
246276 label : agentOpts . label ,
247277 phase,
248278 isolation,
279+ replayMatch : hit . replayMatch ,
280+ replayedFromRunId : hit . replayedFromRunId ,
281+ replayedFromCallIndex : hit . replayedFromCallIndex ,
249282 } ) ;
250283 deps . journal . completeAgentCall ( {
251284 runId : deps . runId ,
@@ -260,10 +293,16 @@ export function createWorkflowApi(deps: WorkflowApiDeps): WorkflowApi {
260293 type : "agent_call_cached" ,
261294 phase,
262295 label : agentOpts . label ,
263- data : { callIndex : index , cacheKey, provider } ,
296+ data : {
297+ callIndex : index ,
298+ cacheKey,
299+ provider,
300+ replayMatch : hit . replayMatch ,
301+ replayedFromRunId : hit . replayedFromRunId ,
302+ replayedFromCallIndex : hit . replayedFromCallIndex ,
303+ } ,
264304 } ) ;
265305 return hit . value ;
266- }
267306 }
268307
269308 await semaphore . acquire ( deps . signal ) ;
@@ -300,13 +339,18 @@ export function createWorkflowApi(deps: WorkflowApiDeps): WorkflowApi {
300339 runId : deps . runId ,
301340 callIndex : index ,
302341 cacheKey,
342+ prompt,
343+ schemaJson : agentOpts . schema ? JSON . stringify ( agentOpts . schema ) : undefined ,
303344 provider,
304345 model : agentOpts . model ,
305346 effort : agentOpts . effort ,
306347 label : agentOpts . label ,
307348 phase,
308349 isolation,
309350 worktreePath,
351+ replayReason : replayDecision ?. miss
352+ ? formatReplayMiss ( replayDecision . miss )
353+ : undefined ,
310354 } ) ;
311355 agentCallBegun = true ;
312356 deps . journal . appendEvent ( {
@@ -453,6 +497,7 @@ export function createWorkflowApi(deps: WorkflowApiDeps): WorkflowApi {
453497 runId : deps . runId ,
454498 callIndex : index ,
455499 error : message ,
500+ errorKind : error instanceof WorkflowEngineError ? error . kind : "internal" ,
456501 worktreePath,
457502 } ) ;
458503 }
@@ -594,6 +639,12 @@ export function createWorkflowApi(deps: WorkflowApiDeps): WorkflowApi {
594639 } ;
595640}
596641
642+ function formatReplayMiss ( miss : WorkflowReplayMiss ) : string {
643+ return miss . reason === "identity_changed" && miss . changedFields ?. length
644+ ? `${ miss . reason } :${ miss . changedFields . join ( "," ) } `
645+ : miss . reason ;
646+ }
647+
597648/** Test helper: read current ALS phase (undefined outside phase). */
598649export function getCurrentWorkflowPhase ( ) : string | undefined {
599650 return phaseAls . getStore ( ) ;
0 commit comments