composeSoul
JavaScript · package toolnexus · SPEC §7E · js/src/agents/home.ts
function composeSoul(dir: string): { soul: string; found: string[] }Read a persona’s home directory and assemble its system prompt. Seven well-known filenames —
AGENTS.md, SOUL.md, IDENTITY.md, USER.md, TOOLS.md, HEARTBEAT.md, MEMORY.md — are
each read (when present) and joined as a ## <filename> section, always in that fixed order
regardless of directory-listing order. Absent files are silently skipped.
When to use it
Section titled “When to use it”Reach for composeSoul when you want the file layout — the identity/memory/heartbeat
directory shape §7E defines — without the rest of the persona machinery. It is a pure
dir -> string read: no tool wiring, no agent registration, no side effects. That makes it useful
on its own for previewing a soul before spawning anything, or for feeding the same composed text
into something that isn’t an agents.Agent at all (a bare createClient call’s systemPrompt,
for instance).
Why this and not the alternative
Section titled “Why this and not the alternative”Two behaviors are worth pinning explicitly, because they are easy to get wrong by hand:
- Order is fixed, not filesystem order.
AGENTS.mdalways precedesMEMORY.mdin the output even if the OS lists them the other way — identity comes first, durable memory comes last. - The 2 MB cap is measured in bytes, not characters. A file over
2097152bytes is truncated at the byte boundary (which can split a multibyte UTF-8 character) with a trailing notice appended to the in-memory string — the file on disk is never touched.
Examples
Section titled “Examples”1. One file present — the rest are silently skipped
Section titled “1. One file present — the rest are silently skipped”import assert from "node:assert"import fs from "node:fs"import os from "node:os"import path from "node:path"import { agents } from "toolnexus"
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "persona-"))fs.writeFileSync(path.join(dir, "SOUL.md"), "You are Ava, a calm and precise ops assistant.")
const { soul, found } = agents.composeSoul(dir)
assert.deepEqual(found, ["SOUL.md"])assert.equal(soul, "## SOUL.md\n\nYou are Ava, a calm and precise ops assistant.")
console.log("ok:", found.join(","))2. Several files — always assembled in bootstrap order
Section titled “2. Several files — always assembled in bootstrap order”Files are written here in reverse order on purpose — MEMORY.md before AGENTS.md — to show
that the output order does not depend on write order or directory listing order.
import assert from "node:assert"import fs from "node:fs"import os from "node:os"import path from "node:path"import { agents } from "toolnexus"
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "persona-"))fs.writeFileSync(path.join(dir, "MEMORY.md"), "The team prefers metric units.")fs.writeFileSync(path.join(dir, "AGENTS.md"), "Be concise. Never guess a balance.")fs.writeFileSync(path.join(dir, "USER.md"), "Reports to the ops lead.")
const { soul, found } = agents.composeSoul(dir)
// IDENTITY.md/TOOLS.md/HEARTBEAT.md were absent — skipped, not blank sections.assert.deepEqual(found, ["AGENTS.md", "USER.md", "MEMORY.md"])assert.ok(soul.indexOf("## AGENTS.md") < soul.indexOf("## USER.md"))assert.ok(soul.indexOf("## USER.md") < soul.indexOf("## MEMORY.md"))
console.log("ok:", found.join(" -> "))3. Oversized file — truncated in memory, byte-capped, disk untouched
Section titled “3. Oversized file — truncated in memory, byte-capped, disk untouched”import assert from "node:assert"import fs from "node:fs"import os from "node:os"import path from "node:path"import { agents } from "toolnexus"
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "persona-"))const oversized = "x".repeat(2 * 1024 * 1024 + 10) // 10 bytes over the 2 MB capfs.writeFileSync(path.join(dir, "MEMORY.md"), oversized)
const { soul, found } = agents.composeSoul(dir)
assert.deepEqual(found, ["MEMORY.md"])assert.match(soul, /\[truncated: exceeds 2 MB bootstrap cap\]$/)// The cap is on the composed IN-MEMORY string — the file on disk is never rewritten.assert.equal(fs.statSync(path.join(dir, "MEMORY.md")).size, oversized.length)
console.log("ok: soul truncated, disk file still", fs.statSync(path.join(dir, "MEMORY.md")).size, "bytes")Options
Section titled “Options”| Field | Type | What it is |
|---|---|---|
dir |
string |
The persona’s home directory. |
What you get back
Section titled “What you get back”| Field | Type | What it is |
|---|---|---|
soul |
string |
The joined ## <filename> sections, in bootstrap order — empty string if no files were found. |
found |
string[] |
Which of the seven bootstrap filenames were present, in bootstrap order. |
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.