← Back to document history
Document version

Hurricane Sentinel v4

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

Preview
Source path
sentinel/README.md
Source commit
No commit recorded
Created at
Jul 28, 2026, 11:17 PM UTC
Source digest
270c293535c0931ee402313bcb9573068da180bbf0d7449d4d5780932564fb32

Document snapshot

Hurricane Sentinel

Summary

Hurricane Sentinel is the secure agent harness for local AI. It runs AI agents on your own machine under real, enforced guardrails — routing every model call through Muzzle (the inspecting proxy) and running each agent as a Leash-leashed user (so the OS enforces what it can touch, reach, and run). On top of that it adds durable, resumable runs and a human approval queue: when an agent tries something flagged, the run pauses and waits for a person to approve before continuing.

Sentinel is the control center that ties the three Hurricane products together: Muzzle guards the model I/O, Leash guards the OS actions, and Sentinel orchestrates the agent and owns the human-in-the-loop.

Status

In the Lab — broad and deployed. The full harness is built and validated end-to-end on Linux and runs on real deployments: agents run as the leashed user with model calls through Muzzle, flagged actions pause for operator approval and resume on a yes (under live Leash + Landlock enforcement), a head agent delegates to a roster of persistent specialists and to whole teams that fan out to their members, and everything is driven from either the CLI or a web console. Added since the core engine: memory + context compaction, saved workflows, the web console, teams (leader-led rosters with a shared workspace), external model providers (OpenAI today, via Muzzle), uploaded Python tools (drop in a .py, pick its functions, served through a private MCP tool host), agent/team portability (export a team as a package, import it elsewhere), and legible guardrails (every Muzzle block and Leash/sandbox denial is explained to both the agent and the operator, so the agent adapts instead of failing). Linux only; no public download yet. See the Roadmap.

What works today

  • Supervised runs as the leashed user. Each run executes as a dedicated Leash user; the OS enforces its file/network/command access per-UID, with model traffic inspected by Muzzle.
  • Durable, resumable runs + async human approvals. Run state is checkpointed to disk; a needs_approval action pauses the run and enqueues an approval that an operator approves/denies via the CLI, after which the run resumes where it stopped.
  • Persistent agents. A roster of on-disk specialists (~/.sentinel/agents/), each with its own soul (persona), bound model (from your configured list), extensible skills, and tools. sentinel agent create/list/show/add-skill/set-model and sentinel run --agent <name>.
  • Orchestration. A head agent can delegate a task to a persistent specialist, or spawn_subagent to compose a temporary one when none fits — each as its own durable, enforced child run, with least-privilege grants and a configurable disposition (discard / save / ask).
  • Teams. A leader-led roster with a shared workspace, authored guidelines (prepended to every team run), and an activity log. The head delegates to a whole team; the team's leader fans the work out to its members — each member a scoped, sandboxed, approval-gated child run — then synthesizes their results. Fan-out is bounded (concurrency + a per-round cap); cross-team delegation only goes through the head, and each member's sandbox excludes other teams' and agents' private workspaces.
  • A web console. A self-contained, token-authed web UI (sentinel web): chat with live delegation activity, approve/deny inline, and manage agents, teams, tools, MCP servers, models, endpoints, memory, and the Muzzle/Leash guardrails — all from the browser. (The control plane runs as root to drop into the leashed user per run; front it with TLS and keep the host trusted.)
  • Legible guardrails. When Muzzle blocks a model call or Leash/the Landlock sandbox blocks an action, the agent gets a clear, categorized reason and adapts — it rewrites a reply that tripped an output filter (bounded retries), stops and explains an input it can't process, or moves to an allowed path/host — instead of hard-failing or looping on a raw errno. Each block also surfaces to the operator as a color-coded card in the run feed (Leash denials enriched with the exact reason from its decision log, correlated root-side), and a compact guardrails skill is auto-injected into the agent's context so it knows what each category means and how to respond. Block detection is read-only observability — it never opens a path around enforcement.
  • External model providers. Point an agent at OpenAI (Anthropic/Gemini scaffolded) in addition to local Ollama. Keys live only in Muzzle — Sentinel is the control plane and pushes a keyless per-provider listener into Muzzle; the model store never holds a secret, and the API never hands one back.
  • Uploaded Python tools. Upload a .py file; Sentinel AST-scans it (never importing it in the root process), you pick which functions become tools, and a private built-in MCP tool host serves the marked functions to agents — approval-gated like any tool, with their third-party requirements recorded and installed on one click (leashed pip).
  • Portability. Export an agent or a whole team as a secret-free .sentinelpkg — its skills, tools, MCP configs (minus keys), model selection, souls, guidelines, and uploaded tool files + requirements. Import it into another deployment (rename on collision, cross-refs rewritten); the receiver adds their own keys and is good to go.
  • Memory + context compaction. Each agent has its own memory bank (private, or shared by scope) — a flat index plus an Obsidian-style note vault; it can remember facts and recall them across runs using bounded, weighted lexical retrieval. Relevant memories are surfaced as explicitly untrusted, potentially stale context. Long runs auto-compact: older turns are summarized while the system prompt and recent turns are kept, so an agent never blows its context window.
  • Small-model friendly. A tool-use prompt scaffold, forgiving tool-call parsing, arg normalization + validation with self-correction, automatic delegation context, duplicate-call dedup, and a bundled file-discipline skill keep things reliable on small local models (e.g. llama3.2, gpt-oss:20b).
  • An interactive chat shell. sentinel chat opens a sleek REPL: a framed banner, colorized live progress, inline yes/no approvals, ↑/↓ history, and completion menus for / commands, @ targeted specialists, and $ skills. (Run it as the leashed user: sudo -u <user> sentinel chat.)
  • Saved workflows. Define a named, multi-step pipeline in YAML where each step runs an agent on a task and outputs thread into later steps via {{input}} / {{<step>.output}} templating. Run it with sentinel workflow run <name> or let an agent trigger one with the run_workflow tool; every step is its own supervised run.
  • A sentinel CLI + web console. CLI: init, chat, run, runs, show, approvals, approve/deny, resume, agent …, team …, workflow …, mcp …, model …, cron …, package export/import, tools install-reqs, web, api, status, logs. Web: sentinel web (control console) and sentinel api (programmable per-endpoint HTTP API).

