diff --git a/pkg/agent/core.go b/pkg/agent/core.go index 7fb384f..333262e 100644 --- a/pkg/agent/core.go +++ b/pkg/agent/core.go @@ -289,6 +289,11 @@ type agent struct { submitMu sync.Mutex // serializes Submit calls (commands + turns) warnedContext bool // true after a context-usage warning; cleared on compaction auditLog *olog.Logger + + // Debounced session persistence + saveMu sync.Mutex + saveDirty bool + saveTimer *time.Timer } // ToolCallCount returns the total number of tool calls executed in this @@ -460,6 +465,7 @@ func NewAgentCore(cfg AgentCoreConfig) Core { // Close releases resources for this session, including its tmpdir. func (s *agent) Close() { s.log.Debug("Close() session=%q", s.sessionID) + s.flushSave() if s.runtime != nil && s.runtime.Dispatcher != nil { if srv, ok := s.runtime.Dispatcher.GetServer("execute"); ok { if c, ok := srv.(interface{ Close() }); ok { @@ -805,6 +811,27 @@ func (s *agent) runCompact(ctx context.Context, trigger string) (int, error) { } func (s *agent) saveSession() { + s.saveMu.Lock() + s.saveDirty = true + if s.saveTimer == nil { + s.saveTimer = time.AfterFunc(2*time.Second, s.flushSave) + } + s.saveMu.Unlock() +} + +// flushSave immediately persists the session if dirty. +func (s *agent) flushSave() { + s.saveMu.Lock() + dirty := s.saveDirty + s.saveDirty = false + if s.saveTimer != nil { + s.saveTimer.Stop() + s.saveTimer = nil + } + s.saveMu.Unlock() + if !dirty { + return + } if s.session == nil || s.sessionID == "" || s.sessionsDir == "" { return } @@ -1292,6 +1319,7 @@ func (s *agent) executeTurn(ctx context.Context, input string) string { s.mu.Unlock() replyBuf.Reset() s.setState("idle") + s.flushSave() if err != nil { // Keep completed work — only remove cancelled tool results.