subagentNoteEffort = " at effort "
)
+// The statuses of the end of a run, in the work event of its report.
+const (
+ subagentFinished = "finished"
+ subagentStopped = "stopped"
+ subagentFailed = "failed"
+)
+
// errSubagentClosed tells that the runs of the subagents ended with the loop.
var errSubagentClosed = errors.New("the daemon stops, so no subagent starts")
-// SubagentRuns runs the subagents of one agent in the background. Each run is
-// a goroutine with its own context, which only Close ends. A run is one or more
-// request chains. The first request of each chain holds the view of the agent
-// at the start of the run, then the state of the turn, then the whole log of
-// the subagent. A message of Tell that comes during a chain joins its next
+// SubagentRuns runs the subagents of one agent in the background. Each run is a
+// goroutine with its own context, which only Stop and Close end. A run is one
+// or more request chains. The first request of each chain holds the view of the
+// agent at the start of the run, then the state of the turn, then the whole log
+// of the subagent. A message of Tell that comes during a chain joins its next
// request after the tool results. One that comes after the final answer starts
// one more chain, which reads the log again. The report of the run goes to
-// report: the final answer of each chain, or the error of a failed call. At the
-// stop of the daemon the runs end with no report.
+// report: the final answer of each chain, or the error of a failed call. Stop
+// ends one run, and its report says that it stopped. At the stop of the daemon
+// the runs end with no report.
//
// Spawn and Tell split the view with the layout of the agent, which has no
// lock. They are tools, so they run in the goroutine of the turns, as each
layout *ViewLayout
client ChainClient
state func() string
- report func(text string, stopped bool)
+ report func(name, text, status string)
+ note func(text string) error
notify func(Event)
mutex sync.Mutex
return "Sent. " + name + "'s reply will reach you as a message.", nil
}
+// Stop ends the run of the subagent name and tells if it ran. The chat gets the
+// note "Stopped by the user: NAME." under the mutex, so the note comes before
+// the report of the run.
+func (r *SubagentRuns) Stop(name string) (bool, error) {
+ r.mutex.Lock()
+ defer r.mutex.Unlock()
+
+ run, found := r.running[name]
+ if !found {
+ return false, nil
+ }
+
+ err := r.note("Stopped by the user: " + name + ".")
+ if err != nil {
+ return true, err
+ }
+
+ run.stopped = true
+ run.cancel()
+
+ return true, nil
+}
+
// Close ends every run and waits for them. No subagent starts after Close.
func (r *SubagentRuns) Close() {
r.mutex.Lock()
}
// start runs run in the background. Its context keeps the values of ctx, but
-// only Close ends it.
+// only Stop and Close end it.
func (r *SubagentRuns) start(ctx context.Context, run *SubagentRun) error {
ctx, run.cancel = context.WithCancel(context.WithoutCancel(ctx))
// run runs the chains of run and reports its end, unless Close ended it. A
// chain follows while messages wait after the final answer of the chain before
-// it, and no chain failed.
+// it, and no chain failed. A chain that Stop ends gives the reply "stopped".
func (r *SubagentRuns) run(ctx context.Context, run *SubagentRun) {
defer r.workers.Done()
defer run.cancel()
var replies []string
+ stopped := false
+ status := subagentFinished
for more := true; more; {
reply, err := r.chain(ctx, run)
- if err != nil {
+
+ r.mutex.Lock()
+ stopped = run.stopped
+ switch {
+ case err != nil && stopped:
+ reply = "stopped"
+ case err != nil:
reply = "failed: " + err.Error()
+ status = subagentFailed
}
- replies = append(replies, reply)
-
- r.mutex.Lock()
- more = err == nil && len(run.pending) > 0
+ more = err == nil && ctx.Err() == nil && len(run.pending) > 0
run.pending = nil
if !more {
delete(r.running, run.subagent.name)
}
r.mutex.Unlock()
+
+ replies = append(replies, reply)
}
- if ctx.Err() != nil {
+ if ctx.Err() != nil && !stopped {
return
}
- r.report(subagentReport(run.subagent.name, replies), false)
+ if stopped {
+ status = subagentStopped
+ }
+
+ r.report(run.subagent.name, subagentReport(run.subagent.name, replies), status)
}
// chain runs one request chain of run and returns its final answer.
}
// SubagentRun is one run of a subagent: the view that its requests get, its
-// effort, the end of its context, and the messages of Tell that wait for a
-// request. The mutex of SubagentRuns protects the messages.
+// effort, the end of its context, the messages of Tell that wait for a request,
+// and whether Stop ended it. The mutex of SubagentRuns protects the messages
+// and the stop.
type SubagentRun struct {
subagent *Subagent
view []string
effort string
cancel context.CancelFunc
pending []Message
+ stopped bool
}
// subagentReport returns the report of the subagent name with its replies: the