Catch interrupts in the agent turn loop#81
Merged
Merged
Conversation
The agent loop in both corteza::chat() and the ~/bin/corteza CLI wrapped turn() in a tryCatch that only caught errors, so an R-level interrupt (Esc in RStudio, Ctrl+C in either) escaped the REPL and dropped the user out of the session mid-tool-call. There was no way to abort a multi-step loop without killing R. Catch the interrupt condition at the turn() boundary. In the CLI, inspect the callr worker's state: if it's "busy" a tool call was in flight, so send SIGINT to the child and drain the queued result; recycle the worker only if it doesn't return to "idle". When the interrupt arrived during the LLM round-trip the worker is already idle and we leave it alone so we don't drop worker-local state like the background-process registry. Inject a synthetic [Interrupted by user before completing.] marker into history (turn_session$history in chat(), the persistent session in the CLI) so the next turn's LLM sees the prior exchange was aborted instead of silently losing the prompt. Terminal Esc remains unsupported — terminals send raw \\033 for Esc, not a signal — and is documented as such in NEWS.
load_saber_briefing() wrapped saber::briefing() in capture.output(), which only grabs stdout. saber::briefing() emits its full text via message(), so the briefing leaked to the user's terminal every time session_setup() ran — including the subagents that archival spawns post-turn. That made interrupts look like they were restarting the session when they were just landing right before a normal archival cycle. Wrap the briefing call in suppressMessages() so message() is silenced while capture.output() continues to catch any stdout side effects.
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
turn()are now caught in bothcorteza::chat()and the~/bin/cortezaCLI, returning control to the prompt instead of dropping out of the session.callrworker's state on interrupt:busymeans a tool was running, so it sends SIGINT and drains;idlemeans the LLM round-trip was in flight and the worker is left alone (preserves background-process registry etc.). Worker is recycled only as a fallback.[Interrupted by user before completing.]marker is injected into history (and transcript) so the next turn's LLM sees the prior exchange was aborted.load_saber_briefing()now wrapssaber::briefing()insuppressMessages(). The briefing was leaking viamessage()every time a subagent calledsession_setup, which had been masquerading as a session restart whenever archival fired after a turn.Terminal Esc remains a no-op — terminals send raw
\033for Esc, not a signal. RStudio Esc works because the IDE translates it to an R interrupt. Documented in NEWS.Test plan
tinypkgr::reload("corteza"); corteza::chat(), kick off a multi-step request, press Esc mid-loop. Expect: yellow "Interrupted." and the prompt comes back.corteza::install_cli(), exit R, runcorteza, give it a long bash call (sleep 30), press Ctrl+C. Expect: "Interrupted.", worker recovers silently, next prompt works without restarting.# Briefing: <project>text no longer prints after a turn completes / archival fires.Notes
Codex review of an earlier draft caught two real issues that this PR fixes: the worker state check used the wrong string (
"ready"instead of"idle"), which would have recycled the worker on every interrupt and dropped its state; and the prior turn was invisible to the next call because we returnedNULLwithout persisting any marker. Both are addressed here.