Add concurrent Go sub-agent example to README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
272e095bc5
commit
5eb93f181b
99
README.md
99
README.md
|
|
@ -202,6 +202,105 @@ A session can spawn ephemeral child sessions to handle a focused task, then tear
|
||||||
|
|
||||||
**Sub-agents as function calls** — the parent primes the child with exactly the context it needs (excerpts from its own `chat`, relevant beads, local file contents) and receives a single focused reply. Failures are isolated; a child that errors or stalls can be killed and retried without affecting the parent. The pattern composes naturally: a sub-agent can itself spawn sub-agents, building a call tree bounded only by the number of sessions open at once.
|
**Sub-agents as function calls** — the parent primes the child with exactly the context it needs (excerpts from its own `chat`, relevant beads, local file contents) and receives a single focused reply. Failures are isolated; a child that errors or stalls can be killed and retried without affecting the parent. The pattern composes naturally: a sub-agent can itself spawn sub-agents, building a call tree bounded only by the number of sessions open at once.
|
||||||
|
|
||||||
|
The coordination logic can be written in any language that reads and writes files. The following Go example fans out N sub-agents concurrently, waits for all replies, and collects the results:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var base = filepath.Join(os.Getenv("HOME"), "mnt", "ollie")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
tasks := []string{
|
||||||
|
"summarize the key ideas in functional programming",
|
||||||
|
"summarize the key ideas in object-oriented programming",
|
||||||
|
"summarize the key ideas in logic programming",
|
||||||
|
}
|
||||||
|
for i, reply := range runSubAgents(tasks) {
|
||||||
|
fmt.Printf("=== agent %d ===\n%s\n", i+1, reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runSubAgents(tasks []string) []string {
|
||||||
|
type result struct {
|
||||||
|
idx int
|
||||||
|
reply string
|
||||||
|
}
|
||||||
|
results := make([]string, len(tasks))
|
||||||
|
ch := make(chan result, len(tasks))
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
for i, task := range tasks {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(idx int, task string) {
|
||||||
|
defer wg.Done()
|
||||||
|
sid := spawnSession()
|
||||||
|
defer killSession(sid)
|
||||||
|
os.WriteFile(filepath.Join(base, sid, "prompt"), []byte(task), 0644)
|
||||||
|
ch <- result{idx, waitReply(filepath.Join(base, sid, "reply"))}
|
||||||
|
}(i, task)
|
||||||
|
}
|
||||||
|
go func() { wg.Wait(); close(ch) }()
|
||||||
|
for r := range ch {
|
||||||
|
results[r.idx] = r.reply
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// spawnSession is serialised to avoid a race between snapshot and detection.
|
||||||
|
var spawnMu sync.Mutex
|
||||||
|
|
||||||
|
func spawnSession() string {
|
||||||
|
spawnMu.Lock()
|
||||||
|
defer spawnMu.Unlock()
|
||||||
|
before := sessionIDs()
|
||||||
|
os.WriteFile(filepath.Join(base, "ctl"), []byte("new\n"), 0644)
|
||||||
|
for {
|
||||||
|
for id := range sessionIDs() {
|
||||||
|
if !before[id] {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionIDs() map[string]bool {
|
||||||
|
entries, _ := os.ReadDir(base)
|
||||||
|
ids := make(map[string]bool)
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.IsDir() {
|
||||||
|
ids[e.Name()] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitReply(path string) string {
|
||||||
|
for {
|
||||||
|
if info, err := os.Stat(path); err == nil && info.Size() > 0 {
|
||||||
|
data, _ := os.ReadFile(path)
|
||||||
|
return strings.TrimSpace(string(data))
|
||||||
|
}
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func killSession(sid string) {
|
||||||
|
os.WriteFile(filepath.Join(base, "ctl"), []byte("kill "+sid+"\n"), 0644)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each goroutine spawns, primes, waits, and cleans up independently. The only serialised step is session creation, to avoid a race between snapshotting existing sessions and detecting the new one.
|
||||||
|
|
||||||
### Self-Generating Workflows
|
### Self-Generating Workflows
|
||||||
|
|
||||||
Since agents have access to `execute_code`, a session can write and execute a workflow script without any human involvement. Given a task and knowledge of the filesystem layout, an agent can decompose the work, spawn sessions, write the coordination script, and run it — all in a single turn. The README you are reading is essentially its system prompt. Conductor functionality, for free.
|
Since agents have access to `execute_code`, a session can write and execute a workflow script without any human involvement. Given a task and knowledge of the filesystem layout, an agent can decompose the work, spawn sessions, write the coordination script, and run it — all in a single turn. The README you are reading is essentially its system prompt. Conductor functionality, for free.
|
||||||
|
|
|
||||||
Reference in New Issue