state: l.state,
report: l.Report,
note: l.note,
+ fail: l.fail,
notify: notify,
running: make(map[string]*SubagentRun),
}
// Submit does. A failed write stops the loop, because the log refuses all
// writes after it.
func (l *TurnLoop) Report(name, text, status string) {
- message, err := l.memory.Append("work", text)
- if err == nil {
- l.notify(Event{
- Kind: "work",
- ID: message.ID,
- Text: text,
- Source: name,
- Status: status,
- })
- }
-
- l.mutex.Lock()
+ message, err := l.Work(name, text, status)
switch {
case err != nil:
- l.failure = fmt.Errorf("%w: %w", errChainLogWrite, err)
+ l.fail(fmt.Errorf("%w: %w", errChainLogWrite, err))
case status != subagentStopped:
- l.pending = append(l.pending, message)
+ l.Wake(message)
+ }
+}
+
+// Work logs text as a work message of source, such as the name of a subagent,
+// and tells the clients, with status. The message starts no turn until it goes
+// to Wake.
+func (l *TurnLoop) Work(source, text, status string) (Message, error) {
+ message, err := l.memory.Append("work", text)
+ if err != nil {
+ return Message{}, err
+ }
+
+ l.notify(Event{
+ Kind: "work",
+ ID: message.ID,
+ Text: text,
+ Source: source,
+ Status: status,
+ })
+
+ return message, nil
+}
+
+// Wake adds the logged messages to the waiting messages and wakes the loop
+// once, as a user message of Submit does. They join the first request of a new
+// turn together, or the next request of a running turn after the tool results.
+func (l *TurnLoop) Wake(messages ...Message) {
+ if len(messages) == 0 {
+ return
}
+
+ l.mutex.Lock()
+ l.pending = append(l.pending, messages...)
l.mutex.Unlock()
select {
}
// Run runs a turn each time user messages or reports wait, until ctx ends. It
-// returns the error of a failed log write, which stops the agent. At its end
-// it ends the runs of the subagents and waits for them.
+// returns the error of a failed log write, which stops the agent. At its start
+// it resumes the subagents that a stop of the daemon cut, and at its end it
+// ends the runs of the subagents and waits for them.
func (l *TurnLoop) Run(ctx context.Context) error {
defer l.runs.Close()
+ err := l.runs.Resume(ctx)
+ if err != nil {
+ return fmt.Errorf("resume subagents: %w", err)
+ }
+
for {
for l.waiting() {
err := l.turn(ctx)
return len(l.pending) > 0
}
+// fail stops the loop with err.
+func (l *TurnLoop) fail(err error) {
+ l.mutex.Lock()
+ l.failure = errors.Join(l.failure, err)
+ l.mutex.Unlock()
+
+ select {
+ case l.wake <- struct{}{}:
+ default:
+ }
+}
+
// failed returns the error of a failed write of a report.
func (l *TurnLoop) failed() error {
l.mutex.Lock()