Drive graceful agent suspend from ctx cancellation

Collapse the dual-mechanism (ctx.Done() = abort + WithStopSignal =
graceful suspend) into a single signal: ctx.Done() now means
graceful suspend. coreLoop shadows the incoming ctx with
context.WithoutCancel(ctx) on entry and uses the shadow for every
downstream call (LLM, tools, hooks, guardrails, save), keeping the
original ctx only for the at-boundary cancellation check.
restoreNestedSuspended applies the same shadow to its
saveProgress closure so partial nested-restore writes survive a
graceful cancel. Resume and resumeNested mirror the pattern so
their pre-loop tool dispatch is non-cancellable while coreLoop
still detects the cancel at its first turn boundary. The dedicated
stop signal API (WithStopSignal / stopSignalFrom) is removed.

There is no longer an in-process hard-abort path; tool authors
who need a deadline must derive it themselves. Document the new
contract on Run, RunStreamed, Resume, and Restore.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
Aurélien Sibiril
2026-04-27 09:51:45 +02:00
parent 3acc3191ea
commit c4228e8e7c
5 changed files with 76 additions and 124 deletions

View File

@@ -59,6 +59,11 @@ func (sr *StreamedRun) Wait() (*Result, error) {
return sr.result, sr.err
}
// RunStreamed launches the agent loop and returns immediately with a
// StreamedRun whose Events channel emits incremental progress. ctx
// follows the same graceful-suspend contract as Run: cancellation
// triggers a checkpoint at the next safe boundary and the run finishes
// with a *SuspendedError surfaced via Wait. See Run for details.
func (a *Agent) RunStreamed(ctx context.Context, messages []llm.Message, opts ...RunOption) *StreamedRun {
events := make(chan StreamEvent, 64)
sr := &StreamedRun{