Liking cljdoc? Tell your friends :D

toolnexus.agents.home

Agent home — the persona surface (SPEC.md §7E).

A persona is an identity that lives in FILES, keeps durable MEMORY it can edit, and runs on a HEARTBEAT so it can act unprompted. All three ride seams that already ship: the composed soul is just a system prompt, the memory tool is a plain Tool, and the heartbeat is post + wake on the §7D runtime's INJECTABLE CLOCK. This layer adds no runtime behavior — it is a directory convention plus composition, not new machinery.

(def ava (home/from-dir "./personas/ava"))

;; one-shot, or as a tool in someone else's toolkit
(def rt (rt/create-runtime {:registry {(:name ava) ava} :llm llm}))
(rt/run-agent rt (:name ava) "what is on my plate?")
(rt/agent-tool rt (:name ava))

;; …or give it its own clock
(def started (home/start-agent ava {:llm llm} {:every-ms 1800000
                                               :on-beat println}))
((:stop started))

Everything here is also usable against a plain client with no runtime at all — a composed soul is a :system-prompt, and the memory tool is a tool like any other.

Agent home — the persona surface (SPEC.md §7E).

A persona is an identity that lives in FILES, keeps durable MEMORY it can edit,
and runs on a HEARTBEAT so it can act unprompted. All three ride seams that
already ship: the composed soul is just a system prompt, the memory tool is a
plain `Tool`, and the heartbeat is `post` + `wake` on the §7D runtime's
INJECTABLE CLOCK. This layer adds no runtime behavior — it is a directory
convention plus composition, not new machinery.

    (def ava (home/from-dir "./personas/ava"))

    ;; one-shot, or as a tool in someone else's toolkit
    (def rt (rt/create-runtime {:registry {(:name ava) ava} :llm llm}))
    (rt/run-agent rt (:name ava) "what is on my plate?")
    (rt/agent-tool rt (:name ava))

    ;; …or give it its own clock
    (def started (home/start-agent ava {:llm llm} {:every-ms 1800000
                                                   :on-beat println}))
    ((:stop started))

Everything here is also usable against a plain client with no runtime at all —
a composed soul is a `:system-prompt`, and the memory tool is a tool like any
other.
raw docstring

apply-index-capclj/s

(apply-index-cap content {:keys [max-lines max-bytes]} notes?)

The §7E index-cap algorithm over MEMORY.md's (already 2 MB-capped) content. Within both bounds ⇒ unchanged; else keep whole leading lines (a lone over-long first line is cut at a code-point boundary) + the notice.

The §7E index-cap algorithm over MEMORY.md's (already 2 MB-capped) content.
Within both bounds ⇒ unchanged; else keep whole leading lines (a lone
over-long first line is cut at a code-point boundary) + the notice.
sourceraw docstring

bootstrap-orderclj/s

Bootstrap discovery order (§7E): identity first, durable memory last. Each present file is injected into the soul as a ## <filename> section, in exactly this order. The order is part of the cross-port contract — a persona whose MEMORY.md outranked its SOUL.md would behave differently in seven languages.

Bootstrap discovery order (§7E): identity first, durable memory last. Each
present file is injected into the soul as a `## <filename>` section, in exactly
this order. The order is part of the cross-port contract — a persona whose
MEMORY.md outranked its SOUL.md would behave differently in seven languages.
sourceraw docstring

compose-soulclj/s

(compose-soul dir)
(compose-soul dir opts)

Compose the bootstrap files present in dir into one soul string — the frozen snapshot injected as the system prompt for a whole run.

Returns {:soul "…" :found ["SOUL.md" …]}. Only present files appear, always in bootstrap-order, each as a ## <filename> section with its body trimmed. Composition happens once, at session start: the soul is fixed for the run, which is what keeps a long-lived persona cache-stable.

Options (§7E "Memory hierarchy", both opt-in — absent ⇒ byte-identical):

:memory-index-cap true (200 lines / 25600 bytes) or {:max-lines :max-bytes}; caps MEMORY.md only, after the 2 MB cap, before the trim :notes notes tier on — selects the notice wording

Compose the bootstrap files present in `dir` into one soul string — the frozen
snapshot injected as the system prompt for a whole run.

