Skip to content

Toolnexus.Agents.Home.dreamer

Elixir · package toolnexus · SPEC §7E · elixir/lib/toolnexus/agents/home.ex

@spec dreamer(String.t(), keyword() | map()) :: AgentDef.t()
def dreamer(dir, opts) # opts: :store (required), :instructions, :name, :model
# the one-line form, on from_dir:
Home.from_dir(dir, dream: %{every_ms: ..., store: ..., instructions: ...})

Toolnexus.Agents.Home.dreamer builds the dreamer: a plain agent whose job is to keep a persona’s memory true. Its tools are exactly two — memory (with the notes tier on, over dir) and transcript_search (over your conversation store) — never the persona’s shell or file tools. Its soul is one ## HEARTBEAT.md section: the SPEC constant DREAM_PROMPT, then DREAM.md from the home dir (if present), then instructions (if given). Construction fails without a store, or with a store that cannot list().

Each pass works in order: verify every index line and note against the transcripts (fix what they contradict, drop only what they contradict — a claim they do not mention stays) → demote detail out of MEMORY.md into a note behind a [[slug]] pointer → merge duplicate notes → repair links → log one dream: … entry to memory/log.md. A pass with nothing to change writes nothing and replies HEARTBEAT_OK, which the heartbeat keeps silent.

Use it for a long-lived persona whose memory is written by the model across many sessions: a personal assistant, a support agent, an on-call operator. Those memories drift — a wrong fact gets recorded once and then re-read every session. The dreamer fixes that offline, against the transcripts, so the agent’s memory gets better while it sleeps, not worse.

Skip it for one-shot workers, for memory a human curates by hand, or when you keep no transcripts (there is nothing to verify against). See the Dreaming guide for the full when-to-use table and cadence advice.

A hand-written HEARTBEAT.md consolidator (the earlier recipe) can only tidy what memory already says. It cannot tell a true note from a wrong one. The dreamer checks claims against the transcripts, which are the ground truth, and records every change in memory/log.md, so a bad “tidy” can be seen and reverted.

1. The smallest useful call — dream nightly, one line

Section titled “1. The smallest useful call — dream nightly, one line”
alias Toolnexus.Agents.Home
alias Toolnexus.Client.FileConversationStore
store = FileConversationStore.new("./data/conversations") # your client's transcripts
ava =
Home.from_dir("./personas/ava",
notes: true,
memory_index_cap: true,
dream: %{every_ms: 24 * 60 * 60_000, store: store} # dream nightly
)
started = Home.start_agent(ava, rt_opts, every_ms: 30 * 60_000)
# ... the dreamer runs on the same runtime; Home.stop(started) stops both

2. The realistic case — your own schedule

Section titled “2. The realistic case — your own schedule”
store = FileConversationStore.new("./data/conversations")
d = Home.dreamer("./personas/ava", store: store) # a plain AgentDef
# Your schedule: a 6-hourly heartbeat with its own on_beat.
started =
Home.start_agent(d, rt_opts,
every_ms: 6 * 60 * 60_000,
on_beat: fn summary -> IO.puts("dreamt: #{summary}") end
)

3. The full surface — goal-focused prose and a single pass on demand

Section titled “3. The full surface — goal-focused prose and a single pass on demand”
d =
Home.dreamer("./personas/ava",
store: store,
instructions: "Pay special attention to the user's travel preferences.", # after DREAM.md
model: "openai/gpt-4o-mini"
)
# One pass, now — from a CI job, a button, or a test.
r = Toolnexus.Agents.run(d, rt_opts, Home.heartbeat_prompt())
IO.puts(r.text) # "HEARTBEAT_OK" when memory already matched the transcripts

Put standing guidance in DREAM.md in the home dir (it is read when the dreamer is built); use instructions for guidance that depends on the host.

Option Default What it does
store — (required) The conversation store to verify against. It must support list().
instructions none Host prose appended after DREAM_PROMPT and DREAM.md.
name <basename(dir)>-dreamer The agent’s name.
model inherit Model id. inherit uses the runtime’s model; a cheaper model is usually enough.

On Home.from_dir, the dream option takes everyMs (must be > 0), store, and optional instructions. Leaving it out changes nothing.

  • Dreaming guide — the memory layout, what a pass does, safety and cadence.
  • transcriptSearchTool — the read-only search the dreamer verifies with.
  • memoryTool — the notes tier (note:<slug>, read, list) the dreamer writes with.
  • fromDir — where the one-line dream option lives.