+// AddChild holds the queue lock while open makes a child's files and loop. The
+// loop joins backups before its first task can be delivered.
+func (m *DaemonMessages) AddChild(parent, name string, open func() (*TurnLoop, error)) (*TurnLoop, error) {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ if m.loops[name] != nil {
+ return nil, fmt.Errorf("name %q is already used", name)
+ }
+
+ root := m.loops[parent]
+ if root == nil || root.agent.config.Parent != "" {
+ return nil, errors.New("only the root can create children")
+ }
+
+ if m.failure != nil {
+ return nil, fmt.Errorf("agent message queue stopped: %w", m.failure)
+ }
+
+ loop, err := open()
+ if err != nil {
+ return nil, fmt.Errorf("open child: %w", err)
+ }
+
+ loop.messages = m
+ loop.tools.messages = m
+ m.loops[name] = loop
+ m.ordered = append(m.ordered, loop)
+
+ return loop, nil
+}
+
+// Child returns only a direct child of parent. Loops live until the daemon
+// stops, so the returned loop stays open for the caller's turn.
+func (m *DaemonMessages) Child(parent, name string) *TurnLoop {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ loop := m.loops[name]
+ if loop == nil || loop.agent.config.Parent != parent {
+ return nil
+ }
+
+ return loop
+}
+
+// ChildState lists the names and responsibilities of a parent's children in
+// name order. It returns no heading when the parent has no children.
+func (m *DaemonMessages) ChildState(parent string) string {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ var lines []string
+
+ for name, loop := range m.loops {
+ if loop.agent.config.Parent == parent {
+ lines = append(lines, name+": "+loop.agent.config.Responsibility)
+ }
+ }
+
+ if len(lines) == 0 {
+ return ""
+ }
+
+ slices.Sort(lines)
+
+ return "\n\nChildren:\n" + strings.Join(lines, "\n")
+}
+
+// SnapshotAgents locks the queue and all registered loops, including children
+// made during this daemon run, before snapshot copies their files.
+func (m *DaemonMessages) SnapshotAgents(ctx context.Context, snapshot func() error) error {
+ return m.Snapshot(func() error {
+ return daemonSnapshot(ctx, m.ordered, snapshot)
+ })
+}
+