Returns `{:soul "…" :found ["SOUL.md" …]}`. Only present files appear, always
in `bootstrap-order`, each as a `## <filename>` section with its body trimmed.
Composition happens once, at session start: the soul is fixed for the run, which
is what keeps a long-lived persona cache-stable.

Options (§7E "Memory hierarchy", both opt-in — absent ⇒ byte-identical):

  :memory-index-cap  true (200 lines / 25600 bytes) or {:max-lines :max-bytes};
                     caps MEMORY.md only, after the 2 MB cap, before the trim
  :notes             notes tier on — selects the notice wording
sourceraw docstring

dream-promptclj/s

What a dream pass is FOR and the order it works in (§7E "Dreaming"). Byte-identical in every port. It becomes the dreamer's ## HEARTBEAT.md section, so the ordinary heartbeat-prompt wake drives a pass and HEARTBEAT_OK keeps an idle pass silent.

What a dream pass is FOR and the order it works in (§7E "Dreaming").
Byte-identical in every port. It becomes the dreamer's `## HEARTBEAT.md`
section, so the ordinary `heartbeat-prompt` wake drives a pass and
`HEARTBEAT_OK` keeps an idle pass silent.
sourceraw docstring

dreamerclj/s

(dreamer dir opts)

The built-in dreamer preset (§7E "Dreaming"): a plain AgentDef whose tools are EXACTLY memory (notes on, over dir) and transcript_search (over :store) — never the persona's own tools. Its soul is one ## HEARTBEAT.md section: dream-prompt, then <dir>/DREAM.md (trimmed, if non-empty), then :instructions (trimmed, if non-empty), joined by blank lines.

Options: :store (required; must carry :list), :instructions, :name (default <basename(dir)>-dreamer), :model (default "inherit").

Run one pass with (rt/run-agent rt name heartbeat-prompt) or schedule it with start-agent.

The built-in dreamer preset (§7E "Dreaming"): a plain AgentDef whose tools
are EXACTLY `memory` (notes on, over `dir`) and `transcript_search` (over
`:store`) — never the persona's own tools. Its soul is one `## HEARTBEAT.md`
section: `dream-prompt`, then `<dir>/DREAM.md` (trimmed, if non-empty), then
`:instructions` (trimmed, if non-empty), joined by blank lines.

Options: `:store` (required; must carry `:list`), `:instructions`, `:name`
(default `<basename(dir)>-dreamer`), `:model` (default `"inherit"`).

Run one pass with `(rt/run-agent rt name heartbeat-prompt)` or schedule it
with `start-agent`.
sourceraw docstring

from-dirclj/s

(from-dir dir)
(from-dir dir {:keys [does model tools memory notes dream] :as opts})

Build a persona AgentDef from a home directory (§7E).

Discovers the bootstrap files in dir, composes them into a FROZEN soul snapshot (once, here — the soul is fixed for the whole run, which is what keeps a long-lived persona cache-stable), and wires a memory tool over the same directory unless :memory false asks for a read-only persona.

Options:

:name the agent name; default the directory's last segment :does the routing description a delegating model sees :model default "inherit" — the runtime's own :llm model :tools extra tools, placed BEFORE the memory tool :memory false omits the memory tool :memory-index-cap cap the injected MEMORY.md (true ⇒ 200 lines / 25600 bytes, or {:max-lines :max-bytes}); off by default :notes notes tier of the memory tool (note:<slug>, read, list) :dream {:every-ms :store :instructions} — when this persona is started with start-agent, a dreamer over the same dir runs on the same runtime every :every-ms

The return value is a plain AgentDef map, which is this port's spelling of §7D's Level-1 agent: put it in a runtime's :registry and both directions of the axiom work on it unchanged — (rt/run-agent rt name prompt) is .run, and (rt/agent-tool rt name) is .asTool. There is no Agent object to learn, because a def IS the agent here. run/agent-tool never dream.

Build a persona AgentDef from a home directory (§7E).

Discovers the bootstrap files in `dir`, composes them into a FROZEN soul
snapshot (once, here — the soul is fixed for the whole run, which is what keeps
a long-lived persona cache-stable), and wires a `memory` tool over the same
directory unless `:memory false` asks for a read-only persona.

