Skip to content

Resume an agent that was waiting without the restart nudge - #1937

Merged
realtonyyoung merged 3 commits into
mainfrom
tonyyoung/ai-4257-resume-idle-without-nudge
Oct 25, 2026
Merged

realtonyyoung merged 3 commits into
mainfrom
tonyyoung/ai-4257-resume-idle-without-nudge

Conversation

@realtonyyoung

Copy link
Copy Markdown
Collaborator

Closes #1849 — AI-4257

What & why

A daemon restart resumed every interactive agent with the restart nudge, even one that had finished its turn and was waiting. That started a turn nobody asked for, and every surface showed the agent as Working.

  • At a keep-agents shutdown, the ledger entry now records whether the agent was waiting (awaiting_input). The field is optional: an entry from an older daemon omits it and keeps the nudge
  • A new factory capability, ResumesIdleWithoutPrompt, is true only for the PTY runtimes (Claude, and Codex on its terminal transport). A waiting agent on one of those resumes with an empty prompt and is marked AwaitingInput at once, so the next restart's turn-boundary wait isn't left waiting on the quiet-turn detector
  • Every other transport keeps the nudge. That includes ACP, whose reopened session is only judged ready once its first turn has run

Where to look

Both PTY launchers drop an empty prompt rather than passing -- and an empty argument, and the PTY factory pastes nothing for one. That is what makes the silent resume safe, and each launcher now has a test pinning it.

Verification

Run Result
Resume table and the shutdown record, before 2 fail (waiting agent got the nudge; nothing recorded)
Launcher pins with Prompt is not null instead of IsNullOrEmpty both fail
Codex factory without the transport check 3 rows fail (app-server, pending, null)
Resume, stop-for-resume, continue, both launcher classes, Codex factory 193/193 pass
ResumeLedgerStoreTests, including an entry with the field stripped 13/13 pass
Daemon dotnet publish -c Release 0 IL warnings

🤖 Generated with Claude Code

realtonyyoung and others added 2 commits October 24, 2026 18:25
Only a terminal runtime can start a resume with no turn: a reopened ACP session is judged by its first turn, so every other transport keeps the nudge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Oct 24, 2026

Copy link
Copy Markdown

AI-4257

@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

The included review limit has been reached and this organization has disabled usage-based review continuation. Wait for reviews to reset or ask a billing admin to change After included review limits.

Next included review available in 4 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: ab4763f6-4b74-4630-9cfe-05660c0cc357
Commits

Reviewing files that changed from the base of the PR and between f130862 and cd57ee6.

Files selected for processing: 11
  • src/Capacitor.Cli.Core/Resume/ResumeLedgerEntry.cs
  • src/Capacitor.Cli.Daemon/Harness/Codex/CodexHostedAgentRuntimeFactory.cs
  • src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs
  • src/Capacitor.Cli.Daemon/Services/IHostedAgentRuntimeFactory.cs
  • src/Capacitor.Cli.Daemon/Services/PtyHostedAgentRuntimeFactory.cs
  • test/Capacitor.Cli.Core.Tests.Unit/Resume/ResumeLedgerStoreTests.cs
  • test/Capacitor.Cli.Daemon.Tests.Unit/Harness/Claude/ClaudeLauncherResumeTests.cs
  • test/Capacitor.Cli.Daemon.Tests.Unit/Harness/Codex/CodexHostedAgentRuntimeFactoryTests.cs
  • test/Capacitor.Cli.Daemon.Tests.Unit/Harness/Codex/CodexLauncherTests.cs
  • test/Capacitor.Cli.Daemon.Tests.Unit/Services/AgentOrchestratorResumeOnBootTests.cs
  • test/Capacitor.Cli.Daemon.Tests.Unit/Services/AgentOrchestratorStopForResumeTests.cs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@realtonyyoung
realtonyyoung marked this pull request as ready for review October 24, 2026 22:45
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Resume waiting PTY agents without a restart nudge

🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Record whether an agent was awaiting input when the daemon stops it for restart.
• Resume waiting PTY agents without a nudge; preserve the nudge for other transports and older
 ledger entries.
• Test ledger compatibility, transport eligibility, and empty-prompt launcher behavior.
Diagram

graph TD
  Clock["Activity clock"] --> Stop["Keep-agents stop"] --> Ledger["Resume ledger"] --> Boot["Boot resume"] --> Eligible{"Waiting PTY?"} -->|Yes| Silent["Silent relaunch"] --> Waiting["Awaiting input"]
  Eligible -->|No| Nudge["Nudged relaunch"]
