"time"
)
+// DaemonAgent owns the open files and model client of one persistent agent. Its
+// loop must stop before Close releases the files.
+type DaemonAgent struct {
+ agent *Agent
+ memory *Memory
+ client *ModelClient
+ subagents *Subagents
+ schedules *Schedules
+ clock func() time.Time
+ logger *slog.Logger
+ openChild func(context.Context, *Agent) (*DaemonAgent, error)
+}
+
+// DaemonAgents holds the root first and then its children in directory order.
+type DaemonAgents []*DaemonAgent
+
// openDaemonAgents opens the root and its direct children. An invalid child
// stops startup, and each opened agent is closed when startup fails.
func openDaemonAgents(ctx context.Context, dir, address string, auth ModelAuthorizer, compactor ModelConfig, clock func() time.Time, logger *slog.Logger) (agents DaemonAgents, err error) {
// one agent. All model calls use its cache key and usage file.
func openDaemonAgent(ctx context.Context, agent *Agent, address string, auth ModelAuthorizer, compactor ModelConfig, clock func() time.Time, logger *slog.Logger) (*DaemonAgent, error) {
client := newModelClient(address, auth, agent.CacheKey(), filepath.Join(agent.dir, "usage.jsonl"))
- memory, err := openMemory(ctx, agent.dir, clock, newCompactor(client, agent.Prompt, compactor, agent.Tools()), logger)
+ memory, err := openMemory(ctx, agent.dir, clock, newCompactor(client, agent.Prompt, compactor, agent.Tools(), agent.Images()), logger)
if err != nil {
return nil, fmt.Errorf("open memory: %w", err)
}
- schedules, err := openSchedules(filepath.Join(agent.dir, scheduleFileName), clock, logger)
+ schedules, err := openSchedules(filepath.Join(agent.dir, scheduleFileName), agent.config.Name, clock, logger)
if err != nil {
return nil, errors.Join(fmt.Errorf("open schedules: %w", err), memory.Close())
}
schedules: schedules,
clock: clock,
logger: logger,
+ openChild: func(ctx context.Context, child *Agent) (*DaemonAgent, error) {
+ return openDaemonAgent(ctx, child, address, auth, compactor, clock, logger.With("name", child.config.Name))
+ },
}, nil
}
-// DaemonAgent owns the open files and model client of one persistent agent. Its
-// loop must stop before Close releases the files.
-type DaemonAgent struct {
- agent *Agent
- memory *Memory
- client *ModelClient
- subagents *Subagents
- schedules *Schedules
- clock func() time.Time
- logger *slog.Logger
-}
-
// Loop makes the turn loop with its event destination. Only the root has a
// WhatsApp link. Child events stay with their own logs.
func (a *DaemonAgent) Loop(whatsapp *WhatsApp, notify func(Event)) (*TurnLoop, error) {
- return newTurnLoop(a.agent, a.memory, a.subagents, whatsapp, a.schedules, a.client, a.clock, a.logger, notify)
+ loop, err := newTurnLoop(a.agent, a.memory, a.subagents, whatsapp, a.schedules, a.client, a.clock, a.logger, notify)
+ if err != nil {
+ return nil, err
+ }
+
+ loop.openChild = a.openChild
+
+ return loop, nil
}
// Close flushes and closes the subagents and the memory.
return errors.Join(a.subagents.Close(), a.memory.Close())
}
-// DaemonAgents holds the root first and then its children in directory order.
-type DaemonAgents []*DaemonAgent
-
// Close closes all agents, including after a partial startup.
func (a DaemonAgents) Close() error {
var err error