Security posture

The one invariant everything else serves: no execution path bypasses the leashed worker + approval gate, and no Sentinel service runs as root except one tiny, schema-constrained Leash-policy broker. The control plane (supervisor, web, API, cron) runs as the leashed user; each run — head, team member, cron, endpoint — is a worker subprocess of it under Leash (egress + fs) and a Landlock sandbox scoped to that run. Sensitive tools pause for approval; autonomous (cron) runs skip anything effectful unless the operator explicitly opts in. Worker subprocesses get a scrubbed environment (the control plane's secrets — the web token, provider keys — never travel), provider/MCP secrets are masked in every API response and live only in Muzzle, uploaded code is AST-scanned, never imported in the control-plane process, and the key-injecting Muzzle listeners bind loopback. The one operation that still needs root — changing Leash egress policy — is brokered by the root sentinel-broker over a root:<leashed> socket (two schema-validated verbs, peer-cred-gated, no arbitrary exec, audited). Guardrail blocks are observable but never bypassable: a block is surfaced to the agent and the operator as a read-only event — the Leash-decision correlation is a supervisor concern, done once outside the run — so making a block legible never becomes a path around enforcement. Agent-influenced subprocesses run through a single bounded runner — wall-clock timeout, whole-process-group kill (a forking runaway can't leave orphans behind), a CPU ceiling, and an in-memory output cap — with the host cgroup owning the memory ceiling. Every confined run emits a security_attestation event recording the posture it actually ran under (profile, effective worker UID, live Landlock ABI, cgroup slice, Muzzle routing), so a run's event stream is a verifiable audit record. The full security and resource model is in SECURITY.md; see docs/plans/* for the design docs and the audit-driven hardening history.

How it fits together

operator ── sentinel CLI / web console (token auth) ──┐
                                                      ▼
        Supervisor (root) ── spawns each run as the leashed user (Leash + Landlock)
                           │
        Agent worker (as leashed user)  ── model calls ──▶  Muzzle listener ──▶ provider
          loop: model → tool calls → policy(auto / needs-approval / deny)
            auto → run the tool        (file/net/exec enforced by Leash + Landlock)
            needs-approval → pause + enqueue → (operator approves) → resume
            deny → tell the agent
            delegate → spawn a scoped, sandboxed child run (agent / team member)

Roadmap