Loading
High-Level Assessment

Keep the capability on runtime factories, defaulting to false. Inferring eligibility from transport names in the orchestrator would duplicate runtime-specific knowledge and risk suppressing the first turn required by other transports.

Files changed (11) +101 / -7

Enhancement (1) +4 / -0
IHostedAgentRuntimeFactory.csDefine prompt-free idle-resume capability +4/-0

Define prompt-free idle-resume capability

• Adds a transport-aware capability for resuming a waiting agent without a first turn. It defaults to false so other runtime factories keep their existing behavior.

src/Capacitor.Cli.Daemon/Services/IHostedAgentRuntimeFactory.cs

Bug fix (4) +21 / -2
ResumeLedgerEntry.csPersist an optional awaiting-input state +4/-1

Persist an optional awaiting-input state

• Adds a nullable AwaitingInput field to resume entries. Older entries can omit it and continue using the existing nudged resume.

src/Capacitor.Cli.Core/Resume/ResumeLedgerEntry.cs

CodexHostedAgentRuntimeFactory.csLimit silent Codex resume to recorded PTY sessions +4/-0

Limit silent Codex resume to recorded PTY sessions

• Reports prompt-free resume support only when the recorded transport is PTY and the underlying PTY factory supports it. App-server, pending, and unknown transports remain ineligible.

src/Capacitor.Cli.Daemon/Harness/Codex/CodexHostedAgentRuntimeFactory.cs

AgentOrchestrator.Resume.csPreserve waiting state across shutdown and resume +9/-1

Preserve waiting state across shutdown and resume

• Writes the activity clock's awaiting-input state when stopping an agent for resume. Eligible waiting agents relaunch with an empty prompt and are immediately marked as awaiting input; other agents retain the restart nudge.

src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs

PtyHostedAgentRuntimeFactory.csAdvertise prompt-free resume for resumable PTY launchers +4/-0

Advertise prompt-free resume for resumable PTY launchers

• Reports silent-resume support when the PTY launcher supports restart resume, matching its empty-prompt launch behavior.

src/Capacitor.Cli.Daemon/Services/PtyHostedAgentRuntimeFactory.cs

Tests (6) +76 / -5
ResumeLedgerStoreTests.csCover waiting-state persistence and older ledger entries +5/-3

Cover waiting-state persistence and older ledger entries

• Includes AwaitingInput in the round-trip fixture and checks that an older entry without the field reads it as null.

test/Capacitor.Cli.Core.Tests.Unit/Resume/ResumeLedgerStoreTests.cs

ClaudeLauncherResumeTests.csPin Claude's empty-prompt resume arguments +9/-0

Pin Claude's empty-prompt resume arguments

• Verifies that an empty resume prompt adds neither a prompt argument nor the argument separator.

test/Capacitor.Cli.Daemon.Tests.Unit/Harness/Claude/ClaudeLauncherResumeTests.cs

CodexHostedAgentRuntimeFactoryTests.csTest Codex transport eligibility for silent resume +13/-0

Test Codex transport eligibility for silent resume

• Checks that only a recorded PTY session qualifies, while app-server, pending, and null transports do not.

test/Capacitor.Cli.Daemon.Tests.Unit/Harness/Codex/CodexHostedAgentRuntimeFactoryTests.cs

CodexLauncherTests.csPin Codex's empty-prompt resume arguments +9/-0

Pin Codex's empty-prompt resume arguments

• Verifies that a resumed Codex session with an empty prompt receives neither a prompt argument nor the argument separator.

test/Capacitor.Cli.Daemon.Tests.Unit/Harness/Codex/CodexLauncherTests.cs

AgentOrchestratorResumeOnBootTests.csCover silent versus nudged boot resumes +27/-2

Cover silent versus nudged boot resumes

• Tests prompt and activity-clock behavior for waiting agents on capable and incapable runtimes, mid-turn agents, and entries without a recorded waiting state.

test/Capacitor.Cli.Daemon.Tests.Unit/Services/AgentOrchestratorResumeOnBootTests.cs

AgentOrchestratorStopForResumeTests.csVerify waiting state is recorded at shutdown +13/-0

Verify waiting state is recorded at shutdown

• Checks that keep-agents shutdown writes distinct awaiting-input values for idle and working agents.

