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 {
15 cancel context.CancelFunc
17 messages *DaemonMessages
19 workers sync.WaitGroup
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()
32 if c.root.openChild == nil {
33 return "", errors.New("child creation is not available")
36 var opened *DaemonAgent
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)
41 return nil, fmt.Errorf("create child config: %w", err)
44 opened, err = c.root.openChild(c.ctx, agent)
46 return nil, fmt.Errorf("open child: %w", err)
49 loop, err := opened.Loop(nil, func(Event) {
52 return nil, errors.Join(fmt.Errorf("open child loop: %w", err), opened.Close())
58 return "", fmt.Errorf("start child %q: %w", name, err)
61 c.opened = append(c.opened, opened)
65 defer c.workers.Done()
67 err := daemonRunChild(c.ctx, loop, opened.schedules)
69 opened.logger.Error("child stopped", "err", err)
72 c.failure = errors.Join(c.failure, err)
79 c.root.logger.Info("child started", "name", name)
81 return "Started " + name + ". Use tell to send it a task.", nil
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 {
89 return errors.Join(c.failure, c.opened.Close())