Skip to content

Agent engine

The agent engine is what runs a goal to completion. You give a model a goal and a set of tools; it thinks, calls a tool, reads the result, and repeats until the goal is done or it hits the iteration cap. It runs on Ollama’s chat tool-calling, records the run to the audit store, and emits live state for the map.

The loop: hearth-loop

hearth-loop is the runner. It is plain Python (standard library only).

Terminal window
hearth-loop --model qwen2.5-coder --agent-name builder --workspace DIR "GOAL"
# run the loop against a mock model, no Ollama needed
hearth-loop --self-test

Each iteration: the model is given the goal, the available tools, and the results so far. It either calls a tool or replies with a final summary. The loop caps at 12 iterations so a confused model cannot run forever.

The loop is built to tolerate the quirks of small local models. It parses tool calls out of the message content (not just the structured field) and recovers from common malformations like a trailing comma, so a slightly-off tool call is not lost. When a tool result shows a recoverable failure (a missing package, a command not on PATH), the loop appends a short, actionable hint so a weak model can self-correct on the next turn instead of looping on the same mistake.

Every step emits runtime state (so the map can show the agent thinking) and the whole run is recorded to the audit database like any other run. See Observability & audit.

The tools

Tools live in a pluggable registry (agent/hearth_tools.py). Each tool has a name, a description, a JSON schema for its parameters, and a function that takes (args, workspace). Each is also assigned a risk class that decides whether it runs freely, needs approval, or is denied in a given mode.

File and workspace tools handle the basics inside the per-run workspace.

ToolWhat it doesRisk
read_fileRead a file from the workspace.safe
list_filesList files in a workspace directory.safe
list_treePrint an indented directory tree of the workspace (skips .git and build directories).safe
write_fileCreate or overwrite a file in the workspace.edit
run_commandRun a shell command in the workspace (build, test, inspect). Times out after a limit.dangerous

Search and edit tools find text and change it precisely.

ToolWhat it doesRisk
search_filesRegex or text grep across files (optional glob); returns path:line: text.safe
edit_fileExact find/replace in one file (first match or all); errors without changing anything if the text is absent.edit
replace_in_filesExact find/replace across all matching files under a path, a multi-file refactor.edit

Web tools reach out to the network.

ToolWhat it doesRisk
http_requestMake an HTTP request (url, method, headers, body); resolves cred: headers.dangerous
web_fetchFetch a URL and return it as readable text.dangerous
web_searchSearch the web (keyless DuckDuckGo) and return title, URL, and snippet.dangerous
fetch_to_kbFetch a web page and add it to the knowledge base in one step.dangerous

Knowledge base and memory tools persist what a run learns.

ToolWhat it doesRisk
kb_addAdd a document to the local knowledge base.edit
kb_searchSearch the local knowledge base.safe
index_dirIndex a whole directory into the knowledge base.edit
rememberWrite a lesson to long-term memory.edit
recallRetrieve lessons from long-term memory.safe

Self-knowledge tools let a run read the host and its own config.

ToolWhat it doesRisk
current_generation, list_generations, system_healthReport the running NixOS generation, the generation list, and system health.safe
read_self_config, git_status, git_diffRead hearth’s own config repo, its git status, and diffs.safe
nix_checkValidate the config with nix flake check --no-build (no build, no activation).safe
write_self_configWrite a file in hearth’s config repo (for self-evolution).edit

Adding a capability is adding one entry to the registry, so the surface an agent can touch is explicit and reviewable. write_self_config and nix_check are what the self-evolve flow uses to propose and validate changes to hearth’s own config. The self-knowledge and memory tools are what make the autonomy modes possible. Knowledge-base chunks and memory lessons are auto-recalled into the agent’s context at the start of a run, so what an earlier run learned is in front of the next one without being asked for.

Containment: the per-run workspace

Every file and command tool operates inside a per-run workspace and refuses any path that escapes it. A tool call trying to write to ../evil is rejected, not followed. Combined with the sandbox profile that the run executes under, the agent is contained at two layers: the tool layer refuses to leave the workspace, and the OS layer refuses writes outside the allow list.

For how http_request reaches credentials without ever seeing their values, see Agent credentials.

On-demand spawn

Agents do not have to be started by hand. The command center can launch one: it drops a small JSON request into /var/lib/hearth/queue, a systemd path-watcher (hearth-spawn) notices it, and starts a per-run sandboxed instance (hearth-agent@<id>). That instance reads the request, runs hearth-loop in a fresh workspace at /var/lib/hearth/agents/<id>, and removes the request file. The queue directory is the only extra path the instance can write, so a launch cannot reach anything else.

The launch path is self-healing: if the queue watcher ever falls into a failed state (which would otherwise silently swallow launches), enqueuing a run clears that state, processes the queue immediately, and re-arms the watcher for next time, so a launch never gets dropped without a trace.

command center -> /var/lib/hearth/queue/<id>.json
-> hearth-spawn (path watcher)
-> hearth-agent@<id> (sandboxed)
-> hearth-loop in /var/lib/hearth/agents/<id>
-> audited run + live map state