M0 supervised runs + approvals ✅ → M1 persistent agents + orchestration ✅ → small-model robustness ✅ → memory + context compaction ✅ → interactive chat shell ✅ → saved workflows ✅ → web console ✅ → teams ✅ → external model providers ✅ → uploaded tools (MCP tool host) ✅ → agent/team portability ✅ → security audit + hardening ✅ → legible guardrails (block visibility + agent adaptation) ✅ → next: public download + install, deeper multi-provider support.

See docs/plans/ for the full design + implementation docs (v1 design, persistent agents, teams, external providers, vault memory, uploaded tools, portability).

Diff from previous

--- README@3
+++ README@4
@@ -79,9 +79,10 @@
   collision, cross-refs rewritten); the receiver adds their own keys and is good to go.
 - **Memory + context compaction.** Each agent has its own memory bank (private, or
   shared by scope) — a flat index plus an Obsidian-style note vault; it can `remember`
-  facts and `recall` them across runs. Long runs auto-compact: older turns are
-  summarized while the system prompt and recent turns are kept, so an agent never blows
-  its context window.
+  facts and `recall` them across runs using bounded, weighted lexical retrieval. Relevant
+  memories are surfaced as explicitly untrusted, potentially stale context. Long runs
+  auto-compact: older turns are summarized while the system prompt and recent turns are
+  kept, so an agent never blows its context window.
 - **Small-model friendly.** A tool-use prompt scaffold, forgiving tool-call parsing,
   arg normalization + validation with self-correction, automatic delegation context,
   duplicate-call dedup, and a bundled `file-discipline` skill keep things reliable on
@@ -103,18 +104,30 @@
 ## Security posture
 
 The one invariant everything else serves: **no execution path bypasses the leashed
-worker + approval gate, and the root process never runs agent/tool/model code.** Every
-run — head, team member, cron, endpoint — is a privilege-dropped worker subprocess under
-Leash (egress + fs) and a Landlock sandbox scoped to that run. Sensitive tools pause for
-approval; autonomous (cron) runs skip anything effectful unless the operator explicitly
-opts in. Subprocesses get a **scrubbed environment** (the root process's secrets never
-travel), provider/MCP secrets are **masked in every API response** and live only in
-Muzzle, uploaded code is **AST-scanned, never imported** in the root process, and the
-key-injecting Muzzle listeners bind **loopback**. Guardrail blocks are **observable but
-never bypassable**: a block is surfaced to the agent and the operator as a read-only
-event — the worker never even reads Leash's root-owned decision log (that correlation
-happens root-side) — so making a block legible never becomes a path around enforcement.
-See `docs/plans/*` for the design docs and the audit-driven hardening history.
+worker + approval gate, and no Sentinel service runs as root except one tiny,
+schema-constrained Leash-policy broker.** The control plane (supervisor, web, API, cron)
+runs as the **leashed user**; each run — head, team member, cron, endpoint — is a worker
+subprocess of it under Leash (egress + fs) and a Landlock sandbox scoped to that run.
+Sensitive tools pause for approval; autonomous (cron) runs skip anything effectful unless
+the operator explicitly opts in. Worker subprocesses get a **scrubbed environment** (the
+control plane's secrets — the web token, provider keys — never travel), provider/MCP
+secrets are **masked in every API response** and live only in Muzzle, uploaded code is
+**AST-scanned, never imported** in the control-plane process, and the key-injecting Muzzle
+listeners bind **loopback**. The one operation that still needs root — changing Leash
+egress policy — is brokered by the root `sentinel-broker` over a `root:<leashed>` socket
+(two schema-validated verbs, peer-cred-gated, no arbitrary exec, audited). Guardrail
+blocks are **observable but never bypassable**: a block is surfaced to the agent and the
+operator as a read-only event — the Leash-decision correlation is a supervisor concern,
+done once outside the run — so making a block legible never becomes a path around
+enforcement.
+Agent-influenced subprocesses run through a **single bounded runner** — wall-clock
+timeout, whole-process-group kill (a forking runaway can't leave orphans behind), a
+CPU ceiling, and an in-memory output cap — with the host cgroup owning the memory
+ceiling. Every confined run emits a `security_attestation` event recording the posture
+it *actually* ran under (profile, effective worker UID, live Landlock ABI, cgroup slice,
+Muzzle routing), so a run's event stream is a verifiable audit record. The full security
+**and resource** model is in [`SECURITY.md`](SECURITY.md);
+see `docs/plans/*` for the design docs and the audit-driven hardening history.
 
 ## How it fits together