Options:

  :name              the agent name; default the directory's last segment
  :does              the routing description a delegating model sees
  :model             default `"inherit"` — the runtime's own `:llm` model
  :tools             extra tools, placed BEFORE the memory tool
  :memory            `false` omits the memory tool
  :memory-index-cap  cap the injected MEMORY.md (`true` ⇒ 200 lines / 25600
                     bytes, or `{:max-lines :max-bytes}`); off by default
  :notes             notes tier of the memory tool (`note:<slug>`, read, list)
  :dream             `{:every-ms :store :instructions}` — when this persona is
                     started with `start-agent`, a `dreamer` over the same dir
                     runs on the same runtime every `:every-ms`

The return value is a plain AgentDef map, which is this port's spelling of
§7D's Level-1 agent: put it in a runtime's `:registry` and both directions of
the axiom work on it unchanged — `(rt/run-agent rt name prompt)` is `.run`, and
`(rt/agent-tool rt name)` is `.asTool`. There is no Agent object to learn,
because a def IS the agent here. `run`/`agent-tool` never dream.
sourceraw docstring

heartbeat-okclj/s

The silent-no-op sentinel: a heartbeat reply containing this surfaces nothing.

The silent-no-op sentinel: a heartbeat reply containing this surfaces nothing.
sourceraw docstring

heartbeat-promptclj/s

The prompt a heartbeat wakes the persona with. It contains HEARTBEAT_OK so the model knows the silent-reply contract, and the word "Heartbeat" so a HEARTBEAT.md that keys off it can recognise the trigger.

The prompt a heartbeat wakes the persona with. It contains `HEARTBEAT_OK` so
the model knows the silent-reply contract, and the word "Heartbeat" so a
HEARTBEAT.md that keys off it can recognise the trigger.
sourceraw docstring

max-file-bytesclj/s

Per-file bootstrap cap (§7E), measured in BYTES (2 MiB), not characters. A larger file is injected truncated; the file on disk is untouched.

Per-file bootstrap cap (§7E), measured in BYTES (2 MiB), not characters. A
larger file is injected truncated; the file on disk is untouched.
sourceraw docstring

memory-index-cap-presetclj/s

:memory-index-cap true ⇒ this preset: 200 lines / 25600 bytes.

`:memory-index-cap true` ⇒ this preset: 200 lines / 25600 bytes.
sourceraw docstring

memory-index-noticeclj/s

(memory-index-notice max-lines max-bytes notes?)

The exact notice appended to a capped MEMORY.md (§7E). <N>/<B> are the CAP values, not the kept counts.

The exact notice appended to a capped MEMORY.md (§7E). `<N>`/`<B>` are the
CAP values, not the kept counts.
sourceraw docstring

memory-notes-descriptionclj/s

The notes-on description (§7E, exact): says what memory is FOR.

The notes-on description (§7E, exact): says what memory is FOR.
sourceraw docstring

memory-notes-input-schemaclj/s

source

memory-toolclj/s

(memory-tool dir)
(memory-tool dir {:keys [notes]})

The memory builtin (§7E) — file-backed and OPT-IN. Not one of the default §4A builtins: it exists only when a home directory is wired.

