@@ -262,6 +262,7 @@ func (c *executionController) handleSync(ctx *gin.Context) {
262262 writeExecutionError (ctx , err )
263263 return
264264 }
265+ plan .executionMode = "sync"
265266
266267 if plan .replayHit != nil {
267268 if err := c .completeReplayHit (reqCtx , plan ); err != nil {
@@ -692,6 +693,7 @@ func (c *executionController) handleAsync(ctx *gin.Context) {
692693 writeExecutionError (ctx , err )
693694 return
694695 }
696+ plan .executionMode = "async"
695697
696698 if plan .replayHit != nil {
697699 if err := c .completeReplayHit (reqCtx , plan ); err != nil {
@@ -984,8 +986,9 @@ func (c *executionController) handleStatusUpdate(ctx *gin.Context) {
984986 }
985987
986988 eventData := map [string ]interface {}{
987- "error" : req .Error ,
988- "progress" : req .Progress ,
989+ "error" : req .Error ,
990+ "progress" : req .Progress ,
991+ "transition_source" : "status_callback" ,
989992 }
990993 if req .StatusReason != nil && strings .TrimSpace (* req .StatusReason ) != "" {
991994 eventData ["status_reason" ] = strings .TrimSpace (* req .StatusReason )
@@ -1053,6 +1056,65 @@ func (c *executionController) publishExecutionEvent(exec *types.Execution, statu
10531056 c .publishExecutionEventWithReasonerInfo (exec , status , data , nil , nil )
10541057}
10551058
1059+ // enrichExecutionLifecycleData adds low-cardinality lifecycle dimensions used by
1060+ // observability consumers. It does not mutate execution state or include payloads.
1061+ func enrichExecutionLifecycleData (data map [string ]interface {}, exec * types.Execution , status string ) {
1062+ if data == nil || exec == nil {
1063+ return
1064+ }
1065+
1066+ data ["is_root_execution" ] = exec .ParentExecutionID == nil || strings .TrimSpace (* exec .ParentExecutionID ) == ""
1067+ if _ , ok := data ["workflow_depth" ]; ! ok {
1068+ if data ["is_root_execution" ] == true {
1069+ data ["workflow_depth" ] = 0
1070+ }
1071+ }
1072+ if exec .DurationMS != nil {
1073+ data ["duration_ms" ] = * exec .DurationMS
1074+ }
1075+
1076+ switch status {
1077+ case string (types .ExecutionStatusSucceeded ):
1078+ data ["outcome" ] = "succeeded"
1079+ case string (types .ExecutionStatusFailed ):
1080+ data ["outcome" ] = "failed"
1081+ data ["failure_category" ] = canonicalFailureCategory (exec .StatusReason , "unknown" )
1082+ case string (types .ExecutionStatusCancelled ):
1083+ data ["outcome" ] = "cancelled"
1084+ data ["failure_category" ] = "cancelled"
1085+ case string (types .ExecutionStatusTimeout ):
1086+ data ["outcome" ] = "timeout"
1087+ data ["failure_category" ] = "timeout"
1088+ }
1089+ }
1090+
1091+ func canonicalFailureCategory (statusReason * string , fallback string ) string {
1092+ if statusReason == nil {
1093+ return fallback
1094+ }
1095+ category := strings .TrimSpace (* statusReason )
1096+ if separator := strings .Index (category , ":" ); separator >= 0 {
1097+ category = strings .TrimSpace (category [:separator ])
1098+ }
1099+ switch category {
1100+ case string (ErrorCategoryLLMUnavailable ),
1101+ string (ErrorCategoryConcurrencyLimit ),
1102+ string (ErrorCategoryAgentTimeout ),
1103+ string (ErrorCategoryAgentError ),
1104+ string (ErrorCategoryAgentUnreachable ),
1105+ string (ErrorCategoryBadResponse ),
1106+ string (ErrorCategoryInternal ),
1107+ "agent_restart_orphaned" ,
1108+ "validation" ,
1109+ "permission_denied" ,
1110+ "node_unavailable" ,
1111+ "target_not_found" :
1112+ return category
1113+ default :
1114+ return fallback
1115+ }
1116+ }
1117+
10561118func (c * executionController ) publishExecutionEventWithReasonerInfo (exec * types.Execution , status string , data map [string ]interface {}, agent * types.AgentNode , reasonerID * string ) {
10571119 if exec == nil {
10581120 return
@@ -1074,6 +1136,7 @@ func (c *executionController) publishExecutionEventWithReasonerInfo(exec *types.
10741136 if data == nil {
10751137 data = make (map [string ]interface {})
10761138 }
1139+ enrichExecutionLifecycleData (data , exec , status )
10771140
10781141 // Add reasoner_id to the event data
10791142 rID := exec .ReasonerID
@@ -1114,6 +1177,7 @@ func (c *executionController) publishExecutionEventWithReasonerInfo(exec *types.
11141177 }
11151178 if workflowExec , err := c .store .GetWorkflowExecution (context .Background (), exec .ExecutionID ); err == nil && workflowExec != nil {
11161179 data ["retry_count" ] = workflowExec .RetryCount
1180+ data ["workflow_depth" ] = workflowExec .WorkflowDepth
11171181 }
11181182
11191183 // Add reasoner definitions if agent info is available
@@ -1200,7 +1264,9 @@ func (c *executionController) publishExecutionStartedEvent(plan *preparedExecuti
12001264 }
12011265
12021266 data := map [string ]interface {}{
1203- "target_type" : plan .targetType ,
1267+ "target_type" : plan .targetType ,
1268+ "execution_mode" : plan .executionMode ,
1269+ "transition_source" : "execution_controller" ,
12041270 }
12051271
12061272 // Include input payload info (not the full payload, just metadata)
@@ -1362,6 +1428,7 @@ type preparedExecution struct {
13621428 agent * types.AgentNode
13631429 target * parsedTarget
13641430 targetType string
1431+ executionMode string
13651432 llmEndpoint string
13661433 webhookRegistered bool
13671434 webhookError * string
@@ -1723,6 +1790,9 @@ func (c *executionController) completeReplayHit(ctx context.Context, plan *prepa
17231790 }
17241791
17251792 eventData := map [string ]interface {}{
1793+ "target_type" : plan .targetType ,
1794+ "execution_mode" : plan .executionMode ,
1795+ "transition_source" : "replay" ,
17261796 "replay" : map [string ]interface {}{
17271797 "source_execution_id" : plan .replayHit .SourceExecutionID ,
17281798 "source_run_id" : plan .replayHit .SourceRunID ,
@@ -1905,7 +1975,11 @@ func (c *executionController) completeExecution(ctx context.Context, plan *prepa
19051975 if plan .webhookRegistered || (updated != nil && updated .WebhookRegistered ) {
19061976 c .triggerWebhook (plan .exec .ExecutionID )
19071977 }
1908- eventData := map [string ]interface {}{}
1978+ eventData := map [string ]interface {}{
1979+ "target_type" : plan .targetType ,
1980+ "execution_mode" : plan .executionMode ,
1981+ "transition_source" : "execution_controller" ,
1982+ }
19091983 if ! c .redactPayloads {
19101984 if payload := decodeJSON (result ); payload != nil {
19111985 eventData ["result" ] = payload
@@ -1990,7 +2064,11 @@ func (c *executionController) failExecution(ctx context.Context, plan *preparedE
19902064 c .triggerWebhook (plan .exec .ExecutionID )
19912065 }
19922066 eventData := map [string ]interface {}{
1993- "error" : errMsg ,
2067+ "error" : errMsg ,
2068+ "target_type" : plan .targetType ,
2069+ "execution_mode" : plan .executionMode ,
2070+ "failure_category" : string (category ),
2071+ "transition_source" : "execution_controller" ,
19942072 }
19952073 if ! c .redactPayloads {
19962074 if payload := decodeJSON (result ); payload != nil {
0 commit comments