agent: save session after each tool round for mid-turn crash recovery

Adds SaveSession callback to agentConfig, called after every state.update()
in the run loop. On restart, the session JSON contains all completed tool
rounds; the model continues naturally from current history.

SIGTERM is already handled in olliesrv via InterruptAll + Shutdown, which
cancels all session contexts and lets the loop exit cleanly after the
current state.update(). Worst-case (SIGKILL) loses one tool round, not
the whole turn.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-04-26 19:24:28 +02:00
parent eabe17a1b1
commit dc83763c24
2 changed files with 8 additions and 0 deletions

View File

@ -777,6 +777,7 @@ func (s *agent) executeTurn(ctx context.Context, input string, handler EventHand
"result": result,
}, s.log)
}
s.cfg.SaveSession = func() { s.saveSession() }
s.cfg.AutoCompact = func(ctx context.Context) {
if ctx.Err() != nil || s.session == nil {
return

View File

@ -24,6 +24,7 @@ type agentConfig struct {
GenerationParams backend.GenerationParams
PopInject func() string // returns and clears pending inject, or ""
AutoCompact func(ctx context.Context) // called after each tool round; may compact in-place
SaveSession func() // called after each state.update(); persists mid-turn progress
PreTool func(ctx context.Context, name string, args json.RawMessage) HookResult // called before each tool; exit 2 blocks execution
PostTool func(ctx context.Context, name string, args json.RawMessage, result string) HookResult // called after each tool; exit 0 appends, exit 2 replaces result
}
@ -127,6 +128,9 @@ func run(ctx context.Context, cfg agentConfig, state state) error {
})
}
state.update(msg, results)
if cfg.SaveSession != nil {
cfg.SaveSession()
}
return fmt.Errorf("step %d: stream interrupted: %w", step, ctx.Err())
}
@ -252,6 +256,9 @@ func run(ctx context.Context, cfg agentConfig, state state) error {
}
state.update(msg, results)
if cfg.SaveSession != nil {
cfg.SaveSession()
}
if interrupted {
return ctx.Err()