One tool, three actions over MEMORY.md (the agent's own notes) and USER.md (its model of the user):

add append an entry replace swap the FIRST occurrence of an existing substring for with remove delete the FIRST occurrence of an existing substring

With {:notes true} it also takes target "note:<slug>" (<dir>/memory/<slug>.md, never injected into the soul) and two read actions, read and list. Without it, the tool is byte-identical to the pre-hierarchy tool.

Every action writes to DISK. It does NOT touch the live session's system prompt: under the frozen-snapshot rule the edit loads at the START of the next session. A replace/remove whose substring is absent is a loud isError. Writes over one path are serialized within this process (see with-path-lock).

The `memory` builtin (§7E) — file-backed and OPT-IN. Not one of the default
§4A builtins: it exists only when a home directory is wired.

One tool, three actions over `MEMORY.md` (the agent's own notes) and `USER.md`
(its model of the user):

  add      append an entry
  replace  swap the FIRST occurrence of an existing substring for `with`
  remove   delete the FIRST occurrence of an existing substring

With `{:notes true}` it also takes `target "note:<slug>"` (`<dir>/memory/<slug>.md`,
never injected into the soul) and two read actions, `read` and `list`. Without
it, the tool is byte-identical to the pre-hierarchy tool.

Every action writes to DISK. It does NOT touch the live session's system
prompt: under the frozen-snapshot rule the edit loads at the START of the next
session. A `replace`/`remove` whose substring is absent is a loud `isError`.
Writes over one path are serialized within this process (see `with-path-lock`).
sourceraw docstring

memory-tool-descriptionclj/s

source

memory-tool-input-schemaclj/s

source

start-agentclj/s

(start-agent agent-def run-opts {:keys [every-ms on-beat]})

Give a persona its own clock (§7E).

On each :every-ms interval the persona posts a tick to its OWN inbox — the unsolicited rail, where timer ticks COALESCE, so a beat slower than the turn it starts can never pile up — and, WHEN IDLE, wakes it with heartbeat-prompt. A reply containing heartbeat-ok is SILENT: only a substantive reply is collected and handed to :on-beat. Silence is the default, which is the whole point — a persona that reported every beat would be a cron job with a bill.

Every timer goes through the RUNTIME'S INJECTABLE CLOCK, never a sleep: pass {:clock (rt/virtual-clock)} in run-opts and a fixture drives the beats with ((:advance! clock) ms), deterministically.

run-opts is anything rt/create-runtime takes except :registry, which is derived from agent-def (plus any registry the caller supplies, so a persona with a :team still resolves its team-mates).

A persona built by from-dir with :dream also gets its dreamer registered on the SAME runtime, spawned under its root, on its own heartbeat at the dream's :every-ms. Dreamer replies never reach :on-beat / :beats — what a dream did is in memory/log.md. :stop stops both.

Returns:

:runtime the live runtime — the host's seam for INBOUND channels. §7E is explicit that channels are the host's job: deliver an external event by calling rt/post / rt/wake on :handle :handle the persona's handle id :beats an atom holding the substantive beats so far (HEARTBEAT_OK excluded) :stop (fn []) — cancel the heartbeat(s) and close the tree gracefully

Throws only if the persona cannot be spawned at all; a spawn failure is a configuration error at the root, which is the one place §7D permits a throw.

Give a persona its own clock (§7E).

On each `:every-ms` interval the persona `post`s a tick to its OWN inbox — the
unsolicited rail, where timer ticks COALESCE, so a beat slower than the turn it
starts can never pile up — and, WHEN IDLE, `wake`s it with `heartbeat-prompt`.
A reply containing `heartbeat-ok` is SILENT: only a substantive reply is
collected and handed to `:on-beat`. Silence is the default, which is the whole
point — a persona that reported every beat would be a cron job with a bill.

Every timer goes through the RUNTIME'S INJECTABLE CLOCK, never a sleep: pass
`{:clock (rt/virtual-clock)}` in `run-opts` and a fixture drives the beats with
`((:advance! clock) ms)`, deterministically.

`run-opts` is anything `rt/create-runtime` takes except `:registry`, which is
derived from `agent-def` (plus any registry the caller supplies, so a persona
with a `:team` still resolves its team-mates).

A persona built by `from-dir` with `:dream` also gets its dreamer registered
on the SAME runtime, spawned under its root, on its own heartbeat at the
dream's `:every-ms`. Dreamer replies never reach `:on-beat` / `:beats` — what
a dream did is in memory/log.md. `:stop` stops both.

Returns:

  :runtime  the live runtime — the host's seam for INBOUND channels. §7E is
            explicit that channels are the host's job: deliver an external
            event by calling `rt/post` / `rt/wake` on `:handle`
  :handle   the persona's handle id
  :beats    an atom holding the substantive beats so far (HEARTBEAT_OK
            excluded)
  :stop     `(fn [])` — cancel the heartbeat(s) and close the tree gracefully

Throws only if the persona cannot be spawned at all; a spawn failure is a
configuration error at the root, which is the one place §7D permits a throw.
sourceraw docstring

truncation-noticeclj/s

Notice appended to a bootstrap file truncated at max-file-bytes.

Notice appended to a bootstrap file truncated at `max-file-bytes`.
sourceraw docstring

valid-note-slug?clj/s

(valid-note-slug? slug)

§7E: ^[a-z0-9][a-z0-9._-]{0,63}$ and no .. — no separators, no traversal.

§7E: `^[a-z0-9][a-z0-9._-]{0,63}$` and no `..` — no separators, no traversal.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close