fix: normalize session idleness for ACP tool work - #1874
Draft
simple-agent-manager[bot] wants to merge 8 commits into
Draft
fix: normalize session idleness for ACP tool work#1874simple-agent-manager[bot] wants to merge 8 commits into
simple-agent-manager[bot] wants to merge 8 commits into
Conversation
Contributor
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
classifySessionIdleness()helper for prompt activity, normalized runtime-work lease/ceiling, child-task work, durable-wait signal input, and inconclusive evidence.stateBefore,stateAfter,stateAtStop) to use the shared predicate.tool_call/tool_call_updatelifecycles into the existing VMruntime_work_statelease with sourceacp_tool_callwhile preserving Claude_claude/sdkMessagesourceclaude_sdkand cf-containeractiveWorksemantics.unknownand also blocks sleep.Converted readers:
checkAutomaticSessionSleepEligibility()sleepWorkspaceSession()state-before gatesleepWorkspaceSession()state-after snapshot gatesleepWorkspaceSession()state-at-stop artifact-verification gateDeliberate gaps:
classifyTaskRuntimeLiveness()before terminalization.task_wait_subscriptionsdirectly. The predicate now hasactiveWaitCountfor that follow-up adapter.tool_call/tool_call_update.Expected control-loop/candidate cost:
project_id+parent_task_id, with active status constants andLIMIT 1. Failure returns inconclusive instead of proving idleness.Rollback:
Validation
pnpm lintpnpm typecheckpnpm test.claude/rules/47-control-loop-io-budget.md)Additional validation:
52a328e67b42fce46f59561b0324482e61306f76— all visible PR checks passed, including Test, Durable Object Workers, VM Agent Integration/E2E/Test/Smoke, Lint, Type Check, SonarCloud, Preflight Evidence, Specialist Review Evidence, Build, Workspace Quality Surfaces, and Devcontainer Volume Mount.pnpm --filter @simple-agent-manager/api test -- tests/unit/services/session-idleness.test.ts tests/unit/services/session-sleep.test.ts— 45/45 passed.pnpm --filter @simple-agent-manager/api test— 580 files / 7,851 tests passed.pnpm --filter @simple-agent-manager/api lint— passed.pnpm --filter @simple-agent-manager/api typecheck— passed.PATH=<task-local-go>/bin:$PATH go test ./internal/acp -run 'Test(ACPToolCallLifecycle|ACPToolCallsDoNotOverrideClaudeLifecycleSource|ACPToolCallActivityReportsOnlyNormalizedState|ClaudeHarnessLifecycle|HarnessActivityReportsOnlyNormalizedState|HarnessActivityStopsWhenCrashRestartFailsBeforeAttach)' -count=1— passed.PATH=<task-local-go>/bin:$PATH go test ./...inpackages/vm-agent— passed.pnpm format:check— passed at head52a328e67b42fce46f59561b0324482e61306f76.pnpm lint— passed at head with existingacp-clientandapps/webwarnings only.pnpm typecheck— passed at head with the documented Astro template-validation baseline.Staging Verification (REQUIRED for all code changes — merge-blocking)
Draft PR remains draft. Staging was executed only after the parent granted the exclusive slot for exact SHA
52a328e67b42fce46f59561b0324482e61306f76; the slot was released after cleanup proof was accepted. Do not mark ready, merge, redeploy, or perform further staging mutations without renewed parent instruction.Deploy Stagingworkflow run32455123148succeeded for branchsam/implement-next-independently-shippable-3wpb6gat exact SHA52a328e67b42fce46f59561b0324482e61306f76: https://github.com/raphaeltm/simple-agent-manager/actions/runs/32455123148app.sammy.partystaging with one reused primary storage state (/tmp/pr1874-staging-storage.json) to avoid token-login rate limits.smallVM node (01M0HJR0DYP4HN4W0RK9AG7NNA, provider instance162988618, runtimevm, credential attributionplatform)./tmp/pr1874-staging-screens/{dashboard,projects,settings,settings-agents}.png.Staging Verification Evidence
Pre-mutation gates:
deploy-staging.ymlwas rechecked with an unfiltered workflow-run listing and had no activequeued/in_progressstaging deploy before mutation.52a328e67b42fce46f59561b0324482e61306f76before the deploy; live PR metadata remainsOPEN+ draft at that SHA for this handoff update.platform_credentials.id=01KNY6DC06C9QCYQM0389NAGNT,provider=hetzner,credential_type='cloud-provider',is_enabled=1, labelUsers Staging Hetzner. Missing user cloud credentials were not treated as blockers; the successful chain proved user→platform fallback throughcredential_attribution_source=platform.Deployment/UI smoke:
32455123148,conclusion=success,status=completed,headBranch=sam/implement-next-independently-shippable-3wpb6g,headSha=52a328e67b42fce46f59561b0324482e61306f76,createdAt=2026-08-21T06:37:06Z,updatedAt=2026-08-21T06:52:51Z.0./tmp/pr1874-staging-screens/dashboard.png,/tmp/pr1874-staging-screens/projects.png,/tmp/pr1874-staging-screens/settings.png,/tmp/pr1874-staging-screens/settings-agents.png.Primary Codex attempt:
01M0HJ2VAPH0T09DEDWC6N5580(PR1874 Codex 5.5 High Chat mt2lrj69) created and later deleted.01M0HJ2Y2ECQ8SP0AEJ92005X9, session8e757c39-264c-422a-a3bc-c9702309043c, VM node01M0HJ31P8JBNTVYAVCCK98WMH/ provider instance162987662, workspace01M0HJ9VDZKWR1QBWSJZ9EE8R9.Authentication required: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.This is a live credential issue, not evidence against the PR behavior. The task/profile/workspace/node were cleaned before retry.Successful secondary Codex VM chain:
01KP5SJ5XZFZFZCQM8YG5H847V(Complex Test Project), temporary profile01M0HJQR2B6TZNHNX0NW51VCVK(PR1874 Secondary Codex 5.5 High VM mt2m673b),agentType=openai-codex,model=gpt-5.5,effort=high,runtime=vm,taskMode=conversation,workspaceProfile=lightweight,vmSizeOverride=small.01M0HJQWA557KWAR4Z68W6N3PM, session1f2c862b-aeb7-422d-b04f-c26115c63d39, workspace01M0HK0CW4WE349J28Q4WB1XZE.01M0HKF3F7P1JBVEZH7JNCHGRE, sessionb25f0ccf-4668-482e-bac4-391d9c8900e0, workspace01M0HKF7S4K2EGRCP1A1X59MKX, parent01M0HJQWA557KWAR4Z68W6N3PM.01M0HMCD728Z8E7366DRA1DP1Q,recovery_source_task_id=01M0HKF3F7P1JBVEZH7JNCHGRE, replacement workspace01M0HMCG5HETXGYXJNDZVB39PZ.01M0HJR0DYP4HN4W0RK9AG7NNA, provider instance162988618, runtimevm, sizesmall, Hetzner,credential_attribution_source=platform.Live diagnostics and lifecycle evidence:
ACP
tool_call/tool_call_updatein-flight detection, real diagnostics endpoint:2026-08-21T07:25:06.012Z,GET /api/admin/tasks/01M0HJQWA557KWAR4Z68W6N3PM/reconciliation-diagnostics.status=in_progress,executionStep=running,workspaceId=01M0HK0CW4WE349J28Q4WB1XZE.eligible=true,elapsedMs=310912,eligibilityThresholdMs=300000,decision=preserve_live_runtime.liveness.live=true,liveness.conclusive=true,liveness.reason=task_acp_session_live,liveness.workspaceStatus=running,liveness.nodeId=01M0HJR0DYP4HN4W0RK9AG7NNA,liveness.activeAcpSessionId=01M0HK1E06G1AZRCYNVSDE1R7Z.agentSessionId=01M0HK1E06G1AZRCYNVSDE1R7Z,agentType=openai-codex,activity=prompting,runtimeWorkState=active,runtimeWorkCount=1,runtimeWorkSource=acp_tool_call,runtimeWorkUpdatedAt=1787297103604,runtimeWorkProgressAt=1787297103423.Parent idle/awaiting-followup while child/subtask is active:
2026-08-21T07:30:55.065Z, same diagnostics endpoint for parent task01M0HJQWA557KWAR4Z68W6N3PM.status=in_progress,execution_step=awaiting_followup,workspace_id=01M0HK0CW4WE349J28Q4WB1XZE.01M0HKF3F7P1JBVEZH7JNCHGREwasstatus=in_progress,execution_step=running,workspace_id=01M0HKF7S4K2EGRCP1A1X59MKX,parent_task_id=01M0HJQWA557KWAR4Z68W6N3PM.eligible=true,elapsedMs=661500,eligibilityThresholdMs=300000,decision=preserve_live_runtime,liveness.reason=task_acp_session_live.activity=idle,runtimeWorkState=inactive,runtimeWorkCount=0,runtimeWorkSource=acp_tool_call,runtimeWorkUpdatedAt=1787297250247,runtimeWorkProgressAt=1787297247112. This is the load-bearing evidence that absence of runtime-work signal alone is not evidence of idleness when child work exists.Sleep blocked by active child/subtask:
POST /api/workspaces/01M0HK0CW4WE349J28Q4WB1XZE/sleepat2026-08-21T07:36:45.105Zreturned HTTP500due route error masking (requestId=c350da61-7b02-4361-80cd-2275bf893de2).1f2c862b-aeb7-422d-b04f-c26115c63d39, workspace01M0HK0CW4WE349J28Q4WB1XZE,status=available,degradation=none,sleep_status=failed,sleep_attempts=1,sleep_error=Child/subtask work is active,sleeping_at=null,updated_at=2026-08-21T07:36:47.950Z.Child terminalization and follow-up behavior:
2026-08-21T07:37:34.362Z:POST /api/projects/01KP5SJ5XZFZFZCQM8YG5H847V/tasks/01M0HKF3F7P1JBVEZH7JNCHGRE/status, HTTP200, childstatus=completed,completedAt=2026-08-21T07:37:32.665Z.wait_for_subtasksfor the child task. Manual user-status terminalization did not immediately resume the parent during the observed window; this is labeled as a live gap below because the low-latency wake hook is on task-runner/MCP terminal paths and ProjectData alarm remains fallback.Idle interval and parent close behavior:
2026-08-21T07:39:46.249ZwithdeliveryId=01M0HM5XM65P5BSBCE1BBRPBE1.sleep_error=Workspace idle interval has not elapsed,updated_at=2026-08-21T07:42:06.864Z.2026-08-21T07:42:42.547Z(closedAt=2026-08-21T07:42:39.808Z); a later explicit sleep call returned404 Workspace not foundbecause close cleanup had already removed the parent workspace.Child automatic sleep:
b25f0ccf-4668-482e-bac4-391d9c8900e0, workspace01M0HKF7S4K2EGRCP1A1X59MKX, snapshot:status=available,degradation=none,sleep_status=sleeping,sleep_attempts=1,sleeping_at=2026-08-21T07:40:57.094Z,updated_at=2026-08-21T07:40:57.094Z.Sleep/wake recovery:
2026-08-21T07:43:16.696Z:POST /api/projects/01KP5SJ5XZFZFZCQM8YG5H847V/sessions/b25f0ccf-4668-482e-bac4-391d9c8900e0/prompt, HTTP202,deliveryId=01M0HMCB4HMW03207JAX99FHB7.01M0HMCD728Z8E7366DRA1DP1Qfrom source task01M0HKF3F7P1JBVEZH7JNCHGRE, replacement workspace01M0HMCG5HETXGYXJNDZVB39PZ, same node01M0HJR0DYP4HN4W0RK9AG7NNA,session_snapshots.recovery_attempts=1.2026-08-21T07:44:46.551Z, recovery task wasin_progress/running, workspace01M0HMCG5HETXGYXJNDZVB39PZwas running, and the transcript containedPR1874_CHILD_WAKE_DONE.Cleanup proof:
2026-08-21T07:45:26Zand2026-08-21T07:45:45Zfor recovery/child/parent tasks, profiles, workspaces, and node resources.2026-08-21T07:46:49.281Z:{"t":"2026-08-21T07:46:49.281Z","nonDeletedNodes":[],"activeWorkspaces":[],"prTasks":[],"prProfiles":[]}.Clearly labeled live gaps:
UI Compliance Checklist (Required for UI changes)
N/A: no UI changes.
End-to-End Verification (Required for multi-component changes)
.claude/rules/10-e2e-verification.md)Data Flow Trace
session/updatewithtool_call/tool_call_update→packages/vm-agent/internal/acp/session_host_client.go:SessionUpdate.packages/vm-agent/internal/acp/session_host_harness_work.go:applyACPToolCallLifecycle/applyACPToolCallStatus.packages/vm-agent/internal/acp/session_host_reporting.go:reportActivityvianudgeHarnessActivityReport().apps/api/src/routes/projects/agent-activity-callback.tsandapps/api/src/durable-objects/project-data/session-state.ts.apps/api/src/services/session-idleness.ts:classifySessionIdlenessfromapps/api/src/services/session-sleep.ts.Remaining Live Gaps
task_wait_subscriptionsreader is not wired into sleep yet; the predicate input is present for the follow-up adapter.Post-Mortem (Required for bug fix PRs)
What broke
Codex/OpenCode ACP tool calls could be invisible to session idleness, so a session could appear idle after the prompt turn even while an agent-initiated tool/unit was expected to return.
Root cause
PR #1845 added normalized VM runtime-work state for Claude
_claude/sdkMessagelifecycle notifications, but standard ACPtool_call/tool_call_updatenotifications were only persisted as chat metadata and did not feedruntime_work_state.Class of bug
Cross-runtime lifecycle signal gap / idleness predicate split-brain.
Why it wasn't caught
Coverage existed for Claude background tasks and sleep lease math, but not for harness-agnostic ACP tool-call lifecycle signals or one shared idleness predicate.
Process fix included in this PR
The active task file records the reconciled research, implemented slice, validation, specialist review, and deliberate gaps:
tasks/active/2026-08-21-session-idleness-work-lease-slice.md.Post-mortem file
tasks/active/2026-08-21-session-idleness-work-lease-slice.mdSpecialist Review Evidence (Required for agent-authored PRs)
needs-human-reviewlabel added and merge deferred to humanharnessWorkMu, avoidsh.mu, and preserves Claude source exclusivity.LIMIT 1, and failure is inconclusive.Exceptions (If any)
2026-08-21T07:46:49.281Zwith zero non-deleted nodes, active workspaces, PR1874 tasks, and PR1874 profiles.Agent Preflight (Required)
Classification
External References
ToolCallStatusenum andSessionUpdatevariants: https://github.com/coder/acp-go-sdk/blob/v0.13.5/types_gen.go01M08VJDHK3MNYMZCQF5AJC17Pand01M07SW32WP8ZXABWF1ZV3AEMXread through SAM MCP.main.Codebase Impact Analysis
packages/vm-agent/internal/acp: ACP tool-call lifecycle signal detection and tests.apps/api/src/services: shared session idleness predicate and sleep-reader conversion.apps/api/tests/unit/services: deterministic idleness/sleep tests.tasks/active: task record/review evidence.Documentation & Specs
tasks/active/2026-08-21-session-idleness-work-lease-slice.md.Constitution & Risk Check