fix: clear stale dispatch gate after context reset (#3210) (#3223)

This commit is contained in:
Carlos Florêncio
2026-08-31 22:37:19 +00:00
committed by GitHub
parent 4e3dc91607
commit 9f2bb78aa2
4 changed files with 200 additions and 0 deletions
@@ -810,6 +810,7 @@ func (m *Manager) ResetAgentContext(ctx context.Context, executionID string) err
case <-exec.promptDoneCh:
default:
}
exec.dispatchedPromptPending.Store(false)
})
// Restore the complete captured configuration. A strict-mode model that is
@@ -1151,6 +1152,7 @@ func (m *Manager) resetAgentRestartState(executionID string, commands agentComma
case <-exec.promptDoneCh:
default:
}
exec.dispatchedPromptPending.Store(false)
})
}
@@ -250,6 +250,10 @@ func newRestartMockAgentctlServer(t *testing.T, failStop, failSessionNew bool) *
"success": true,
})
}
case "agent.prompt":
resp, _ = ws.NewResponse(msg.ID, msg.Action, map[string]interface{}{
"success": true,
})
case "agent.stderr":
stderrLines := m.stderrLines
if len(stderrLines) == 0 {
@@ -406,6 +410,7 @@ func TestManager_RestartAgentProcess_Success(t *testing.T) {
exec.assistantHistoryBuffer.WriteString("old response")
exec.needsResumeContext = true
exec.resumeContextInjected = true
exec.dispatchedPromptPending.Store(true)
exec.promptDoneCh <- PromptCompletionSignal{StopReason: "stale"}
mgr.executionStore.Add(exec)
@@ -443,6 +448,9 @@ func TestManager_RestartAgentProcess_Success(t *testing.T) {
t.Fatalf("expected stale prompt signal to be drained")
default:
}
if exec.dispatchedPromptPending.Load() {
t.Fatal("expected stale dispatch gate to be cleared")
}
httpActions := mock.getHTTPActions()
if !slices.Equal(httpActions, []string{"stop", "configure", "start"}) {
@@ -854,6 +862,47 @@ func TestManager_ResetAgentContext_ReappliesSessionMode(t *testing.T) {
require.Zero(t, exec.assistantHistoryBuffer.Len())
}
// @covers AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-002.3
func TestManager_ResetAgentContext_ClearsIdleDispatchGate(t *testing.T) {
mgr := newTestManager(t)
mock := newRestartMockAgentctlServer(t, false, false)
client := createTestClient(t, mock.server.URL)
t.Cleanup(client.Close)
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
require.NoError(t, client.StreamUpdates(ctx, func(agentctl.AgentEvent) {}, nil, nil))
exec := &AgentExecution{
ID: "exec-idle-reset",
TaskID: "task-1",
SessionID: "session-1",
AgentProfileID: "profile-1",
ACPSessionID: "old-session",
AgentCommand: "auggie --model test",
Status: v1.AgentStatusRunning,
WorkspacePath: "/workspace",
sessionInitialized: true,
agentctl: client,
promptDoneCh: make(chan PromptCompletionSignal, 1),
}
exec.dispatchedPromptPending.Store(true)
exec.promptDoneCh <- PromptCompletionSignal{StopReason: "end_turn"}
require.NoError(t, mgr.executionStore.Add(exec))
require.NoError(t, mgr.ResetAgentContext(ctx, exec.ID))
require.False(t, exec.dispatchedPromptPending.Load(),
"reset must clear the gate when it drains the completion signal")
followUpCtx, followUpCancel := context.WithTimeout(context.Background(), 3*time.Second)
defer followUpCancel()
_, err := mgr.PromptAgent(followUpCtx, exec.ID, "follow-up", nil, true)
require.NoError(t, err, "follow-up prompt must reach the new agent context")
require.Contains(t, mock.getWSActions(), "agent.prompt")
}
// TestManager_ResetAgentContext_ReappliesSessionModel is the regression test
// for an ACP fast-path reset replacing the task's selected model with the
// provider default from the freshly-created session.
@@ -0,0 +1,62 @@
---
created: 2026-08-31
status: done
requirements:
- REQ-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-002
system_design:
- ../../specs/tasks/system-design/workflow-step-agent-start-ownership.md
legacy_specs: []
---
# Implementation Plan: Clear the Idle Context-Reset Dispatch Gate
## Overview
Clear the dispatch-only completion gate when an idle context reset removes its buffered completion signal. Cover the ACP reset path and the process-restart fallback.
## Scope
### In scope
- Reproduce the idle dispatch-only state in lifecycle manager tests.
- Clear `dispatchedPromptPending` after each successful reset path drains `promptDoneCh`.
- Prove that the next prompt can reach agentctl after an ACP session reset.
- Prove that the process-restart fallback also clears the old gate.
### Out of scope
- Change the timeout behavior in `waitForPendingDispatchedPrompt`.
- Change active-turn quiescence or cancellation ownership.
- Add UI recovery for sessions that entered this state before the correction.
- Explain unrelated differences between prompt-completion log counts.
## Technical approach
Update `Manager.ResetAgentContext` in `manager_interaction.go`. Clear the atomic pending flag in the same locked state update that drains the old completion signal.
Update `Manager.resetAgentRestartState` in the same file. Apply the same state transition to the process-restart fallback.
Add focused tests in `manager_interaction_test.go`. The ACP test will reset an idle execution and send a follow-up dispatch-only prompt through the real manager path.
Extend the existing restart-state test with a pending gate. The test will prove that process replacement clears the signal and its matching flag.
## Tests
- `AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-002.3`: `TestManager_ResetAgentContext_ClearsIdleDispatchGate` proves that the follow-up prompt reaches the new ACP session.
- `AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-002.3`: `TestManager_RestartAgentProcess_Success` proves that the process-restart fallback clears the prior gate.
- `AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-002.5`: Existing pending-wait tests remain unchanged because the timeout still preserves active predecessor ownership.
## Work orders
- [x] [Task 01: Clear the idle reset dispatch gate](task-01-clear-idle-reset-dispatch-gate.md)
## Verification results
- RED: Both focused regression tests failed because the reset paths kept `dispatchedPromptPending` set.
- GREEN: The focused tests passed after both reset paths cleared the flag after the signal drain.
- Final: The race-enabled verification passed 2 tests. `git diff --check` also passed.
## Risks
- Clearing the gate before the old turn is quiescent can admit concurrent prompts. The existing orchestrator reset boundary must remain unchanged.
- Clearing only the ACP fast path leaves adapters that use process replacement exposed to the same error.
@@ -0,0 +1,87 @@
---
id: "01-clear-idle-reset-dispatch-gate"
title: "Clear the idle reset dispatch gate"
status: done
wave: 1
depends_on: []
plan: "plan.md"
requirements:
- REQ-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-002
acceptance_criteria:
- AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-002.3
- AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-002.5
system_design:
- ../../specs/tasks/system-design/workflow-step-agent-start-ownership.md
---
# Task 01: Clear the Idle Reset Dispatch Gate
## Summary
Clear the pending dispatch-only gate when a successful idle context reset removes its completion signal. Apply the correction to both provider-reset paths.
## In scope
- Add a failing regression test for an idle ACP reset with a buffered dispatch-only completion.
- Prove that a follow-up dispatch-only prompt reaches agentctl after the reset.
- Add pending-gate coverage to the existing process-restart state test.
- Clear the old pending flag after both reset paths drain the old signal.
## Out of scope
- Clear the gate when `waitForPendingDispatchedPrompt` times out.
- Change cancellation escalation or active-turn reset behavior.
- Add frontend behavior or browser tests.
## Acceptance
- An ACP session reset removes the old completion signal and clears its matching pending flag.
- The next prompt reaches agentctl without a predecessor timeout.
- The process-restart fallback clears the same signal and flag pair.
## Verification
```bash
go test -race ./internal/agent/runtime/lifecycle -run 'TestManager_(ResetAgentContext_ClearsIdleDispatchGate|RestartAgentProcess_Success)$' -count=1
git diff --check
```
Run the commands from `apps/backend` and the repository root, respectively.
## Files likely touched
- `apps/backend/internal/agent/runtime/lifecycle/manager_interaction.go`
- `apps/backend/internal/agent/runtime/lifecycle/manager_interaction_test.go`
- `docs/plans/idle-context-reset-dispatch-gate/plan.md`
- `docs/plans/idle-context-reset-dispatch-gate/task-01-clear-idle-reset-dispatch-gate.md`
## Dependencies
None.
## Risks
- A reset path that drains the signal without clearing the flag will keep the execution unusable.
- A flag clear before successful context replacement can release a valid active generation.
## Parallelism
`sequential`
## Inputs
- `REQ-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-002`
- `docs/specs/tasks/system-design/workflow-step-agent-start-ownership.md`
- `docs/decisions/2026-08-30-context-reset-quiesces-active-turn.md`
- GitHub issue `#3210`
- Existing reset and pending-prompt tests in the lifecycle package.
## Results
- RED: The focused command failed 2 tests before the production correction.
- `TestManager_RestartAgentProcess_Success` found that process replacement kept the stale gate.
- `TestManager_ResetAgentContext_ClearsIdleDispatchGate` found the same state after an ACP reset.
- GREEN: The focused command passed 2 tests after both reset paths cleared the gate.
- Final: The race-enabled verification passed 2 tests. `git diff --check` passed.
- Changed files: `manager_interaction.go`, `manager_interaction_test.go`, and this plan package.
- No known blockers remain.