From: Anderson A. Giacomolli Date: Wed, 11 Nov 2026 14:43:13 +0000 (-0300) Subject: Add local context to memory operation failures X-Git-Url: https://git.giacomolli.uk/onechat/commitdiff_plain/448c931a776c3e3f65ca2eab11da0c062d597818?ds=inline;hp=0cb73c3dde36d0078f3c1aa4200f2edcb5ceed38 Add local context to memory operation failures Memory, tree, view, log, and chain failures name their local operation while retaining the original error through wrapping. Combined result checks keep their successful nil error instead of wrapping it. Error classification, cancellation causes, and successful values stay intact. Issue: 3cdc678 --- diff --git a/agent.go b/agent.go index 13491c1..fbea57f 100644 --- a/agent.go +++ b/agent.go @@ -70,7 +70,7 @@ func openAgent(dir string) (*Agent, error) { config, err := defaultAgentConfig() if err != nil { - return nil, err + return nil, fmt.Errorf("prepare agent defaults: %w", err) } decoder := json.NewDecoder(bytes.NewReader(data)) @@ -345,7 +345,7 @@ func (c AgentConfig) validate() error { func createAgent(dir string) (*Agent, error) { config, err := defaultAgentConfig() if err != nil { - return nil, err + return nil, fmt.Errorf("prepare agent defaults: %w", err) } return writeAgent(dir, config) diff --git a/chain.go b/chain.go index f201812..c85b2a2 100644 --- a/chain.go +++ b/chain.go @@ -65,7 +65,7 @@ func (c *Chain) Run(ctx context.Context, input []any) (string, error) { input, text, err = c.handle(ctx, input, response) if err != nil { - return "", err + return "", fmt.Errorf("handle model response: %w", err) } if len(input) == 0 { @@ -87,7 +87,7 @@ func (c *Chain) handle(ctx context.Context, input []any, response ModelResponse) for _, raw := range response.Output { item, err := chainInputItem(raw) if err != nil { - return nil, "", err + return nil, "", fmt.Errorf("decode chain input: %w", err) } input = append(input, item) @@ -103,7 +103,7 @@ func (c *Chain) handle(ctx context.Context, input []any, response ModelResponse) case "message": text, err := modelOutputText([]json.RawMessage{raw}) if err != nil { - return nil, "", err + return nil, "", fmt.Errorf("read response text: %w", err) } text = strings.TrimSpace(text) diff --git a/compactor.go b/compactor.go index 6738955..2b06288 100644 --- a/compactor.go +++ b/compactor.go @@ -56,12 +56,12 @@ func newCompactor(client *ModelClient, instructions func() (string, error), mode func (c *Compactor) Summarize(ctx context.Context, task Task) (string, error) { text, err := compactorTask(task) if err != nil { - return "", err + return "", fmt.Errorf("prepare compaction task: %w", err) } instructions, err := c.instructions() if err != nil { - return "", err + return "", fmt.Errorf("read compaction instructions: %w", err) } source := task.Context + "\n" + text @@ -110,7 +110,11 @@ func (c *Compactor) Summarize(ctx context.Context, task Task) (string, error) { } if len(lines) == 0 { - return "", err + if err != nil { + return "", fmt.Errorf("summarize node: %w", err) + } + + return "", nil } return compactorShortest(lines), nil diff --git a/datadir.go b/datadir.go index f191e5c..41cc941 100644 --- a/datadir.go +++ b/datadir.go @@ -17,7 +17,7 @@ func dataDir() (string, error) { dir, err = defaultDataDir() if err != nil { - return "", err + return "", fmt.Errorf("find default data directory: %w", err) } } diff --git a/journal.go b/journal.go index 745ae79..6251e8c 100644 --- a/journal.go +++ b/journal.go @@ -38,13 +38,13 @@ type Journal struct { func (j *Journal) Write(day string, line []byte) (Position, error) { number, err := journalDayNumber(day) if err != nil { - return Position{}, err + return Position{}, fmt.Errorf("parse journal day: %w", err) } if number != j.day { err = j.closeWriter() if err != nil { - return Position{}, err + return Position{}, fmt.Errorf("close previous day writer: %w", err) } j.file, err = os.OpenFile(filepath.Join(j.dir, day+".jsonl"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600) @@ -150,7 +150,7 @@ func (j *Journal) Scan(ctx context.Context, place Position, add func([]byte, Pos for n, path := range paths { day, err := journalDayNumber(strings.TrimSuffix(filepath.Base(path), ".jsonl")) if err != nil { - return err + return fmt.Errorf("parse day filename: %w", err) } if day < place.day { @@ -164,7 +164,7 @@ func (j *Journal) Scan(ctx context.Context, place Position, add func([]byte, Pos err = journalScanFile(ctx, path, day, offset, n == len(paths)-1, add) if err != nil { - return err + return fmt.Errorf("scan day file: %w", err) } } diff --git a/log.go b/log.go index 1ed70e7..31e1789 100644 --- a/log.go +++ b/log.go @@ -65,7 +65,7 @@ func openLog(ctx context.Context, root string, clock func() time.Time) (*Log, er lock, err := lockFile(filepath.Join(root, "lock")) if err != nil { - return nil, err + return nil, fmt.Errorf("lock message log: %w", err) } l := &Log{ @@ -111,7 +111,7 @@ func (l *Log) catchUp(ctx context.Context) error { if l.index.count > 0 { record, _, err := l.index.Read(l.index.count - 1) if err != nil { - return err + return fmt.Errorf("read last log position: %w", err) } last := decodePosition(record) @@ -220,7 +220,7 @@ func (l *Log) Len() int { func (l *Log) Message(id int) (Message, error) { record, found, err := l.index.Read(id) if err != nil { - return Message{}, err + return Message{}, fmt.Errorf("read message position: %w", err) } if !found { diff --git a/memory.go b/memory.go index 49215ac..be58105 100644 --- a/memory.go +++ b/memory.go @@ -86,7 +86,7 @@ type MemoryViewFile struct { func openMemory(ctx context.Context, root string, clock func() time.Time, summarizer Summarizer, logger *slog.Logger) (*Memory, error) { log, err := openLog(ctx, root, clock) if err != nil { - return nil, err + return nil, fmt.Errorf("open memory log: %w", err) } m := &Memory{ @@ -207,7 +207,7 @@ func (m *Memory) restoreQueue() error { for id := range m.chat.end { built, err := m.tree.Built(0, id) if err != nil { - return err + return fmt.Errorf("check restored leaf: %w", err) } if built { @@ -216,7 +216,7 @@ func (m *Memory) restoreQueue() error { message, err := m.log.Message(id) if err != nil { - return err + return fmt.Errorf("read restored message: %w", err) } leaf, free := freeLeaf(message) @@ -228,13 +228,13 @@ func (m *Memory) restoreQueue() error { err = m.build(leaf) if err != nil { - return err + return fmt.Errorf("build restored leaf: %w", err) } } keys, err := m.tree.Keys() if err != nil { - return err + return fmt.Errorf("read built nodes: %w", err) } for _, key := range keys { @@ -244,7 +244,7 @@ func (m *Memory) restoreQueue() error { err = m.settle(key) if err != nil { - return err + return fmt.Errorf("settle restored node: %w", err) } } @@ -261,12 +261,12 @@ func (m *Memory) catchUp() error { for m.chat.end < m.log.Len() { message, err := m.log.Message(m.chat.end) if err != nil { - return err + return fmt.Errorf("read pending message: %w", err) } err = m.addMessage(message) if err != nil { - return err + return fmt.Errorf("add pending message: %w", err) } } @@ -305,7 +305,7 @@ func (m *Memory) viewPath() string { func (m *Memory) build(node Node) error { err := m.tree.Add(node) if err != nil { - return err + return fmt.Errorf("add built node: %w", err) } m.chat.Refresh(node) @@ -322,12 +322,16 @@ func (m *Memory) settle(key NodeKey) error { parent := NodeKey{key.level + 1, key.index / 2} ready, err := m.ready(parent) if err != nil || !ready { - return err + if err != nil { + return fmt.Errorf("check parent readiness: %w", err) + } + + return nil } left, right, err := m.children(parent) if err != nil { - return err + return fmt.Errorf("read parent children: %w", err) } node, free := freeMerge(left, right) @@ -339,7 +343,7 @@ func (m *Memory) settle(key NodeKey) error { err = m.tree.Add(node) if err != nil { - return err + return fmt.Errorf("add merged node: %w", err) } key = parent @@ -353,7 +357,7 @@ func (m *Memory) settle(key NodeKey) error { func (m *Memory) addMessage(message Message) error { built, err := m.tree.Built(0, message.ID) if err != nil { - return err + return fmt.Errorf("check message leaf: %w", err) } leaf, free := freeLeaf(message) @@ -362,7 +366,7 @@ func (m *Memory) addMessage(message Message) error { case free: err = m.build(leaf) if err != nil { - return err + return fmt.Errorf("build message leaf: %w", err) } default: m.queue.AddLeaf(message.ID) @@ -415,7 +419,7 @@ func (m *Memory) task(key NodeKey) (Task, error) { if key.level == 0 { message, err := m.log.Message(key.index) if err != nil { - return Task{}, err + return Task{}, fmt.Errorf("read leaf message: %w", err) } task.Input = []string{leafSource(message)} @@ -426,7 +430,7 @@ func (m *Memory) task(key NodeKey) (Task, error) { left, right, err := m.children(key) if err != nil { - return Task{}, err + return Task{}, fmt.Errorf("read task children: %w", err) } for _, child := range []Node{left, right} { @@ -483,13 +487,20 @@ func (m *Memory) ready(key NodeKey) (bool, error) { for _, index := range []int{2 * key.index, 2*key.index + 1} { built, err := m.tree.Built(key.level-1, index) if err != nil || !built { - return false, err + if err != nil { + return false, fmt.Errorf("check child readiness: %w", err) + } + + return false, nil } } built, err := m.tree.Built(key.level, key.index) + if err != nil { + return !built, fmt.Errorf("check parent readiness: %w", err) + } - return !built, err + return !built, nil } // children reads the two children of the node key. @@ -499,7 +510,7 @@ func (m *Memory) children(key NodeKey) (Node, Node, error) { for n := range children { child, built, err := m.tree.Node(key.level-1, 2*key.index+n) if err != nil { - return Node{}, Node{}, err + return Node{}, Node{}, fmt.Errorf("read child node: %w", err) } if !built { diff --git a/model.go b/model.go index c443a12..f630b3e 100644 --- a/model.go +++ b/model.go @@ -463,8 +463,11 @@ func modelReadEvent(response *ModelResponse, data []byte) (bool, error) { } response.Text, err = modelOutputText(response.Output) + if err != nil { + return true, fmt.Errorf("read completed response text: %w", err) + } - return true, err + return true, nil case "response.incomplete": reason := "unknown" if event.Response != nil && event.Response.IncompleteDetails != nil { diff --git a/syncer.go b/syncer.go index bf2a5ff..297405b 100644 --- a/syncer.go +++ b/syncer.go @@ -137,7 +137,7 @@ func (s *Syncer) Flush(ctx context.Context) error { } if failure != nil { - return failure + return fmt.Errorf("flush durable writes: %w", failure) } select { diff --git a/tree.go b/tree.go index 4309597..db93b1d 100644 --- a/tree.go +++ b/tree.go @@ -93,19 +93,19 @@ func (t *Tree) open(ctx context.Context, total int) error { t.order, err = openRecords(filepath.Join(t.dir, "tree.idx"), treeRecordBytes) if err != nil { - return err + return fmt.Errorf("open node order index: %w", err) } start := Position{} if t.order.count == 0 { err = t.removeLevels() if err != nil { - return err + return fmt.Errorf("remove empty level indexes: %w", err) } } else { record, _, err := t.order.Read(t.order.count - 1) if err != nil { - return err + return fmt.Errorf("read last node order: %w", err) } // A crash can stop an add between the two index writes, so the @@ -113,7 +113,7 @@ func (t *Tree) open(ctx context.Context, total int) error { node, last := treeDecodeRecord(record) err = t.writeLevel(node, last) if err != nil { - return err + return fmt.Errorf("restore last node level: %w", err) } start = Position{ @@ -182,7 +182,7 @@ func (t *Tree) removeLevels() error { func (t *Tree) writeLevel(node NodeKey, place Position) error { level, err := t.level(node.level) if err != nil { - return err + return fmt.Errorf("open node level index: %w", err) } record := make([]byte, positionBytes) @@ -203,7 +203,7 @@ func (t *Tree) check(node Node) error { built, err := t.Built(node.Level, node.Index) if err != nil { - return err + return fmt.Errorf("check target node: %w", err) } if built { @@ -217,7 +217,7 @@ func (t *Tree) check(node Node) error { for _, index := range []int{2 * node.Index, 2*node.Index + 1} { built, err = t.Built(node.Level-1, index) if err != nil { - return err + return fmt.Errorf("check child node: %w", err) } if !built { @@ -237,7 +237,7 @@ func (t *Tree) addIndex(node NodeKey, place Position) error { err := t.order.Append(record) if err != nil { - return err + return fmt.Errorf("append node order: %w", err) } return t.writeLevel(node, place) @@ -274,12 +274,16 @@ func (t *Tree) place(level, index int) (Position, bool, error) { records, err := t.level(level) if err != nil { - return Position{}, false, err + return Position{}, false, fmt.Errorf("open node level index: %w", err) } record, found, err := records.Read(index) if err != nil || !found { - return Position{}, false, err + if err != nil { + return Position{}, false, fmt.Errorf("read node level record: %w", err) + } + + return Position{}, false, nil } place := decodePosition(record) @@ -295,7 +299,7 @@ func (t *Tree) Add(node Node) error { err := t.check(node) if err != nil { - return err + return fmt.Errorf("validate node: %w", err) } line, err := json.Marshal(node) @@ -320,7 +324,11 @@ func (t *Tree) Add(node Node) error { func (t *Tree) Node(level, index int) (Node, bool, error) { place, built, err := t.place(level, index) if err != nil || !built { - return Node{}, false, err + if err != nil { + return Node{}, false, fmt.Errorf("find node position: %w", err) + } + + return Node{}, false, nil } line, err := t.journal.Read(place) diff --git a/view.go b/view.go index 4a6e567..8877462 100644 --- a/view.go +++ b/view.go @@ -88,7 +88,7 @@ func (v *View) Append(tree ViewNodeReader) (bool, error) { line := NodeKey{0, v.end} _, err := v.load(tree, line) if err != nil { - return false, err + return false, fmt.Errorf("load appended leaf: %w", err) } v.lines = append(v.lines, line) @@ -121,14 +121,18 @@ func (v *View) fit(tree ViewNodeReader) (bool, error) { for v.merging && size > v.low { best, err := v.mostDue(tree) if err != nil || best < 0 { - return merged, err + if err != nil { + return merged, fmt.Errorf("choose merge candidate: %w", err) + } + + return merged, nil } left, right := v.lines[best], v.lines[best+1] parent := NodeKey{left.level + 1, left.index / 2} _, err = v.load(tree, parent) if err != nil { - return merged, err + return merged, fmt.Errorf("load merged parent: %w", err) } size += v.lineSize(parent) - v.lineSize(left) - v.lineSize(right) @@ -341,7 +345,7 @@ func restoreView(state ViewState, high, low int, tree ViewNodeReader) (*View, er built, err := v.load(tree, line) if err != nil { - return nil, err + return nil, fmt.Errorf("load restored view node: %w", err) } if line.level > 0 && !built {