update readme
This commit is contained in:
parent
c00129a488
commit
1158efffe3
101
README.md
101
README.md
|
|
@ -202,106 +202,7 @@ 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:
|
`spawn_subagents.py` reads a JSON spec from stdin, fans out the work concurrently, and returns a JSON array of results — a single `execute_tool` call from the parent's perspective. Note: this requires adding Python language support to `execute_code`, `execute_tool`, and `execute_pipe`, which is low effort.
|
||||||
|
|
||||||
```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.
|
|
||||||
|
|
||||||
The same pattern works as a reusable agent tool. `spawn_subagents.py` reads a JSON spec from stdin, fans out the work concurrently, and returns a JSON array of results — a single `execute_tool` call from the parent's perspective. Note: this requires adding Python language support to `execute_code`, `execute_tool`, and `execute_pipe`, which is low effort.
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
|
||||||
Reference in New Issue