]> git.giacomolli.uk - onechat/commitdiff
Add local context to memory operation failures
authorAnderson A. Giacomolli <redacted>
Wed, 11 Nov 2026 14:43:13 +0000 (11:43 -0300)
committerAnderson A. Giacomolli <redacted>
Wed, 11 Nov 2026 14:43:13 +0000 (11:43 -0300)
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

agent.go
chain.go
compactor.go
datadir.go
journal.go
log.go
memory.go
model.go
syncer.go
tree.go
view.go

index 13491c11d22c41fd8a272f017e4497bdebf7064e..fbea57f166e546b5600e699d2e902d657cf68cc4 100644 (file)
--- 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)
index f201812d31ba2440309c7e151e2d0b9df2c26f45..c85b2a2b0b0bc8e809c04c13cda1689e02283124 100644 (file)
--- 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)
index 6738955143a59125c77f01dd73a938eb0a3dd203..2b062882b18ae0e07dd363f3a8203d2302d28d64 100644 (file)
@@ -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
index f191e5cd6e80a11763480ceff232c372d1421e2b..41cc941e7c261a9b94472a2c87d6490cfdedd0a2 100644 (file)
@@ -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)
                }
        }
 
index 745ae790089ec1f5a9bf98debe7e17d212537859..6251e8c7d04b2f38749babc1b3f8b454a26563ea 100644 (file)
@@ -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 1ed70e78e36aaccaf862118cfe56c95fa107c671..31e17898855d4130dcf08b9f01a6a8ac35db0809 100644 (file)
--- 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 {
index 49215ac54405edb82e3957609713022aaeecf14c..be5810560dabf09ff9c5bdc52ed2a0bb6ae8c141 100644 (file)
--- 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 {
index c443a12981c8f47546396fd4c6150b2cc9fbdd18..f630b3e8f50ab76f92261bd8111f9010f0f76d95 100644 (file)
--- 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 {
index bf2a5ff98003d5a4155626ae1847ae3876df52c2..297405bd6d0eccdcfd9bb771050b3ff67eb7e0cd 100644 (file)
--- 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 4309597387a01ff0b6fbfc8e697a7c4a82f84830..db93b1d70357bb98e893e6ee6484b05570b24885 100644 (file)
--- 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 4a6e567563030631fbcd55fbc719a1adaeb8e119..8877462f6b028c80f54fc23a5d6a5f621545b1fe 100644 (file)
--- 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 {