Agent home — personas over the §7D runtime (SPEC §7E).
The persona archetype: an identity that lives in files, durable memory the agent can edit, and a heartbeat so it can act unprompted. All three ride shipped seams — the soul injection point, the six verbs, the runtime-wide store — and add no new runtime behavior.
Three things, all recipes over Toolnexus.Agents:
from_dir/2— the directory IS the agent. Ordered bootstrap files (AGENTS.md,SOUL.md,IDENTITY.md,USER.md,TOOLS.md,HEARTBEAT.md,MEMORY.md) compose the soul as## <file>sections, 2 MB/file cap. Composition happens once at session start (frozen snapshot — cache-stable).memory_tool/1— oneadd|replace|removetool overMEMORY.md/USER.md. Writes go to disk; the live prompt is intentionally NOT touched (the frozen snapshot re-reads on the next session). A missingreplace/removesubstring is a loudisError.start_agent/3— a heartbeat: each interval posts a tick to the agent's own inbox (the unsolicited rail — ticks coalesce) and, when idle, wakes it. AHEARTBEAT_OKreply stays silent. All timing rides the runtime's injectable clock, so conformance fixtures run on a virtual clock. Channels (Telegram/etc.) stay the host's job — wire them toRuntime.post/3.
Higher patterns are composition, not API: dream / consolidation is a
start_agent whose HEARTBEAT.md says "fold notes into MEMORY.md via the memory
tool"; a channel assistant is the host's inbound handler calling post/wake.
Summary
Functions
The non-silent beats collected so far (HEARTBEAT_OK replies excluded).
The canonical bootstrap file order (identity first, memory last).
Compose the discovered bootstrap files under dir into one soul string (the frozen
snapshot). Returns {soul, found} where found is the discovered files in the
canonical order. Each present file becomes a ## <file> section; absent files are
skipped; a file over 2 MB is truncated with a notice (the on-disk file is untouched).
The directory IS the agent. Discovers the bootstrap files → soul (frozen snapshot),
and wires the memory tool over the same dir. Returns a Toolnexus.Agents.AgentDef
runnable with Toolnexus.Agents.run/3 like any agent.
The reserved silent-beat reply. A reply containing it produces no report.
The memory builtin over MEMORY.md (the agent's own notes) and USER.md (its
model of the user), under dir. One tool, three actions — add, replace,
remove — each writing to disk. A replace/remove whose target substring is
absent is a loud isError. Writes do NOT mutate the current session's prompt (the
frozen snapshot re-reads at the START of the next session); the description states
this to the model.
Start a long-lived persona with its own clock. Builds a runtime from rt_opts
(:transport, :llm, :clock, ...), spawns the persona, and arms a heartbeat.
Stop the heartbeat and tear the runtime down.
Await the heartbeat process (its beat, if any, has been fully applied).
Functions
@spec beats(Toolnexus.Agents.Home.Started.t()) :: [String.t()]
The non-silent beats collected so far (HEARTBEAT_OK replies excluded).
@spec bootstrap_order() :: [String.t()]
The canonical bootstrap file order (identity first, memory last).
Compose the discovered bootstrap files under dir into one soul string (the frozen
snapshot). Returns {soul, found} where found is the discovered files in the
canonical order. Each present file becomes a ## <file> section; absent files are
skipped; a file over 2 MB is truncated with a notice (the on-disk file is untouched).
@spec from_dir(String.t(), keyword() | map()) :: Toolnexus.Agents.AgentDef.t()
The directory IS the agent. Discovers the bootstrap files → soul (frozen snapshot),
and wires the memory tool over the same dir. Returns a Toolnexus.Agents.AgentDef
runnable with Toolnexus.Agents.run/3 like any agent.
Options:
:does— routing description (default"persona agent from <dir>"):name— agent name (default: the dir's basename):model— LLM model (default"inherit"):tools— extra tools beyond the memory builtin:memory— setfalseto omit the memory tool (a read-only persona)
@spec heartbeat_ok() :: String.t()
The reserved silent-beat reply. A reply containing it produces no report.
@spec memory_tool(String.t()) :: Toolnexus.Tool.t()
The memory builtin over MEMORY.md (the agent's own notes) and USER.md (its
model of the user), under dir. One tool, three actions — add, replace,
remove — each writing to disk. A replace/remove whose target substring is
absent is a loud isError. Writes do NOT mutate the current session's prompt (the
frozen snapshot re-reads at the START of the next session); the description states
this to the model.
@spec start_agent(Toolnexus.Agents.AgentDef.t(), keyword() | map(), keyword() | map()) :: Toolnexus.Agents.Home.Started.t()
Start a long-lived persona with its own clock. Builds a runtime from rt_opts
(:transport, :llm, :clock, ...), spawns the persona, and arms a heartbeat.
Each interval the heartbeat posts a tick to the agent's own inbox (the unsolicited
rail — ticks coalesce, so a slow beat can't pile up) and, when the agent is idle,
wakes it with a prompt to read HEARTBEAT.md and act, else reply HEARTBEAT_OK.
A HEARTBEAT_OK reply is silent (no report); only a substantive reply reaches
:on_beat. All timing rides the runtime's injectable clock (fixtures run virtual).
Options:
:every_ms— heartbeat interval (default 30 min):prompt— the heartbeat prompt (default asks to follow HEARTBEAT.md):on_beat—(text -> any)fired only for non-silent beats
Returns a Started struct. Use beats/1 to read collected non-silent beats,
sync/1 to await the current beat (deterministic under a virtual clock), and
stop/1 to tear everything down.
@spec stop(Toolnexus.Agents.Home.Started.t()) :: :ok
Stop the heartbeat and tear the runtime down.
@spec sync(Toolnexus.Agents.Home.Started.t()) :: :ok
Await the heartbeat process (its beat, if any, has been fully applied).