// This file holds PersistTurn, the session-tree persistence step shared by the // REPL loop and the /goal autonomous loop. It reads and advances a session's // mutable cursor state through the Host contract, so a command need not import // the concrete replDeps aggregate to persist the tail of a turn. package cli import ( "fmt" "io" "time" ) // PersistTurn appends the messages produced since the last persist as a new // branch descending from the host's current leaf, advancing the leaf and the // persisted-message cursor. Growing the tree with AppendBranch (rather than a // full linear Save) is what lets a later /tree leaf-switch fork the on-disk // history instead of clobbering it. If nothing new was produced it is a no-op: // rewriting the file would regenerate entry ids and flatten the tree. func PersistTurn(out io.Writer, h Host) { agentCtx := h.AgentCtx() // Automatic compaction during a turn rewrites Messages into a summary + recent // tail, shrinking the slice below the persisted cursor. The append-a-tail // branch model no longer holds (the prefix changed and Messages[persisted:] // would be out of range), so re-save the flattened context linearly and reset // the branch cursor to the new leaf, mirroring the /compact handler. if h.Persisted() > len(agentCtx.Messages) { header := h.Header() header.UpdatedAt = time.Now().UTC() if err := h.Store().Save(header, agentCtx.Messages); err != nil { fmt.Fprintf(out, "pigo: session save failed: %v\n", err) return } h.SetPersisted(len(agentCtx.Messages)) h.SetCurLeaf("") if _, entries, err := h.Store().LoadEntries(header.ID); err == nil && len(entries) > 0 { h.SetCurLeaf(entries[len(entries)-1].ID) } return } tail := agentCtx.Messages[h.Persisted():] if len(tail) == 0 { return } header := h.Header() header.UpdatedAt = time.Now().UTC() leaf, err := h.Store().AppendBranch(header, h.CurLeaf(), tail) if err != nil { fmt.Fprintf(out, "pigo: session save failed: %v\n", err) return } h.SetCurLeaf(leaf) h.SetPersisted(len(agentCtx.Messages)) }