"encoding/json"
"errors"
"fmt"
- "log"
+ "log/slog"
"maps"
"os"
"path/filepath"
report func(name, text, status string)
note func(text string) error
fail func(err error)
- logger *log.Logger
+ logger *slog.Logger
notify func(Event)
mutex sync.Mutex
for _, name := range names {
_, err = r.Tell(ctx, name, text)
if err != nil {
- r.logger.Printf("subagent %s lost at restart: %v", name, err)
+ r.logger.Error("subagent lost at restart", "name", name, "err", err)
r.report(name, subagentReport(name, []string{"failed: " + err.Error()}), subagentFailed)
}
}
return r.save()
}
+// Snapshot locks the run state, memory, and subagent logs while snapshot reads
+// the agent files. The lock order agrees with Stop, which logs under the run
+// lock.
+func (r *SubagentRuns) Snapshot(ctx context.Context, snapshot func() error) error {
+ r.mutex.Lock()
+ defer r.mutex.Unlock()
+
+ return r.memory.Snapshot(ctx, func() error {
+ return r.subagents.Snapshot(ctx, snapshot)
+ })
+}
+
// Close ends every run and waits for them. No subagent starts after Close.
func (r *SubagentRuns) Close() {
r.mutex.Lock()
r.mutex.Unlock()
run.started = r.subagents.clock()
- r.logger.Printf("subagent %s started", run.subagent.name)
+ r.logger.Info("subagent started", "name", run.subagent.name)
r.notify(Event{
Kind: "subagent",
Text: run.subagent.name,
}
// run runs the chains of run and reports and logs its end with the length of
-// the run, unless Close ended it. A chain follows while messages wait after the final answer of the chain before
-// it, and no chain failed. A chain that Stop ends gives the reply "stopped".
+// the run, unless Close ended it. A chain follows while messages wait after the
+// final answer of the chain before 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()
status = subagentStopped
}
- length := durationText(r.subagents.clock().Sub(run.started))
+ length := r.subagents.clock().Sub(run.started)
switch status {
case subagentStopped:
- r.logger.Printf("subagent %s stopped after %s", run.subagent.name, length)
+ r.logger.Info("subagent stopped", "name", run.subagent.name, "after", length)
case subagentFailed:
- r.logger.Printf("subagent %s failed after %s: %v", run.subagent.name, length, failure)
+ r.logger.Error("subagent failed", "name", run.subagent.name, "after", length, "err", failure)
default:
- r.logger.Printf("subagent %s done after %s", run.subagent.name, length)
+ r.logger.Info("subagent done", "name", run.subagent.name, "after", length)
}
r.report(run.subagent.name, subagentReport(run.subagent.name, replies), status)
input = append(input, modelUserMessage(r.state()), modelUserMessage(log))
chain := &Chain{
client: r.client,
+ images: r.agent.Images(),
tools: &Toolbox{
log: run.subagent,
chat: r.memory,
subagents: r.subagents,
workdir: run.subagent.WorkDir(),
+ images: r.agent.Images(),
},
request: ModelRequest{
Model: r.agent.config.Model,
// SubagentRun is one run of a subagent: the view that its requests get, its
// effort, the end of its context, the messages of Tell that wait for a request,
-// whether Stop ended it, and the time it started. The mutex of SubagentRuns protects the messages
-// and the stop.
+// whether Stop ended it, and the time it started. The mutex of SubagentRuns
+// protects the messages and the stop.
type SubagentRun struct {
subagent *Subagent
view []string