Repository navigation
fix(server): hold background liveness until a waking task resumes #13933
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1039,6 +1039,15 @@ export function runtimeEventToActivities( | |
| return []; | ||
| } | ||
|
|
||
| /** | ||
| * Subscribe to provider runtime events and fold them into thread commands, | ||
| * activities, and in-memory background liveness. | ||
| * | ||
| * A waking task completion keeps that liveness working until the follow-up | ||
| * turn starts, so the shell does not read as ready in the gap. | ||
| * | ||
| * @returns The ingestion service, with `start` and `drain`. | ||
| */ | ||
| const make = Effect.gen(function* () { | ||
| const threadBackgroundLiveness = yield* ThreadBackgroundLivenessService; | ||
| const threadPlanProgress = yield* ThreadPlanProgressService; | ||
|
|
@@ -1778,6 +1787,15 @@ const make = Effect.gen(function* () { | |
| }, | ||
| ); | ||
|
|
||
| /** | ||
| * Fold one provider runtime event into the thread: session lifecycle, | ||
| * activities, and in-memory background liveness. A waking task completion | ||
| * keeps that liveness working until the follow-up turn starts, so the | ||
| * shell does not read as ready in the gap. | ||
| * | ||
| * @param event - Provider runtime event for the thread being ingested. | ||
| * @returns The effect that persists the event and updates liveness. | ||
| */ | ||
| const processRuntimeEvent = (event: ProviderRuntimeEvent) => | ||
| Effect.gen(function* () { | ||
| if ( | ||
|
|
@@ -2504,6 +2522,7 @@ const make = Effect.gen(function* () { | |
| taskType?: string; | ||
| status?: string; | ||
| agentId?: string; | ||
| resumesProvider?: boolean; | ||
| }; | ||
| threadBackgroundLiveness.recordTaskLiveness({ | ||
| threadId: thread.id, | ||
|
|
@@ -2519,9 +2538,30 @@ const make = Effect.gen(function* () { | |
| : event.type === "task.updated" | ||
| ? "updated" | ||
| : "completed", | ||
| awaitsProviderResume: | ||
| event.type === "task.completed" && payload.resumesProvider === true, | ||
| }); | ||
| break; | ||
| } | ||
| case "turn.started": | ||
| // The follow-up turn is running. Session status keeps the thread | ||
| // working until that turn settles, so the resume hold can drop. | ||
| if (shouldApplyThreadLifecycle) { | ||
| threadBackgroundLiveness.releaseProviderResume(thread.id); | ||
| } | ||
| break; | ||
| case "turn.aborted": | ||
| if (shouldApplyThreadLifecycle) { | ||
| threadBackgroundLiveness.releaseProviderResume(thread.id); | ||
| } | ||
| break; | ||
| case "session.state.changed": | ||
| // Ready is the gap itself. Release only when the session can no | ||
| // longer resume, so a failure is not pinned on Working. | ||
| if (event.payload.state === "error" || event.payload.state === "stopped") { | ||
| threadBackgroundLiveness.releaseProviderResume(thread.id); | ||
| } | ||
| break; | ||
|
Comment on lines
+2546
to
+2564
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: set -eu
printf '%s\n' '--- changed files ---'
git diff --stat ab099178a7b7f9728843e90fc95ed90bb61d710d 7cce0c417460f8289cc916750e15248d53fd5f8a
printf '%s\n' '--- targeted diff ---'
git diff --unified=40 ab099178a7b7f9728843e90bb61d710d 7cce0c417460f8289cc916750e15248d53fd5f8a -- apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
printf '%s\n' '--- candidate Claude files ---'
rg -l --glob '*.ts' 'class ClaudeAdapter|ClaudeAdapter|turnState|task_notification|resumesProvider|providerResume' apps/server/src | head -80
printf '%s\n' '--- relevant declarations/usages ---'
rg -n -C 8 'class ClaudeAdapter|turnState|task_notification|resumesProvider|providerResume|session_state_changed|turn\.started|turn\.aborted' apps/server/src/orchestration apps/server/src | head -500Repository: pingdotgg/t3code Length of output: 917 🤖 get_repo_knowledge executed:
Length of output: 32408 🏁 Script executed: set -eu
BASE='ab099178a7b7f9728843e90fc95ed90bb61d710d'
HEAD='7cce0c417460f8289cc916750e15248d53fd5f8a'
printf '%s\n' '--- revision availability ---'
git rev-parse --verify "$BASE^{commit}"
git rev-parse --verify "$HEAD^{commit}"
printf '%s\n' '--- targeted diff ---'
git diff --unified=25 "$BASE" "$HEAD" -- \
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts \
apps/server/src/provider/Layers/ClaudeAdapter.ts \
apps/server/src/orchestration/ThreadBackgroundLiveness.ts \
packages/contracts/src/providerRuntime.ts
printf '%s\n' '--- Claude adapter relevant source ---'
rg -n -C 18 'turnState|task_notification|session_state_changed|result|num_turns|parent|assistant' apps/server/src/provider/Layers/ClaudeAdapter.ts
printf '%s\n' '--- ingestion relevant source ---'
rg -n -C 16 'resumesProvider|providerResume|turn.started|turn.aborted|session.state.changed|session.exited|shouldApplyThreadLifecycle|processRuntimeEvent' apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
printf '%s\n' '--- liveness implementation ---'
cat -n apps/server/src/orchestration/ThreadBackgroundLiveness.ts
printf '%s\n' '--- relevant tests ---'
rg -n -C 12 'resumesProvider|providerResume|task_notification|turnState|session_state_changed|num_turns|turn.started|turn.aborted' \
apps/server/src/provider/Layers/ClaudeAdapter.test.ts \
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts \
apps/server/src/orchestration/ThreadBackgroundLiveness.test.tsRepository: pingdotgg/t3code Length of output: 45660 🏁 Script executed: set -eu
printf '%s\n' '--- Claude adapter system-message handler ---'
sed -n '3590,3925p' apps/server/src/provider/Layers/ClaudeAdapter.ts
printf '%s\n' '--- Claude adapter turn-state declarations and updates ---'
rg -n -C 10 'turnState|num_turns|message\.type === "assistant"|case "assistant"|type === "result"|case "result"|handleSystemMessage|task_notification' apps/server/src/provider/Layers/ClaudeAdapter.ts
printf '%s\n' '--- relevant Claude adapter tests ---'
rg -n -C 20 'task_notification|resumesProvider|turnState|num_turns|result|parent assistant|assistant message' apps/server/src/provider/Layers/ClaudeAdapter.test.ts
printf '%s\n' '--- relevant ingestion tests ---'
sed -n '4640,4845p' apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts
printf '%s\n' '--- full changed contracts section ---'
git diff --unified=12 ab099178a7b7f9728843e90fc95ed90bb61d710d 7cce0c417460f8289cc916750e15248d53fd5f8a -- packages/contracts/src/providerRuntime.tsRepository: pingdotgg/t3code Length of output: 42029 🏁 Script executed: set -eu
printf '%s\n' '--- all hold release call sites ---'
rg -n -C 6 'releaseProviderResume|resumeHolds|resumesProvider' apps/server/src packages/contracts/src
printf '%s\n' '--- adapter turn-state and result handling ---'
rg -n -C 30 'const handleResultMessage|function handleResultMessage|handleResultMessage|turnState =|turnState:|newTurn|parent_tool_use_id|case "assistant"|message.type === "assistant"|type === "result"' apps/server/src/provider/Layers/ClaudeAdapter.ts
printf '%s\n' '--- focused adapter test names and bodies ---'
rg -n -C 35 'resumesProvider|task notification|task_notification|result-without-active-turn|num_turns' apps/server/src/provider/Layers/ClaudeAdapter.test.ts
printf '%s\n' '--- ingestion lifecycle switch and guards ---'
sed -n '2190,2265p' apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
sed -n '2510,2575p' apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.tsRepository: pingdotgg/t3code Length of output: 41827 Release a resume hold when the provider does not start a follow-up turn.
Add a release event for a result that arrives without an active turn, or add a bounded timeout for the hold. 🤖 Prompt for AI Agents |
||
| case "session.exited": | ||
| threadBackgroundLiveness.clearThreadLiveness(thread.id); | ||
| break; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Medium
Layers/ProviderRuntimeIngestion.ts:2546A
runtime.erroror recoveredturn.completedleavesthreadBackgroundLivenessinworking, so the shell suppresses completion/settlement behavior untilsession.exited. The switch never releases the resume hold for either event; add release handling for both paths.🤖 Copy this AI Prompt to have your agent fix this: