Toolnexus.Agents.Home (toolnexus v0.12.0)

Copy Markdown View Source

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:

  1. from_dir/2the 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).
  2. memory_tool/1 — one add|replace|remove tool over MEMORY.md/USER.md. Writes go to disk; the live prompt is intentionally NOT touched (the frozen snapshot re-reads on the next session). A missing replace/remove substring is a loud isError.
  3. 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. A HEARTBEAT_OK reply 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 to Runtime.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

beats(started)

The non-silent beats collected so far (HEARTBEAT_OK replies excluded).

bootstrap_order()

@spec bootstrap_order() :: [String.t()]

The canonical bootstrap file order (identity first, memory last).

compose_soul(dir)

@spec compose_soul(String.t()) :: {String.t(), [String.t()]}

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).

from_dir(dir, opts \\ [])

@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 — set false to omit the memory tool (a read-only persona)

heartbeat_ok()

@spec heartbeat_ok() :: String.t()

The reserved silent-beat reply. A reply containing it produces no report.

memory_tool(dir)

@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.

start_agent(agent, rt_opts, opts \\ [])

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.

stop(started)

@spec stop(Toolnexus.Agents.Home.Started.t()) :: :ok

Stop the heartbeat and tear the runtime down.

sync(started)

@spec sync(Toolnexus.Agents.Home.Started.t()) :: :ok

Await the heartbeat process (its beat, if any, has been fully applied).