← Back to document history
Document version

How Hurricane Sentinel Works v1

This is the stored snapshot for the approved document version. The diff below shows what changed from the previous version.

Building
Source path
sentinel/HOWITWORKS.md
Source commit
No commit recorded
Created at
Jun 29, 2026, 6:22 AM UTC
Source digest
d17fb3685361bad038ddc485cf62cf605a0d2ea06bfd38b029c036653d362fb0

Document snapshot

How Hurricane Sentinel Works

Summary

Sentinel is a local agent harness for Linux. A privileged supervisor runs each agent as a Leash-leashed user with model calls pointed at a Muzzle listener, so enforcement is genuine: Leash gates the agent's file/network/command access per-UID and Muzzle inspects its model I/O. Runs are durable and checkpointed, and a flagged action pauses the run for a human approval before it proceeds.

The supervised run

  1. Supervisor (root). The sentinel service owns run lifecycle and the approval queue. For each run it drops privileges and spawns a worker process as the leashed user, with the worker's model endpoint set to a Muzzle listener.
  2. Run engine (durable + checkpointed). A run is one agent task. Its state — message history, step, status — is persisted to the leashed user's ~/.sentinel/runs/<id>/. Status moves running → waiting_approval → done | failed, and every step (and every pause) is checkpointed so a run resumes exactly where it stopped.
  3. Agent worker (as the leashed user). The loop: build messages → call the model through Muzzle → read the model's tool calls → for each, consult the action policy:
    • auto → run the tool (its file/network/command effects are enforced by Leash);
    • needs_approval → checkpoint, enqueue an approval, and pause the run;
    • deny → feed the denial back to the agent. No tool calls → the model's answer is final → the run is done.
  4. Async approvals. Pending approvals are persisted. An operator lists them and approves or denies via the CLI; on approval the supervisor resumes the run and the tool executes, then the loop continues.

Why files live in the leashed user's home

Per-agent runtime state (souls/, skills/, memory/, runs/) lives under the leashed user's ~/.sentinel, so the worker — which runs as that user under Leash's filesystem enforcement — can read and write its own state without anyone having to open up a privileged system directory.

Dependencies

Sentinel requires Muzzle and Leash. The installer verifies (and can install) both, and sentinel init --leash <user> [--muzzle] registers the agent's user with Leash, ensures Leash is running, and checks Muzzle.

Identity: one name for the agent and its user

First-run setup asks for one name (sentinel setup prompts "Name your agent (also its Linux user)"; headless installs default to naomi). That single name becomes both the head agent and the leashed Linux user: the display name is preserved for the agent (e.g. Naomi), and a normalized form (lowercase, [a-z0-9_]) is the OS account (e.g. naomi). There is no generic main agent anymore — init creates the named head agent, points default_agent at it, and writes its starter soul.md. --leash <name> doubles as the name on either command.

Migration: existing deployments keep their current leashed user and main agent until you re-run setup with a name (which creates the new named head agent alongside main — nothing is renamed or deleted).

Self-state: soul, operator model, and memory

The head agent maintains its own context across sessions through three self-state files under ~/.sentinel/, all loaded into the system prompt each session:

  • agents/<name>.soul.md — its persona (overrides the profile soul). Edited via the edit_soul tool (auto; each write is backed up to .soul.md.bak).
  • user.md — its model of the operator, shared across the head agent. Appended via the update_user tool (auto).
  • memory — durable notes via remember / recall (see below).

All four self-state/memory tools run auto (no per-edit approval) so the agent can keep itself current without interrupting the operator.

Agents author their own skills and tools

An agent can extend itself at runtime, not just remember things:

  • Skills are markdown. create_skill writes ~/.sentinel/skills/<name>/SKILL.md (frontmatter + body) and attaches it to the authoring agent; it loads into that agent's system prompt on the next turn. Skills carry no execution, so create_skill / list_skills run auto.
  • Tools are Python. create_tool writes ~/.sentinel/tools/<name>/{tool.yaml,tool.py} (the code must define run(args) returning a string). The tool then loads into the registry like any built-in, and a call runs the code out-of-process as the leashed user — so it inherits the same Leash egress allowlist and Landlock filesystem sandbox as the worker, plus a CPU-time limit and a wall-clock timeout (RSS/memory bounding via cgroup is a deploy TODO). Any failure (timeout, sandbox denial, exception) comes back as an error: … string fed to the model; it never crashes the run. create_tool / edit_tool / list_tools / delete_tool run auto — managing a tool is not the same as running it.

Approve-once-then-auto. Running an authored tool is gated by trust keyed on a SHA-256 of its code. The first call to a freshly authored (or freshly edited) tool pauses for approval, showing the operator the code they're about to trust; approving runs it and marks that exact code trusted, so later calls run automatically. Editing the tool clears trust (the code changed), so the next call re-prompts with the new code. Denial does not trust it. This keeps the "agent evolves itself" loop fast while keeping a human in the loop the first time any new code would run.

