A personal, always-on agentic system designed from the ground up for security and vendor neutrality.
A long-running personal AI agent that:
- talks to you over secure messaging (Telegram, Signal) and email (its own IMAP/SMTP account)
- remote-controls a web browser, performs web searches and page fetches
- executes Python in a strict sandbox
- maintains persistent memory in Postgres with hybrid retrieval (pgvector + BM25 + graph)
- reviews its own plans through CASSANDRA, a semantic oversight layer with hard-coded constitutional constraints, before any tool runs
- runs continuously, periodically resetting its context window from memories and a persistent task list
- Security boundary = the agent's own OS user account. Worst-case compromise (LLM, tool, dependency, or LLM-authored Python) does not escape that boundary.
- Vendor neutrality. Primary host is the NVIDIA DGX Spark, but no hard NVIDIA dependency. Linux and macOS are both first-class.
- License hygiene. Project is AGPL-3.0; every dependency is AGPL-compatible.
- Small core. The agent core is Rust (no eval, no metaprogramming, no dynamic import). Python lives only inside sandboxed workers.
The mechanical layers along the bottom of the diagram — bwrap, Landlock,
seccomp, the egress proxy, the dispatcher chokepoint — enforce boundaries:
"this process cannot open that socket." The CASSANDRA layer running
alongside the agent core enforces intent: "should the agent be doing this
at all, given what the user actually asked for?" CASSANDRA reviews each
plan (not each tool call) through a pipeline of deterministic and LLM
stages, with five hard-coded constitutional constraints that no user,
admin, or configuration change can override. See
docs/cassandra_design_plan.md.
The diagram below traces a single user instruction through every gate —
channel ingress, plan formulation, the CASSANDRA review pipeline
(Stages −1 through 4), the dispatcher chokepoint, sandboxed worker
execution, and the egress proxy — with the block / advisory / escalation
branches drawn explicitly. Source: docs/security-request-flow.svg.
Several Rust personal-agent projects exist in the OpenClaw-derived family — notably IronClaw and ZeroClaw. They share a lot with hhagent: Rust core, local-first, OS sandboxing, MCP-compatible IPC. The reason for another one is posture, not feature count: security is the foundational property here, not a layer added later. Each rule below is a load-bearing invariant, not a default we relax under deadline pressure.
-
One OS process + one kernel sandbox per tool invocation. IronClaw runs tools as WASM modules inside the runtime; ZeroClaw runs them as in-process Rust traits with the OS sandbox wrapping the whole runtime. Both are software-only or coarse-grained boundaries. hhagent's boundary is the OS process boundary —
bubblewrapon Linux,sandbox-execon macOS — so a compromised tool reaches at most the endpoints in that tool's allowlist, never the next tool's, and never the core. -
Double containment. The parent installs the OS sandbox at spawn; the worker then installs a second layer on itself (Landlock + seccomp-bpf on Linux) before serving any JSON-RPC request. A kernel bug in either layer alone does not breach the worker. See
workers/prelude/. -
seccomp is an allow-list, not a deny-list. Default action is
KillProcess; ~110 base syscalls plus per-profile additions (e.g. the BSD-socket family forWorkerNetClient) are explicitly permitted. The kill-list-of-obviously-bad-calls posture common elsewhere lets new attack syscalls walk in unchallenged every time the kernel grows. -
Dispatcher chokepoint. A single function authors every
WorkerCommand, consults policy, and writes the audit-log entry. New channels (Telegram, Signal, IMAP) and scheduled routines call into it — they never spawn workers themselves. Borrowed from IronClaw'sToolDispatcher::dispatch()and made non-negotiable. -
Semantic oversight on top of mechanical sandboxing (CASSANDRA). Kernel sandboxes catch "this process tried to open that socket"; they cannot catch "send this confidential pathology report to a recipient who happens to be permitted but contextually wrong." Every plan the agent formulates is reviewed by CASSANDRA before any tool runs — a chain of deterministic and LLM stages enforcing five constitutional constraints (no physical harm, no fraud / impersonation, no irreversible action without verified human-in-the-loop, no power concentration, no oversight suppression) that no user, admin, or configuration change can override. Runtime verification at the dispatcher then re-checks that what executes matches what was approved. The agent core never bypasses this gate. See
docs/cassandra_design_plan.md. -
AGPL with AGPL-compatible deps only. No CDDL, BUSL, SSPL, Elastic License, or "source-available" components. License hygiene is part of the security boundary: a permissive dep can re-enter the process under a corporate fork the user cannot audit.
-
Cross-platform parity by construction. The same
SandboxPolicystruct drives both backends. Linux's stronger stack (bwrap + Landlock + seccomp) and macOS's weaker stack (Seatbelt) are both first-class with negative tests asserting that denials actually deny. The asymmetry between them is documented openly indocs/threat-model.mdrather than papered over. -
No vendor lock-in. Primary host is the NVIDIA DGX Spark, but nothing in the core requires NVIDIA, CUDA, or a specific cloud. Local LLMs run via vLLM/SGLang on Linux or llama.cpp/Ollama on macOS behind an OpenAI-compatible HTTP API.
The full set of invariants — including secret handling, single-point
egress inspection, and the "no in-process untrusted code" rule — lives
in docs/architecture.md. Reviewers are
expected to refuse PRs that violate them.
Early scaffold. See docs/architecture.md and docs/threat-model.md. Phased build plan is tracked in the design plan file outside this repo.
core/ Rust agent core (scheduler, memory, policy, LLM router, audit, IPC)
sandbox/ Cross-platform sandbox crate (bwrap+Landlock on Linux, Seatbelt on macOS)
supervisor/ Service-supervisor abstraction (systemd --user / launchd LaunchAgents)
workers/ Tool workers, each its own sandboxed process
adapters/ Channel adapters (Telegram, Signal)
db/migrations/ Postgres schema (pgvector, pg_search, Apache AGE)
config/ Runtime policy and per-worker sandbox profiles
docs/ Architecture & threat-model docs
The kernel restricts unprivileged user namespaces by default
(kernel.apparmor_restrict_unprivileged_userns=1), so bwrap cannot create
its own jail without a per-binary AppArmor profile. Install one once:
sudo scripts/linux/install-bwrap-apparmor-profile.shThis is the same pattern Flatpak uses (/etc/apparmor.d/flatpak). After
installing, sandbox tests should pass:
cargo test -p hhagent-sandboxIf you skip this step, the agent will refuse to spawn workers and emit a clear error pointing back here. Other Linux distros without AppArmor user-ns restrictions don't need this script.
AGPL-3.0-only. See LICENSE.


