17 // subagentDirName is the directory of the subagents in the directory of
19 subagentDirName = "subagents"
21 // subagentNameDraws is the most names that Create draws before it
23 subagentNameDraws = 1000
26 // newSubagents returns the subagents in dir, one directory for each name. The
27 // clock gives the dates of their messages, and draw gives the names of new
29 func newSubagents(dir string, clock func() time.Time, draw func() string) *Subagents {
34 open: make(map[string]*Subagent),
38 // Subagents holds the subagents of one agent. Each subagent has a directory
39 // named for it, which holds its log. A log takes an exclusive lock, so
40 // Subagents opens the log of a name once and keeps it open until Close. Its
41 // mutex protects the open logs.
42 type Subagents struct {
44 clock func() time.Time
48 open map[string]*Subagent
51 // Create makes the directory and the log of a new subagent. It draws a name
52 // again when the directory of the name exists.
53 func (s *Subagents) Create(ctx context.Context) (*Subagent, error) {
54 err := os.MkdirAll(s.dir, 0o700)
56 return nil, fmt.Errorf("create subagent directory: %w", err)
59 for range subagentNameDraws {
61 err = os.Mkdir(filepath.Join(s.dir, name), 0o700)
62 if errors.Is(err, fs.ErrExist) {
67 return nil, fmt.Errorf("create subagent directory: %w", err)
70 return s.Get(ctx, name)
73 return nil, fmt.Errorf("no free subagent name after %d draws", subagentNameDraws)
76 // Get returns the subagent name with its log open. A name that is not valid or
77 // that has no directory is an unknown agent.
78 func (s *Subagents) Get(ctx context.Context, name string) (*Subagent, error) {
80 defer s.mutex.Unlock()
82 subagent, found := s.open[name]
87 unknown := fmt.Errorf("no agent %s", name)
88 if !subagentNameValid(name) {
92 root := filepath.Join(s.dir, name)
93 info, err := os.Stat(root)
94 if errors.Is(err, fs.ErrNotExist) {
99 return nil, fmt.Errorf("read subagent directory: %w", err)
106 log, err := openLog(ctx, root, s.clock)
108 return nil, fmt.Errorf("open log of %s: %w", name, err)
111 syncer, err := openSyncer(root)
113 return nil, errors.Join(fmt.Errorf("open log of %s: %w", name, err), log.Close())
116 subagent = &Subagent{
121 s.open[name] = subagent
126 // Close makes the writes of each open log durable and closes it.
127 func (s *Subagents) Close() error {
129 defer s.mutex.Unlock()
132 for name, subagent := range s.open {
133 err = errors.Join(err, subagent.close())
140 // Subagent is one subagent and its log. Its mutex protects the log, because
141 // the run of the subagent writes it while the agent reads it with zoom.
142 type Subagent struct {
149 // Append logs a message of the subagent.
150 func (a *Subagent) Append(kind, text string) (Message, error) {
152 defer a.mutex.Unlock()
154 message, err := a.log.Append(kind, text)
156 return Message{}, err
164 // Flush returns when every write before the call is durable.
165 func (a *Subagent) Flush(ctx context.Context) error {
166 return a.syncer.Flush(ctx)
169 // Show returns the whole log, each message as "i|kind: text", with a blank line
171 func (a *Subagent) Show() (string, error) {
173 defer a.mutex.Unlock()
175 lines := make([]string, 0, a.log.Len())
176 for id := range a.log.Len() {
177 message, err := a.log.Message(id)
182 lines = append(lines, strconv.Itoa(id)+"|"+leafSource(message))
185 return strings.Join(lines, "\n\n"), nil
188 // close makes the writes durable and closes the log.
189 func (a *Subagent) close() error {
191 defer a.mutex.Unlock()
193 return errors.Join(a.syncer.Close(), a.log.Close())