X-Git-Url: http://git.giacomolli.uk/onechat/blobdiff_plain/e1e48ba388c28105fff3b70c8ca02304f5d57f53..47f69638bc96100f2e446f63c7c3689023156e64:/subagent_run.go?ds=sidebyside diff --git a/subagent_run.go b/subagent_run.go index b2e5e62..20b15f2 100644 --- a/subagent_run.go +++ b/subagent_run.go @@ -22,15 +22,16 @@ const ( // 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 @@ -43,6 +44,7 @@ type SubagentRuns struct { client ChainClient state func() string report func(text string, stopped bool) + note func(text string) error notify func(Event) mutex sync.Mutex @@ -172,6 +174,29 @@ func (r *SubagentRuns) Tell(ctx context.Context, name, message string) (string, 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() @@ -237,7 +262,7 @@ func (r *SubagentRuns) effort(subagent *Subagent) (string, error) { } // 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)) @@ -270,34 +295,40 @@ func (r *SubagentRuns) start(ctx context.Context, run *SubagentRun) error { // 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 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() } - 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) + r.report(subagentReport(run.subagent.name, replies), stopped) } // chain runs one request chain of run and returns its final answer. @@ -354,14 +385,16 @@ func (r *SubagentRuns) take(run *SubagentRun) []Message { } // 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