diff --git a/pkg/agent/core.go b/pkg/agent/core.go index d9efa05..01c214e 100644 --- a/pkg/agent/core.go +++ b/pkg/agent/core.go @@ -709,6 +709,18 @@ func (s *agent) saveSession() { } } +// SaveSession writes the current session state to the given path, including +// backend and model metadata for external restore. +func (s *agent) SaveSession(path string) error { + s.mu.RLock() + defer s.mu.RUnlock() + if s.session == nil { + return fmt.Errorf("no active session") + } + return s.session.saveToFull(path, s.sessionID, s.agentName, + s.runtime.Backend.Name(), s.runtime.Backend.Model(), s.CWD()) +} + func (s *agent) getActionCancel() context.CancelCauseFunc { if a := s.currentAction.Load(); a != nil { return a.cancel diff --git a/pkg/agent/session.go b/pkg/agent/session.go index fe2459c..d03ff95 100644 --- a/pkg/agent/session.go +++ b/pkg/agent/session.go @@ -37,6 +37,8 @@ Be concise. Capture what another LLM needs to seamlessly continue.` type PersistedSession struct { ID string `json:"id"` Agent string `json:"agent,omitempty"` + Backend string `json:"backend,omitempty"` + Model string `json:"model,omitempty"` CWD string `json:"cwd,omitempty"` Messages []backend.Message `json:"messages"` TaskState *TaskState `json:"taskState,omitempty"` @@ -81,11 +83,18 @@ func (ts *TaskState) render() string { return sb.String() } -// SaveTo writes the full message history to path as JSON. +// saveTo writes the full message history to path as JSON. func (s *Session) saveTo(path, id, agentName, cwd string) error { + return s.saveToFull(path, id, agentName, "", "", cwd) +} + +// saveToFull writes session state including backend/model info. +func (s *Session) saveToFull(path, id, agentName, backendName, modelName, cwd string) error { ps := PersistedSession{ ID: id, Agent: agentName, + Backend: backendName, + Model: modelName, CWD: cwd, Messages: s.messages, TaskState: s.TaskState, @@ -97,6 +106,19 @@ func (s *Session) saveTo(path, id, agentName, cwd string) error { return os.WriteFile(path, data, 0600) } +// LoadPersistedSession reads a PersistedSession from a JSON file. +func LoadPersistedSession(path string) (*PersistedSession, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var ps PersistedSession + if err := json.Unmarshal(data, &ps); err != nil { + return nil, fmt.Errorf("parse session %s: %w", path, err) + } + return &ps, nil +} + // RestoreSession reconstructs a Session from a persisted message list. func RestoreSession(messages []backend.Message) *Session { s := &Session{messages: messages} diff --git a/pkg/agent/types.go b/pkg/agent/types.go index 60e6bc1..3ffeb44 100644 --- a/pkg/agent/types.go +++ b/pkg/agent/types.go @@ -139,6 +139,10 @@ type Core interface { // [ $(($(cat tcct) % 10)) -eq 0 ] && ... ToolCallCount() int64 + // SaveSession writes the current session state to the given path. + // The file includes all messages, agent/backend/model metadata, and task state. + SaveSession(path string) error + // Close releases resources associated with the session, including its // temporary directory under /tmp/ollie/. Close()