package session import ( "os" "path/filepath" "sync" "agent-native/runtime/llm" ) // Store is an append-only JSONL transcript. New sessions are a tree of // entries (id / parentId). Load still returns the model-visible message // path, and files written as one llm.Message per line remain readable. type Store struct { path string mu sync.Mutex } func Open(path string) (*Store, error) { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return nil, err } f, err := os.OpenFile(path, os.O_CREATE, 0o644) if err != nil { return nil, err } _ = f.Close() return &Store{path: path}, nil } func (s *Store) Path() string { return s.path } // Append records one message on the active branch. func (s *Store) Append(msg llm.Message) error { s.mu.Lock() defer s.mu.Unlock() _, err := s.appendEntryLocked(Entry{Type: TypeMessage, Message: &msg}) return err } // Load returns the model-visible messages on the active branch. // A compaction entry replaces earlier turns with its summary plus the // retained tail; the file itself is never rewritten. // Crash residue: a trailing line without a newline that is not valid JSON // is dropped. A bad line in the middle is still an error. func (s *Store) Load() ([]llm.Message, error) { s.mu.Lock() defer s.mu.Unlock() entries, err := s.readEntriesLocked() if err != nil { return nil, err } return visibleMessages(entries, s.leafLocked(entries)), nil }