Emit session dropped event when stuck workflow is cleaned up
What does this MR do and why?
Emits agent_platform_session_dropped when CleanStuckWorkflowsService force-fails a stuck Duo Agent Platform session, so those sessions stop disappearing from completion-rate reporting.
CleanStuckWorkflowsService calls w.drop directly on the model. Every other transition to failed goes through UpdateWorkflowStatusService, which is the only place that emits the terminal analytics event. So a stuck-cleaned session was recorded as created plus started, and then nothing.
That matters because agent_platform_session_dropped is AI usage event id 20, which populates dropped_event_at in the ClickHouse agent_platform_sessions materialized view backing the completion_rate metric. The service does already emit cleanup_stuck_agent_platform_session, but that event is not in the Gitlab::Tracking::AiTracking registry and AiTracking.track_event returns early on unregistered events, so it reaches Snowplow only and never contributes a terminal state.
Net effect of this change: stuck sessions are now counted as dropped, and the completion rate reflects them.
Scope notes
- No new event definition.
ee/config/events/agent_platform_session_dropped.ymlalready exists. - No ClickHouse migration.
agent_platform_sessions_mvalready mapsevent = 20todropped_event_atand already filtersevent IN (8, 9, 19, 20, 21, 22). - No double counting. If the executor later PATCHes
dropon an already-failed session,UpdateWorkflowStatusServicereturns early before emitting, and the MV'sanyIfaggregation would collapse duplicates regardless. - This makes stuck sessions countable going forward. It does not retroactively explain the whole unaccounted bucket in the dashboards, which is also fed by sessions still in flight at query time and by abandoned
input_required/pausedsessions (those transitions emit no analytics event either). Historical numbers will not reconcile.
Redis counter batching (added after review)
The execute loop is now wrapped in Gitlab::InternalEvents.with_batched_redis_writes. Every internal event gets two default total-counter selection rules regardless of metric definitions (Gitlab::Tracking::EventDefinition#find_event_selection_rules), so track_fail does 4 Redis increments per workflow, up from 2 before this MR.
flush_redis_batch issues one write per unique key, and the keys are {event_counters}_<event_name> plus a week suffix, so they are identical for every workflow in a run. Batching per workflow would therefore re-flush the same 4 keys each time and save nothing; batching around the loop collapses 4 × N into 4. Verified locally: 3 stale sessions produce 12 deferred increments and 4 flushed keys.
Trade-off worth knowing: counter increments now flush once at the end of the run, so a hard kill mid-run loses that run's increments rather than just the tail. Sidekiq shuts down gracefully and the flush is in an ensure, and these are non-critical total counters, so this seems an acceptable trade.
Known gap, deliberately not fixed here
Concerns::WorkflowEventTracking#track_workflow_event passes only user and project, so namespace-level sessions lose namespace attribution. That is pre-existing and shared with UpdateWorkflowStatusService and CreateWorkflowService, so changing it would alter data for five other events. I reused the helper for consistency and added a spec asserting the current behaviour so it stays a known gap rather than an accident. Happy to split a follow-up if agent execution wants it fixed.
Why no changelog
Internal analytics instrumentation only. No user-facing behaviour change.
How to set up and validate locally
bundle exec rspec ee/spec/services/ai/duo_workflows/clean_stuck_workflows_service_spec.rb
bundle exec rspec ee/spec/workers/ai/duo_workflows/fail_stuck_workflows_worker_spec.rbTo confirm the assertions are not vacuous, stash the service change and re-run: three examples fail without it.
Manual check in a GDK:
- Create a project-level workflow and start it.
- Backdate
updated_atpast 30 minutes. - Run
Ai::DuoWorkflows::FailStuckWorkflowsWorker.new.perform. - Confirm an
Ai::UsageEventrow exists withevent: :agent_platform_session_dropped. Before this change there is none.
References
- #608249 (closed)
- Related SLI issue: gitlab-com/runbooks#238