test/Capacitor.Cli.Daemon.Tests.Unit/Services/AgentOrchestratorStopForResumeTests.cs

@qodo-code-review

qodo-code-review Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Active turns can be marked as waiting ✗ Dismissed
Description
NativeResumeAsync calls SetAwaitingInput(true) after LaunchAdmittedAsync has published the
agent and started processing its output, so it can overwrite a turn-start hook or user input that
already cleared the wait. If a turn begins during that interval, later PTY output cannot clear the
overwritten flag, and a subsequent keep-agents shutdown can record the active turn as waiting.
Code

src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs[882]

+        if (silent) resume.Agent?.ActivityClock.SetAwaitingInput(true);
Relevance

●●● Strong

Recent activity-transition race findings were accepted, supporting fixing post-launch state writes
that misclassify active turns.

PR-#616
PR-#1623

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The launch publishes the agent and starts its output reader before the new assignment. Hooks and
delivered input can clear the clock during that interval; the later assignment sets a non-silence
wait, which terminal output does not clear.

src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs[874-882]
src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.cs[3042-3058]
src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.cs[3179-3179]
src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.cs[1172-1173]
src/Capacitor.Cli.Daemon/Services/AgentActivityClock.cs[168-178]
src/Capacitor.Cli.Daemon/Services/AgentActivityClock.cs[270-285]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A silent resume sets its waiting state after the resumed agent can receive input and turn-start hooks, potentially overwriting evidence of a new turn.
## Fix Focus Areas
- src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs[874-882]
- src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.cs[3042-3058]
## Recommended Fix
Initialize the resumed agent's waiting state before publishing it to input and hook handlers, rather than setting it unconditionally after launch returns. Add a test in which a turn-start signal arrives during launch.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. A crash can skip a working agent's nudge ✓ Resolved
Description
NativeResumeAsync trusts entry.AwaitingInput == true even when the entry is a running agent's
ledger record, while WriteResumedLedger preserves that shutdown snapshot after the agent resumes.
If the user starts another turn and the daemon crashes before a graceful shutdown refreshes the
field, boot recovery treats the interrupted turn as idle and relaunches it with an empty prompt.
Code

src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs[R875-876]

+        var silent  = entry.AwaitingInput == true
+                   && _runtimeFactories.TryGetValue(entry.Vendor, out var factory) && factory.ResumesIdleWithoutPrompt(entry.Transport);
Relevance

●●● Strong

Recent resume and lifecycle race findings were accepted, supporting correction of stale state
suppressing recovery work.

PR-#1623
PR-#1655

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The newly added predicate suppresses the nudge for any true ledger value. A successful resume writes
the entry back as Running without resetting that value, and boot sorts running entries into resume
candidates; only graceful shutdown samples the current clock. The continuation handover also copies
and retains the predecessor's value.

src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs[874-882]
src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs[694-701]
src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs[1162-1184]
src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs[539-565]
src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs[245-263]
src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs[1046-1051]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A waiting snapshot survives into the running ledger entry, so crash recovery can mistake a later active turn for an idle one.
## Fix Focus Areas
- src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs[875-879]
- src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs[1162-1184]
- src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs[1046-1051]
## Recommended Fix
Consume or clear the saved waiting snapshot when writing a running resumed or continuation entry. Keep capturing the live clock at graceful shutdown, and test crash recovery after a resumed agent starts another turn.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
⚠️ Tickets: not configured — No jira, linear integration is connected for the linked tickets
✅ Compliance rules (platform): 59 rules
✅ Cross-repo context — repo relationships
  Explored: repo: kurrent-io/kcap-server (sha: ee98c349) — View relationship
Review mode: Auto: ⚖️ Balanced: Stateful restart behavior spans ledger, orchestration, transports, and lifecycle paths.

Grey Divider

Tip of the day
💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs Outdated
Comment thread src/Capacitor.Cli.Daemon/Services/AgentOrchestrator.Resume.cs
Marking it after launch could overwrite a turn begun during registration, and a running entry that kept the shutdown's waiting state would resume a crashed turn without its nudge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@realtonyyoung
realtonyyoung merged commit a18313a into main Oct 25, 2026
13 checks passed
@realtonyyoung
realtonyyoung deleted the tonyyoung/ai-4257-resume-idle-without-nudge branch October 25, 2026 00:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Daemon restart nudges agents that were already idle into a new turn

1 participant