-// load reads the saved view. A crash can stop an append after the log write and
-// before the view save, so load continues the view with the messages that it
-// does not cover yet.
-func (m *Memory) load() error {
- data, err := os.ReadFile(m.viewPath())
- if errors.Is(err, os.ErrNotExist) && m.log.Len() == 0 {
- m.chat = newView(chatHigh, chatLow)
- return nil
+// Summarizer builds the text of one node with a model.
+type Summarizer interface {
+ Summarize(ctx context.Context, task Task) (string, error)
+}
+
+// Memory is the memory of one chat: the message log, the summary tree, the chat
+// view, the compaction view, and the queue of the builds. The compaction view
+// is the chat view merged further, at the limits of 16 to 32 KB. It takes the
+// same new lines, and it merges again in one batch when it is too large or when
+// the chat view merges. So the compactions share its prefix in the prompt
+// cache. It saves the views to view.json after each change and loads them at
+// start. It never builds the views again from the log, because a new view
+// differs from the live one and so loses the prompt cache.
+//
+// Each write goes to the operating system at once, and a syncer makes the
+// writes durable by group commit in the background. Flush waits until the
+// writes so far are durable.
+//
+// The summarizer builds the nodes in the queue in the background. The start
+// reads the indexes once to fill the queue again. After that, only a new
+// message or a new node adds work. Memory keeps the nodes of the views and the
+// tasks of the running builds. It reads all other messages and nodes from their
+// day files through the indexes.
+type Memory struct {
+ mutex sync.Mutex
+ root string
+ log *Log
+ syncer *Syncer
+ tree *Tree
+ chat *View
+ cview *View
+ queue *BuildQueue
+ summarizer Summarizer
+ logger *log.Logger
+ ctx context.Context
+ cancel context.CancelFunc
+ workers sync.WaitGroup
+ changed chan struct{}
+}
+
+// Close stops the builds, waits for them, makes the writes durable, closes the
+// files of the chat, and releases its lock.
+func (m *Memory) Close() error {
+ m.cancel()
+ m.workers.Wait()
+
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ var err error
+ if m.syncer != nil {
+ err = m.syncer.Close()