fix(server): refresh provider session lastSeenAt on turn and task activity - #5524
fix(server): refresh provider session lastSeenAt on turn and task activity#5524kraptor23 wants to merge 2 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
One finding: the new activity-touch warning log serializes the raw cause into the log payload instead of a normalized error tag.
Posted via Macroscope — Effect Service Conventions
ApprovabilityVerdict: Needs human review This PR changes session lifecycle behavior by updating lastSeenAt timestamps based on turn/task events, affecting how the inactivity reaper determines session idleness. While well-tested and throttled, this runtime behavior change to session management warrants human review. You can customize Macroscope's approvability policy. Learn more. |
…ivity The inactivity reaper measures idleness from provider_session_runtime .last_seen_at, but that column was only written by startSession / recoverSessionForThread / sendTurn / stopSession. Turns delivered without a sendTurn call (queued follow-ups, plan-sourced turns) and background tasks never refreshed it, so the reaper kills sessions minutes after real work finished (pingdotgg#4198). Treat turn and task lifecycle runtime events as session activity: a new ProviderSessionDirectory.touch updates only last_seen_at for an existing binding (never creates rows), and ProviderService.processRuntimeEvent touches the binding on turn.started/completed/aborted and task.started/progress/completed. Touch failures log a warning and never disrupt event fan-out. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lures as error tags Review feedback: Codex background tasks transition through task.updated without necessarily emitting task.progress, so include it in the activity set; log the normalized causeErrorTag instead of the raw cause, matching the runStopAll finalizer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
95cd9c1 to
0fadabd
Compare
What
Refreshes
provider_session_runtime.last_seen_atwhen the adapter emits turn or background-task lifecycle events, so the inactivity reaper measures idleness from the last completed work instead of the last explicitsendTurn.Addresses the staleness half of #4198.
Why
last_seen_atis only written bystartSession/recoverSessionForThread/sendTurn/stopSession. Turns delivered without asendTurncall (queued follow-ups, plan-sourced turns) and background tasks never refresh it. Observed on0.0.32-nightly.20260805.1006: one thread was reaped 6 times in ~12 hours, three of those 105 s - 4 m 53 s after a turn had actually completed — the reaper was measuring idleness from alast_seen_atseveral turns stale, exactly what the reporters in #4198 back-computed from their logs.How
ProviderSessionDirectory.touch(threadId)updates onlylast_seen_atfor an existing binding via a new repositorytouchByThreadId(UPDATE ... WHERE thread_id). It never creates rows, and preserves every other column.ProviderService.processRuntimeEventtouches the binding onturn.started/completed/abortedandtask.started/progress/completed, after event fan-out.stopSession) because this runs on the event drain fiber andtask.progresscan fire many times per second in multi-agent sessions; reaper thresholds are minutes, so minute-level granularity is sufficient. The gate timestamp is set before the write so a failing database is not retried per event.provider.session.activity-touch-failedand never disturb fan-out.This deliberately does not add a busy-guard for in-flight background tasks (discussed in #4198) — that is a separate lifecycle change; this PR only fixes the idle clock.
Tests
ProviderService.test.ts: every activity event type refreshes the binding'slastSeenAt;content.deltadoes not; a second boundary inside the throttle window does not write again.ProviderSessionDirectory.test.ts:touchrefresheslastSeenAtand leaves all other fields unchanged;touchon an unknown thread creates no row.vp test runon the five touched test files (97 tests) andtsgo --noEmitpass.Prior art and non-goals
This addresses the liveness-staleness half of #4198. Back-computing the reaper's logged
idleDurationMsagainst turn timestamps shows every incident reported there is an event-emitting case this PR covers: ordinary provider-started turns that never refreshedlast_seen_at(NoorChasib's table — turns at 06:08 and 06:17 withlast_seen_atstuck at 05:49), and background tasks killed seconds after their foreground turn settled (Mooned8, Zeus-Deus —task.started/turn.completedboth now touch the binding).Earlier attempts in this area (#4199, #3856, #4994) were closed for orchestration-V2 roadmap reasons rather than on the mechanism, and #2829 is still open — this PR is deliberately the smallest reviewable slice of that idea: one new directory method, one call site on the existing event drain, throttled, with no lifecycle behavior changes.
Non-goals, intentionally out of scope here:
backgroundLivenessthread-shell state;lastSeenAttrustworthy.Written by Claude Fable 5 via Claude Code.
Note
Medium Risk
Changes when sessions are considered idle for reaping (core provider lifecycle), but scope is narrow—targeted DB updates with throttling and non-blocking failure handling.
Overview
Provider session
lastSeenAtnow advances when adapters emit turn or background-task lifecycle events, not only onstartSession,sendTurn, orstopSession. That aligns the inactivity reaper with last completed work (queued turns, plan-driven work, long-running tasks) instead of the last explicit user send.A
touchpath updates onlylast_seen_atvia repositorytouchByThreadId(UPDATE … WHERE thread_id); it never inserts rows and leaves other binding fields unchanged.ProviderService.processRuntimeEventcalls it after fan-out forSESSION_ACTIVITY_EVENT_TYPES(turn.*,task.*), with a 60s per-thread throttle and throttle state cleared onstopSession. Streaming events likecontent.deltado not touch; touch failures are logged and do not affect event delivery.Reviewed by Cursor Bugbot for commit 0fadabd. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Refresh provider session
lastSeenAton turn and task activity eventstouchmethod toProviderSessionDirectoryandProviderSessionDirectoryShapethat updates onlylast_seen_atfor an existing binding, with no-op behavior for unknown threads.ProviderSessionRuntimeRepositorywithtouchByThreadId, which performs a targetedUPDATEonlast_seen_atwithout affecting other fields.makeProviderService, emitting events from the newSESSION_ACTIVITY_EVENT_TYPESset (turn.started,turn.completed,turn.aborted,task.*) triggersdirectory.touch, throttled to at most once per 60 seconds per thread. The throttle map entry is cleared onstopSession.lastSeenAtnow updates during active turns and tasks, where previously it only updated on session creation or explicit upsert.Macroscope summarized 0fadabd.