ComposeSoul
Go · package github.com/muthuishere/toolnexus/golang/agents · SPEC §7E · golang/agents/home.go
var BootstrapOrder = []string{ "AGENTS.md", "SOUL.md", "IDENTITY.md", "USER.md", "TOOLS.md", "HEARTBEAT.md", "MEMORY.md",}
func ComposeSoul(dir string) (soul string, found []string)ComposeSoul is the directory-is-the-agent primitive: it reads BootstrapOrder’s seven
well-known filenames out of dir, in openclaw convention order (identity first, memory
last), and joins each present one into a single soul string as a "## <filename>" section.
Absent files are skipped silently. Each file is read with a 2 MB byte cap — a larger file is
truncated with a notice appended, and the on-disk file is untouched. found lists exactly
which files were present, in the order they were composed.
When to use it
Section titled “When to use it”Reach for ComposeSoul when you’re building a persona-shaped agent by hand — composing the
soul yourself into agents.Spec.Soul — rather than going through FromDir,
which calls it for you. It’s also the right call when you only want the composed prompt text
(to log it, hash it for cache-stability checks, or feed it to a different agent constructor)
without building a full *agents.Agent.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. The smallest useful call — compose from two files
Section titled “1. The smallest useful call — compose from two files”package main
import ( "fmt" "log" "os" "path/filepath" "strings"
"github.com/muthuishere/toolnexus/golang/agents")
func main() { dir, err := os.MkdirTemp("", "persona-*") if err != nil { log.Fatal(err) } defer os.RemoveAll(dir)
if err := os.WriteFile(filepath.Join(dir, "SOUL.md"), []byte("You are Kip, a terse assistant."), 0o644); err != nil { log.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "MEMORY.md"), []byte("- prefers metric units"), 0o644); err != nil { log.Fatal(err) }
soul, found := agents.ComposeSoul(dir) if len(found) != 2 || found[0] != "SOUL.md" || found[1] != "MEMORY.md" { log.Fatalf("unexpected found order: %v", found) } if !strings.Contains(soul, "## SOUL.md") { log.Fatalf("soul missing SOUL.md section: %s", soul) }
fmt.Println("ok: composed from", found)}2. The realistic case — absent files skip silently, order is fixed
Section titled “2. The realistic case — absent files skip silently, order is fixed”Only AGENTS.md and USER.md exist here; ComposeSoul still walks the full
BootstrapOrder, composing only what it finds, in that canonical order — not directory
listing order or creation order.
package main
import ( "fmt" "log" "os" "path/filepath" "strings"
"github.com/muthuishere/toolnexus/golang/agents")
func main() { dir, err := os.MkdirTemp("", "persona-*") if err != nil { log.Fatal(err) } defer os.RemoveAll(dir)
// Write USER.md first, on disk, but BootstrapOrder still puts AGENTS.md ahead of it. if err := os.WriteFile(filepath.Join(dir, "USER.md"), []byte("The user is Muthu."), 0o644); err != nil { log.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "AGENTS.md"), []byte("Reply in one line."), 0o644); err != nil { log.Fatal(err) }
soul, found := agents.ComposeSoul(dir) if len(found) != 2 || found[0] != "AGENTS.md" || found[1] != "USER.md" { log.Fatalf("expected AGENTS.md before USER.md, got %v", found) } if strings.Index(soul, "AGENTS.md") > strings.Index(soul, "USER.md") { log.Fatal("expected AGENTS.md section to precede USER.md section in the composed text") }
fmt.Println("ok: composed", found, "- SOUL.md/IDENTITY.md/etc were absent and skipped")}3. The full surface — wired into an agent as Spec.Soul, and a truncation notice
Section titled “3. The full surface — wired into an agent as Spec.Soul, and a truncation notice”ComposeSoul’s output is exactly what FromDir assigns to agents.Spec.Soul — this example
does that assembly explicitly, and also shows the 2 MB truncation notice on an oversized file.
package main
import ( "bytes" "encoding/json" "fmt" "io" "log" "net/http" "os" "path/filepath" "strings"
"github.com/muthuishere/toolnexus/golang/agents")
func main() { dir, err := os.MkdirTemp("", "persona-*") if err != nil { log.Fatal(err) } defer os.RemoveAll(dir)
if err := os.WriteFile(filepath.Join(dir, "IDENTITY.md"), []byte("You are Archivist."), 0o644); err != nil { log.Fatal(err) } // One byte over the 2 MB cap: ComposeSoul truncates with a notice, on-disk file untouched. oversized := bytes.Repeat([]byte("x"), 2*1024*1024+1) if err := os.WriteFile(filepath.Join(dir, "TOOLS.md"), oversized, 0o644); err != nil { log.Fatal(err) }
soul, found := agents.ComposeSoul(dir) if len(found) != 2 || found[0] != "IDENTITY.md" || found[1] != "TOOLS.md" { log.Fatalf("unexpected found: %v", found) } if !strings.Contains(soul, "[truncated: exceeds 2 MB bootstrap cap]") { log.Fatal("expected a truncation notice for the oversized TOOLS.md") } onDisk, _ := os.ReadFile(filepath.Join(dir, "TOOLS.md")) if len(onDisk) != len(oversized) { log.Fatal("on-disk file must stay untouched by the read-time truncation") }
// Assign the composed soul exactly as FromDir does. archivist := agents.New("archivist", agents.Spec{Does: "keeps records", Soul: soul, Model: "m-archivist"})
transport := roundTripFunc(func(req *http.Request) (*http.Response, error) { body, _ := json.Marshal(map[string]any{ "choices": []any{map[string]any{"message": map[string]any{"role": "assistant", "content": "archived"}}}, "usage": map[string]any{"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2}, }) return &http.Response{StatusCode: 200, Header: http.Header{"Content-Type": []string{"application/json"}}, Body: io.NopCloser(bytes.NewReader(body))}, nil }) r, _ := archivist.Run(agents.Options{Transport: transport}, "file this") if r.Status != "done" || r.Text != "archived" { log.Fatalf("unexpected run result: %+v", r) }
fmt.Println("ok: soul carries", len(found), "sections and truncates oversized files safely")}
type roundTripFunc func(*http.Request) (*http.Response, error)
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { return f(req) }Fields and behavior
Section titled “Fields and behavior”| Aspect | Behavior |
|---|---|
BootstrapOrder |
AGENTS.md, SOUL.md, IDENTITY.md, USER.md, TOOLS.md, HEARTBEAT.md, MEMORY.md — fixed, identity-first, memory-last. |
| Absent files | Skipped silently — no error, no placeholder section. |
| Per-file cap | 2 MB (2097152 bytes), byte-based (may split a multibyte character). Larger ⇒ truncated + "\n[truncated: exceeds 2 MB bootstrap cap]" appended; on-disk file is never modified. |
| Section format | Each present file becomes "## <filename>\n\n<trimmed body>", joined with "\n\n". |
found |
The ordered list of filenames that were actually present — use it to know what shaped the soul. |
| Timing | Composition happens once, at session start — the soul is a frozen snapshot for the run (the cache-stability rule). |
See also
Section titled “See also”FromDir— Point at an agent home directory and get a configured agent back.MemoryTool— The opt-in built-in that lets a persona write durable notes to its own home.agents.New— the constructorSpec.Soulfeeds into.