]> git.giacomolli.uk - onechat/blob - daemon_children.go
Check the width of comment lines
[onechat] / daemon_children.go
1 package main
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "sync"
8 )
9
10 // DaemonChildren starts children made by the root during a daemon run. The root
11 // calls Create in its turn goroutine. Close runs only after that loop stops.
12 // The daemon context, not the calling turn, owns each child's lifetime.
13 type DaemonChildren struct {
14 ctx context.Context
15 cancel context.CancelFunc
16 root *TurnLoop
17 messages *DaemonMessages
18 opened DaemonAgents
19 workers sync.WaitGroup
20 mutex sync.Mutex
21 failure error
22 }
23
24 // Create makes and opens a child, registers its queue, and starts its loop.
25 // Registration holds the queue lock through file creation, so a backup cannot
26 // copy a child before its open files are part of the snapshot.
27 func (c *DaemonChildren) Create(name, responsibility string) (string, error) {
28 if c.ctx.Err() != nil {
29 return "", c.ctx.Err()
30 }
31
32 if c.root.openChild == nil {
33 return "", errors.New("child creation is not available")
34 }
35
36 var opened *DaemonAgent
37
38 loop, err := c.messages.AddChild(c.root.agent.config.Name, name, func() (*TurnLoop, error) {
39 agent, err := c.root.agent.CreateChild(name, responsibility, c.root.runs.subagents)
40 if err != nil {
41 return nil, fmt.Errorf("create child config: %w", err)
42 }
43
44 opened, err = c.root.openChild(c.ctx, agent)
45 if err != nil {
46 return nil, fmt.Errorf("open child: %w", err)
47 }
48
49 loop, err := opened.Loop(nil, func(Event) {
50 })
51 if err != nil {
52 return nil, errors.Join(fmt.Errorf("open child loop: %w", err), opened.Close())
53 }
54
55 return loop, nil
56 })
57 if err != nil {
58 return "", fmt.Errorf("start child %q: %w", name, err)
59 }
60
61 c.opened = append(c.opened, opened)
62 c.workers.Add(1)
63
64 go func() {
65 defer c.workers.Done()
66
67 err := daemonRunChild(c.ctx, loop, opened.schedules)
68 if err != nil {
69 opened.logger.Error("child stopped", "err", err)
70 c.mutex.Lock()
71
72 c.failure = errors.Join(c.failure, err)
73 c.mutex.Unlock()
74 }
75
76 c.cancel()
77 }()
78
79 c.root.logger.Info("child started", "name", name)
80
81 return "Started " + name + ". Use tell to send it a task.", nil
82 }
83
84 // Close waits for the runtime children, closes their files, and returns their
85 // failures. The caller stops the daemon context before this call.
86 func (c *DaemonChildren) Close() error {
87 c.workers.Wait()
88
89 return errors.Join(c.failure, c.opened.Close())
90 }