Persistent agents and orchestration

An agent is a named, on-disk profile under the leashed user's ~/.sentinel/agents/: a soul (persona / system prompt), a bound model chosen from your configured list (routed via Muzzle), an extensible skill set (SKILL.md files injected into its context), and the tools it may call. Run one with sentinel run --agent <name>.

One agent acts as the orchestrator. Given a task it can:

  • delegate the task to a persistent specialist it picks from the roster, or
  • spawn a temporary subagent — composing a persona, model, and skills on the fly — when no specialist fits.

A delegated or spawned subagent runs as its own durable child run under the same Muzzle

  • Leash enforcement (possibly a different model), and returns its result to the parent. Delegation is the approval gate (you approve each delegation, seeing the agent + task + grants); within it the child's granted tools run automatically. Tool grants flow parent → child (least privilege), depth is capped, and a temporary subagent is discarded, saved to the roster, or queued for your approval (ask) per config.

Reliable on small models

Because small local models are inconsistent at tool calling, the harness compensates: a tool-use prompt scaffold tells the model to emit real tool calls; the gateway recovers calls a model emits as plain text; tool arguments are normalized and validated against their schema, with a clear correction fed back so the model retries; and a subagent automatically receives the orchestrator's original request so a terse hand-off never loses detail.

Memory and context compaction

Each agent has its own memory bank under ~/.sentinel/memory/<scope>/ — a flat index.md plus an Obsidian-style vault/ of notes (YAML frontmatter + body + [[links]]). The scope comes from the agent's profile (memory: field): private to the agent by default, or shared when several agents name the same scope. The agent uses two tools: remember(title, content, tags?) writes a note, and recall(query) does full-text search and returns the most relevant notes — so knowledge persists across runs. (Semantic/embedding recall is a later enhancement; v1 is keyword-based and model-agnostic.)

Context compaction keeps long runs inside the model's window: when a run's history crosses a (character-estimated, tokenizer-agnostic) threshold, Sentinel summarizes the older middle turns into one running "summary so far" message via a model call, keeps the system prompt (soul + skills) and the most recent turns verbatim, and checkpoints the result.

Validated today

Built and validated on Linux end-to-end: supervised runs (as the leashed user, model via Muzzle) that pause on a needs_approval action and resume on approval — including with Leash filesystem enforcement live; a main agent delegating to a persistent specialist that runs as a parent-linked child and produces the correct result on a small local model; and an agent remembering a fact in one run and recalling it correctly in a later one. A local web UI is the remaining milestone.

The interactive shell

sentinel chat opens a colorized REPL for talking to an agent. Run it as the leashed user (sudo -u <user> sentinel chat) so the agent's tools execute under that account and write to its workspace. The shell:

  • streams live progress (each model call and tool call) and types out the final, Muzzle-cleared answer;
  • handles approvals inline — a flagged action prompts y/N right there (the async queue is still there for headless run);
  • offers completion menus as you type: / for commands (/help, /agents, /use, /model, /status, /memory, /skills, /new, /quit…), @name to send a task to a specific specialist, and $ to browse and attach skills;
  • keeps ↑/↓ history across sessions and colorizes commands as you type.

The same loop that powers headless runs drives the chat, so durable state, memory, delegation, and the small-model safeguards (tool-call dedup, the bundled file-discipline skill) all apply. Agents work from a per-user workspace, so a bare filename like notes.txt lands somewhere the leashed user owns.

Chat sessions are durable and resume by default. A plain sentinel chat auto-resumes the most recent session (with a one-line banner); sentinel chat --new (or /new in the shell) starts fresh. sentinel chats lists saved sessions by id and title, and sentinel restore <id> reopens a specific one. Each session is a durable run, so its history and self-state survive across invocations.

Saved workflows

A workflow is a saved, named pipeline at ~/.sentinel/workflows/<name>.yaml. Each step names an agent and a task; the runner executes the steps in order, and outputs thread into later steps through templating:

  • {{input}} — the value passed to workflow run --input,
  • {{<step>.output}} — a named earlier step's result,
  • {{previous}} — the immediately prior step's result.

Each step runs as its own supervised run with its agent (so Muzzle inspection and Leash enforcement apply, and the step's granted tools run within the workflow's authorization). Trigger a workflow with sentinel workflow run <name> [--input …], the chat's /workflow run <name>, or let an agent invoke the run_workflow(name, input) tool as part of its own work (gated by approval policy, like delegate). Workflows are linear in v1 — no branching or loops yet.

Operating it

The sentinel CLI: chat [--new], chats, restore <id>, setup, init, run "<task>" [--agent <name>], runs, show <id>, approvals, approve <id> / deny <id>, resume <id>, agent create/list/show/add-skill/set-model, workflow create/list/show/run, status (config + Muzzle/Leash health), and logs.

Diff from previous

This is the first approved version, so there is no previous diff.