hyperspace
Documentation · v2.1.2 production

Hyperspace AVM

Agent Verification Machine. The execution engine for AI agents — the same idea V8 brought to browsers, applied to agent actions. One Rust daemon, three sandbox tiers, sixteen detection modules, neurosymbolic gating (rules first, ML advisory), hash-chained audit. Every agent on the box — Cursor, Claude Code, Aider, OpenClaw, your custom LangChain script — funnels through it.

Install Source on GitHub
# 60-second install — Linux + macOS, no root curl -fsSL https://releases.hyper.space/avmd/install.sh | bash
AVM is V8 for AI agents — same isolation idea applied to agent actions

AVM is V8 for AI agents. Browsers ran untrusted JS through V8 so the machine survived; agents run untrusted actions through AVM for the same reason.

How AVM gates an agent action — agent → preloader → avmd UDS → 16-module classify → sandbox

Every agent funnels through one Unix-domain socket. Three lines of integration per runtime; one policy file across them all.

Three sandbox tiers — Docker, Process (rlimits + seccomp), Direct fallback — automatic downgrade chain

Three tiers, automatic downgrade. Strongest isolation that's currently available wins.

22cloud vendors with regression-pinned destructive APIs
0false-allows on the catch set (PocketOS / litellm / 17 adversarials)
~3 µsadded per classify (p50, single-threaded)
~10 µsp99 latency (in BENCHMARKS.md)
554tests in the suite — your floor
BSL 1.1→ Apache 2.0 in 4 years
Why AVM? What is AVM? Quickstart / Install Integrate with your agent Vendor wiring (copy-paste) Architecture What it catches Vendor matrix PocketOS walkthrough litellm walkthrough Sandbox tiers Privacy posture ML detection layer Training avm-classifier-7b Audit chain Resource governor Outbound proxy RPC reference avmctl CLI SDKs Deploy / systemd Test suites Performance Threat model What AVM does not catch Roadmap FAQ Trust & transparency Telemetry contract Cloud daemon (design) MCP server Reference policies Signed defs Fuzz harness All docs Changelog
Why AVM?
April 2026 · the reference incident

An AI coding agent deleted PocketOS's production database in nine seconds — one Railway GraphQL volumeDelete mutation. The model later "confessed" in writing to violating every safety rule it had been told to follow. The root cause wasn't a bad model. It was that safety rules in the system prompt are not enforcement. Walkthrough →

Every agent runtime today builds its own ad-hoc safety. Each framework's tool ACL is different. Each one's audit log is different (or missing). Each one re-implements regex catches incompletely. You configure each separately, audit each separately, and hope none missed anything. Nothing they ship enforces against the destructive call once the model decides to make it.

AVM is the shared substrate that does enforce. One Rust daemon, one policy, one hash-chained audit. Every agent on the box — tool-using assistants, autonomous loops, MCP servers — funnels through the same gate. The gate runs before the syscall fires, the curl wires up, or setup.py executes.

design principle

Trust no model output. Trust no framework's internal sandbox. Trust the OS-level boundary, signed configs, hash-chained audit, and local-only ML inference. Everything else is policy.

Try it without installing anything

Type a command. The live widget runs the exact regex bank from src/patterns.rs in your browser — no network, no install. Real avmd adds behavioural, consequence, and ML layers on top; the regex tier alone catches the canonical kill-chain.

No command yet — click a preset or type one.
What each runtime ships natively vs what AVM enforces
  tool ACL destructive regex behavioural anomaly privacy ML sandbox tiers tamper-evident audit
tool-using assistants (MCP / function-call style) per-tool prompts none none none none none
autonomous-loop agents (long-running goal-pursuers) partial none none none none partial (in-process logs)
cloud agent runtimes (cloud-internal planning + tool legs) vendor-side none none none vendor-side only vendor-side only
+ AVM enforced, deny-by-default 73 destructive · 91 injection · 24 PII · 32 supply-chain per-agent baseline, persisted regex + 1.5B model + 7B coming Docker → process → direct downgrade hash-linked, offline-verifiable

The column "enforced" is doing real work in that bottom row: the rules are in code, with property tests pinning the invariant that ML can never relax a rule's block. See ML detection layer.

Local-first

Runs on the user's box. The audit chain is owned by the user, not the vendor. Never phones home. Telemetry — if any — is per-user opt-in.

Vendor-neutral

Any agent runtime — tool-using, autonomous, MCP server, or the local-touchdown leg of a cloud agent — integrates via three lines. SDKs in TypeScript and Python.

Defence in depth

Gating happens at every layer the agent can reach: classify pipeline, ACL primitives, egress proxy, sandbox tiers, seccomp, cgroup, gas budget. No single bypass collapses the whole.

Real incidents AVM closes against: PocketOS Railway-volumeDelete prod-deletion (April 2026); the litellm supply-chain attack (poisoned PyPI exfiltrating SSH/AWS/GCP creds, March 2026); generic "ignore previous instructions" injection; staged write→chmod→exec credential exfil; runaway-cost loops in long-running agents. Walkthroughs below.

What is AVM?

A single Rust daemon (avmd, ~2 MB stripped) and an operator CLI (avmctl). Sixteen detection modules + supporting infrastructure, ~16 KLOC, 532 tests, BSL 1.1.

AVM classify pipeline — 16 modules from destructive_detector through privacy_ml feeding allow/confirm/block, ~3 microseconds p50

Classify pipeline. Every command, network call, install, and outbound payload runs the relevant subset of 16 modules in ~3 µs.

agent Cursor / Claude Code / Aider / your custom script child_process / fetch / exec preloader Node --require avmd-preload.cjs / Python sitecustomize line-delimited JSON / 0o600 UDS avmd — 16-module classify pipeline + ACLs + hash-chained audit destructive_detector · injection · consequence · behavioural · privacy supply_chain · permissions · oob_confirmation · output_review · sandbox resources/{cgroup, governor, process, budget} · seccomp · network_proxy allow syscall fires block exception confirm (OOB) cannot be auto-completed

Local-first

Lives on the user's box. The audit chain is owned by the user, not the vendor. Never phones home.

Vendor-neutral

Cursor, Claude Code, Aider, OpenClaw, custom LangChain — framework-agnostic.

Production Rust

~2 MB binary. Hand-rolled BPF for seccomp. No unsafe outside pre_exec + seccomp install.

Hash-chained audit

Every classify, register, gas debit, install, autoupdate, and tool_gate decision writes a JSONL row whose hash includes the previous row's serialisation.

16-module pipeline

Pattern, behaviour, consequence reasoning, blast radius, exfiltration, injection (5 layers), credential provenance, network scope, rate limiter, supply chain, output review, OOB confirmation.

Two-tier PII

Regex on structured (cards, keys, JWTs) + OpenAI Privacy Filter ML on unstructured (names, addresses). Local sidecar — never sends data to the cloud.

The 16-module classify pipeline at a glance

Every command, network call, install, file write, and outbound payload runs the relevant subset of these modules in ~3 µs per classify (p50). Decisions are allow / confirm / block; confirms are out-of-band and the agent cannot auto-complete them.

destructive_detectormulti-kind ensemble
injection_scanner5 layers, 91 regex
consequence_engineUNKNOWN ops catch
blast_radiusscope inference
exfiltration_scanneroutbound creds
behavioral_detectorper-agent anomaly
network_scopeper-task ACL
credential_provenancewhich agent, when
rate_limitertoken bucket
supply_chainnpm / pip sandbox
output_reviewtool-result inj scan
oob_confirmationlevel-4 always asks
agent_registrylifecycle + gas
sandbox3-tier exec
privacyregex (24 patterns)
privacy_mlopenai/privacy-filter
Quickstart
1. Install

Standalone (vendor-neutral). The installer detects your platform, fetches the matching tarball, verifies sha256 against the signed release manifest, places /usr/local/bin/avmd and avmctl, writes a default fail-closed policy at ~/.hyperspace/avm-policy.json, and registers a hardened systemd user unit (Linux) or LaunchAgent plist (macOS).

curl -fsSL https://releases.hyper.space/avmd/install.sh | bash

Bundled with Hyperspace CLI (recommended for hyperspace nodes). Since CLI v5.40.0, the avmd + avmctl binaries are bundled in the CLI release tarball; hyperspace start spawns avmd as a child process automatically and surfaces it via hyperspace avm status. Existing CLI installs auto-update to v5.40.0 and pick up avmd on next start.

curl -fsSL https://agents.hyper.space/cli | bash # Hyperspace CLI installs to ~/.local/bin/hyperspace (and /usr/local/bin) # avmd + avmctl land at ~/.hyperspace/bin/ alongside the other sidecars
2. Verify it's running — real terminal output

Captured from a fresh install of the released v2.1.2 binary on Ubuntu 22.04. Not a mockup; not what we wish it'd say. Run it yourself and you should see the same shape.

avmctl status
$ avmctl status { "sandbox": { "docker": true, "process": true, "direct": true }, "process_tier_hardening": { "no_new_privs": true, "dumpable_off": true, "rlimit_as": true, "rlimit_cpu": true, "rlimit_nproc": true, "seccomp_bpf": true, "seccomp_default_filter_instructions": 24 }, "languages": { "node": true, "python3": true }, "resources": { "cgroup_v2_available": false, "num_cpus": 26 }, "version": "2.1.2" }
3. Test the gate without an agent
avmctl gate "rm -rf /" --tool bash
$ avmctl gate "rm -rf /" --tool bash --agent demo-1 { "action": "deny", "stage": "tool_acl", "reason": "tool 'bash' denied by policy", "tool": "bash" } ─── (with permissive.json the same command would reach 'classify' and block at the destructive_detector instead — try it locally) ──
4. Read the audit log — tamper-evident, hash-chained
avmctl audit verify · ~/.hyperspace/audit.jsonl
$ avmctl audit verify { "verified": true, "events": 4, "path": "~/.hyperspace/audit.jsonl" } $ head -3 ~/.hyperspace/audit.jsonl | jq -c '{seq, event_type, hash: .hash[:12]}' {"seq":1, "event_type":"config_reloaded", "hash":"4accb4ea6e22…"} {"seq":2, "event_type":"tool_called", "hash":"9a5be0010b63…"} {"seq":3, "event_type":"agent_registered", "hash":"edfe15ed0b50…"} ─── each row's hash includes the previous row's serialisation; one retroactive edit invalidates the chain from that row onward ──
# Other useful audit operations avmctl logs --tail 20 avmctl logs --filter "volumeDelete" avmctl logs --since 1h --filter "action=block" avmctl call audit_archives '{}' # list rotated archives
Integrate with your agent
Three integration paths — Preloader (NODE_OPTIONS / PYTHONPATH), SDK (@hyperspace/avm-client / hyperspace-avm-client), MCP (avmd-mcp server / settings.json one line) — all converge into avmd UDS 0o600

Three integration paths. All three converge at the same Unix socket; pick whichever fits the agent runtime cleanest.

Three lines per runtime. After that, every child_process.spawn, subprocess.run, urllib.urlopen, requests.delete, httpx.post, os.system, fetch, and direct exec/spawn syscall passes through avmd's classify pipeline before it runs.

Node.js (Cursor, Claude Code CLI, Aider, custom)
NODE_OPTIONS="--require /usr/local/lib/avmd/preload/avmd-preload.cjs" my-agent
Python (Aider, anthropic-claude-code-py, LangChain, AutoGen)
PYTHONPATH="/usr/local/lib/avmd/python:$PYTHONPATH" python my_agent.py
Anything else

Speak JSON-RPC to ~/.hyperspace/avmd.sock directly. Use the typed SDKs:

# Node npm install @hyperspace/avm-client # Python pip install hyperspace-avm-client
MCP integration (Cursor / Claude Code)

One JSON entry in the agent's settings; no shell rewriting needed.

# ~/.cursor/mcp.json or ~/.claude/mcp.json { "mcpServers": { "avmd": { "command": "/usr/local/bin/avmd-mcp" } } }

The MCP server exposes six tools: avm.gate, avm.classify, avm.privacy_egress, avm.review_output, avm.audit_verify, avm.status. Each maps 1:1 to an existing avmd RPC method — no daemon-side changes needed.

Architecture
One avmd, every agent on the box — agents above, avmd internals (8 boxes), kernel/files/network/packages below, ML privacy-filter sidecar on the side

One daemon, eight inner subsystems, every agent funnels through it. Sidecar handles ML inference out-of-process.

Sixteen modules + supporting infrastructure (~16 KLOC Rust) src/ agent_registry.rs per-agent lifecycle + gas budget audit_logger.rs hash-chained tamper-evident JSONL autoupdate.rs signed manifest + atomic binary swap behavioral_detector.rs per-agent anomaly profiles, persisted blast_radius.rs scope inference (file / dir / svc / vol) consequence_engine.rs catches UNKNOWN destructive ops credential_provenance.rs which agent accessed which credential when defs_loader.rs signed security definitions hot-reload destructive_detector.rs multi-kind classifier (bash + http + graphql) execution_tracer.rs per-task execution traces, replayable exfiltration_scanner.rs outbound credential / secret scanner injection_scanner.rs 5-layer prompt-injection detector network_scope.rs per-agent network policy oob_confirmation.rs out-of-band approval (cannot auto-complete) output_review.rs symmetric injection scan on tool results patterns.rs every compiled regex permissions.rs tool / file / network ACL primitives privacy.rs regex PII / credentials / financial classifier privacy_ml.rs OpenAI Privacy Filter sidecar client protocol.rs JSON-RPC dispatcher (35+ methods) rate_limiter.rs per-agent token bucket resources/ budget.rs gas accounting + GasExhausted cgroup.rs Linux cgroup v2 manager governor.rs per-agent CPU/RSS sampler at 1 Hz process.rs /proc/{pid}/{stat,status,io} reader sandbox.rs Docker / process / direct tiers seccomp.rs BPF deny-list (x86_64 + aarch64) supply_chain.rs npm/pip behavioural scanner + Docker install tool_approvals.rs approval queue + persistent grants network_proxy.rs CONNECT-method outbound gate policy_lint.rs 11-rule schema validator state_store.rs atomic JSON snapshot persistence sidecar_supervisor.rs privacy-filter lifecycle metrics.rs Prometheus-format counters avmctl.rs operator CLI binary sidecars/ privacy-filter/ Python reference (transformers + ONNX) privacy-filter-rs/ Rust ONNX (production, --self-test) avmd-mcp/ Model Context Protocol server
What AVM catches

Catastrophic vendor mutations

22 vendors with regression tests. AWS, GCP, Azure, Railway, Fly, DigitalOcean, Vercel, Supabase, Cloudflare, GitHub, Stripe, Heroku, Linear, Notion, Datadog, Sentry, PagerDuty, MongoDB Atlas, Render, OpenAI, Anthropic, Auth0, Clerk, Pinecone, Neon, PlanetScale, CockroachDB.

Local destruction

rm -rf /, mkfs, wipefs, fdisk, dd if=/dev/zero of=/dev/sd*, sudo rm, shred, recursive chmod / chown over /, MongoDB db.dropDatabase(), Redis FLUSHALL.

Database destruction

DROP DATABASE, DROP SCHEMA, DROP TABLE, DELETE FROM ... ; (no WHERE), TRUNCATE, dropdb, pg_drop.

Container destruction

terraform destroy, docker system prune -af, docker volume rm, kubectl delete --all / namespace / pvc, helm uninstall.

Source destruction

git push --force, git reset --hard, git branch -D.

Obfuscation wrappers

eval $(...), base64 -d | bash, curl ... | bash, printf hex | bash, python -c, node -e, bash -c, IFS-trick, X=rm assignment, PowerShell -enc.

Prompt injection

5-layer detector with 91+ compiled regexes. "Ignore previous instructions" variants, role-override attacks, system-prompt extraction, encoded payloads. Severity-graded (Critical / High / Medium / Low).

Credential theft

~/.ssh, ~/.aws, ~/.kube, ~/.config/gcloud, browser cookies / keychains. Outbound payloads with credential patterns block at privacy_egress_ml.

PII leakage

Regex tier (24 patterns) + OpenAI Privacy Filter ML (8 categories). Per-category policy: allow / warn / ask / block. Local-only classification — user data never leaves the box.

Resource exhaustion

cgroup v2 caps + per-agent gas budget. Runaway agent halts cleanly instead of taking down the host.

Tool-result injection

output_review symmetric scan catches a compromised tool smuggling injection payloads back through the result channel.

Supply chain

npm / pip install runs inside Docker with --cap-drop ALL --read-only. Lockfile sha256 verified. 32-pattern post-install behavioural scanner.

Honest misses (documented, audit-pinned)

The regex tier alone cannot catch:

See FEATURES.md and tests/adversarial.rs for the explicit catches/misses contract.

Vendor matrix — destructive APIs gated
VendorDestructive APIs caught (regression-tested)
AWSs3 rb, s3api delete-bucket, ec2 terminate-instances, rds delete-db-*, cloudformation delete-stack, dynamodb delete-table, eks delete-cluster + GraphQL TerminateInstances, DeleteBucket, DeleteStack, DeleteDBInstance, DeleteTable, DeleteCluster, DeleteFunction
GCPgcloud projects delete, compute instances delete, sql instances delete, container clusters delete + projects.delete, instances.delete
Azureaz group delete + ResourceGroups_Delete
RailwayvolumeDelete · volumeRemove · volumeDestroy · volumeWipe · projectDelete/Remove/Destroy/Wipe · serviceDelete · environmentDelete · deploymentDelete + REST DELETE /v2/volumes/
Fly.iodestroyApp, deleteVolume, deleteMachine + fly destroy
DigitalOceandestroyDroplet, destroyVolume, destroyDatabase + doctl compute droplet delete + REST DELETE /v2/droplets/
VerceldeleteProject, deleteDeployment + vercel remove
SupabasedeleteProject, deleteOrganization
CloudflaredeleteZone, deleteWorker, purgeCache (warn)
GitHubdeleteRepository, deleteBranch + REST DELETE /repos/ + DELETE /orgs/
Stripecustomer.delete, subscription.delete, invoice.delete, product.delete + REST DELETE /v1/customers etc.
Herokuapp-delete, heroku apps:destroy, heroku pg:reset
LinearissueDelete, teamDelete, organizationDelete, projectDelete, cycleDelete
NotiondeletePage, deleteBlock, deleteDatabase + REST DELETE /v1/databases
DatadogmonitorDelete, dashboardDelete, syntheticDelete, siloDelete
SentryprojectDestroy, teamRemove, organizationRemove
PagerDutydeleteService, deleteEscalationPolicy, deleteSchedule
MongoDB AtlasdeleteAtlasCluster, deleteAtlasProject, deleteAtlasOrganization
RenderdeleteRenderService, deleteRenderPostgres
OpenAIdeleteAssistant, deleteFile, deleteThread, deleteVectorStore + REST DELETE /v1/assistants etc.
AnthropicdeleteWorkspace, archiveWorkspace, deleteApiKey + REST DELETE /v1/messages etc.
Auth0 / ClerkdeleteTenant, deleteApplication, deleteOrganization, deleteUser
PineconedeleteIndex, deleteCollection
Neon / PlanetScale / CockroachDBdeletePsBranch, deletePsDatabase, deleteCluster (CockroachDB), deleteBranch (Neon)
Walkthrough: PocketOS
Seven gates fire before any kernel call — destructive_detector, consequence_engine, network_scope, permission_check, behavioral_detector, injection_scanner, approval_gate — final result BLOCKED level 4 DESTROY

The PocketOS curl meets seven independent gates before the kernel ever sees the syscall. Pinned in two regression tests.

The exact wire-format Jer Crane reported — one curl, 9 seconds, prod gone:

curl -X POST https://backboard.railway.app/graphql/v2 \ -H "Authorization: Bearer [token]" \ -d '{"query":"mutation { volumeDelete(volumeId: \"3d2c42fb-...\") }"}'

With AVM in the loop, that exact string hits seven independent gates before the kernel sees the syscall:

GateWhat fires
destructive_detectorvolumeDelete is Level 4 DESTROY in DANGEROUS_MUTATIONS; multi-kind classifier ensembles bash + graphql signals
consequence_engine"fix credential mismatch" task → "delete production volume" action is a cross-purpose escalation
network_scopebackboard.railway.app not on the agent's outbound allowlist for this task
permission_check_networksame answer at the ACL layer
behavioral_detectorthis agent doesn't normally call Railway's GraphQL API; anomaly score spikes
injection_scannercross-purpose: task said "fix credential", action is volume deletion
oob_confirmationlevel-4 ops require out-of-band approval the agent literally cannot autocomplete

Pinned in two regression tests:

# tests/destructive_detector.rs test test_pocketos_railway_volume_delete_caught_with_auto_kind ... ok # tests/protocol.rs test test_tool_gate_pocketos_volume_delete_blocks ... ok

Net result: classify returns {"action":"block"}, the preloader raises in the agent, the curl never runs.

Walkthrough: litellm supply-chain attack
Supply-chain protection — pip install runs inside AVM sandbox with cap-drop ALL, network restricted to pypi.org/npmjs.org only, lockfile sha256 verified, post-install behavioural scan; clean packages install, suspicious go to quarantine

The litellm-style attack runs inside supply_chain::sandboxed_install. Even if the payload is in the build, it sees no host secrets and no exfil network.

March 2026: a poisoned litellm build (97M monthly downloads) landed on PyPI for under an hour. A single pip install litellm exfiltrated SSH keys, AWS / GCP / Azure creds, Kubernetes configs, env vars, crypto wallets, and CI/CD secrets — spreading transitively to every downstream package. Caught only because a bug in the payload crashed the machine.

With AVM, the install runs inside supply_chain::sandboxed_install:

avmctl call install '{"manager":"pip","packages":["litellm"],"agent_id":"pip"}' # {"success":false,"quarantined":["litellm-X.Y.Z"],"suspicious":[...], # "audit":["supply_chain: blocked by behavioural scan"]}
Sandbox tiers

Three tiers, automatic downgrade chain, every step logged:

1
Docker
Full container. --cap-drop ALL --network none --read-only, memory / CPU / PID limits, no new privileges. Strongest isolation; requires Docker daemon.
2
Process (rlimits + seccomp)
RLIMIT_{AS,FSIZE,NPROC,CORE,CPU,NOFILE} + PR_SET_NO_NEW_PRIVS + PR_SET_DUMPABLE=0 + hand-rolled seccomp-bpf deny list (18 dangerous syscalls; x86_64 + aarch64).
3
Direct fallback
Bare exec with output truncation + timeout SIGKILL. Used only when both upstream tiers fail to initialise.
Hand-rolled seccomp-bpf

No libseccomp dep. 18 dangerous syscalls return EPERM. Architecture-specific syscall numbers (x86_64 + aarch64). Architecture mismatch triggers SECCOMP_RET_KILL_PROCESS.

CategorySyscalls denied
kernel-statemount, umount2, pivot_root, chroot, swapon, swapoff, reboot, init_module, finit_module, delete_module, kexec_load, kexec_file_load
tracingptrace, perf_event_open
sandboxsetns, unshare
BPF loadingbpf (defence in depth — no nested filters)
page-fault escapesuserfaultfd (used in CVE-2016-2384 style escapes)
cgroup v2 manager

Per-process CPU / memory / PIDs caps under /sys/fs/cgroup/avm/<name>/. Graceful no-op when cgroup v2 isn't mounted (macOS, non-root, unprivileged containers).

Privacy posture
Privacy ensemble: regex tier (cards, keys, JWTs, DSNs, phones) and ML tier (names, addresses, dates) overlap on dedupe, then merge into REDACTED output. All local; never sent to cloud.

Two complementary tiers. Regex is fast and precise on structured PII; the ML model handles names, addresses, and free-form fields. Both run locally.

Local-only. AVM never sends user data to a remote service for classification. The privacy-filter sidecar runs as a separate process on the user's host, talking only to avmd over a 0o600 socket.

Regex tier

24 high-precision patterns: AWS keys, GitHub tokens, OpenAI / Anthropic keys, Stripe keys, Supabase keys, JWTs, private keys, Postgres URLs, Slack tokens, Twilio SIDs, SendGrid keys, generic SECRET= / PASSWORD= / TOKEN= / API_KEY= / CLIENT_SECRET=, credit cards (with Luhn), phone numbers, bearer tokens.

ML tier

openai/privacy-filter 1.5B-param model (Apache 2.0, released 2026-04-22). Eight categories: private_person, private_address, private_email, private_phone, private_url, private_date, account_number, secret. Runs in a Unix-socket sidecar; falls back to regex if absent.

Ensemble merge

Regex wins on structured PII; ML wins on unstructured (names, addresses, free-form dates). Same-category overlap deduped. privacy_egress_ml RPC returns redacted text + per-category action in one call.

Per-category policy

pii / credentials / financial / location / code / public × allow / warn / ask / block. Default fail-closed for credentials and PII.

Two sidecars

Pick by tradeoff:

SidecarWhen to use
privacy-filter (Python)Reference; transformers + PyTorch. ~3 GB resident with model. Easiest to ship and update.
privacy-filter-rs (Rust ONNX)Production. Single Rust binary, ONNX inference via ort. Constrained Viterbi BIOES decoder. Gates every release on --self-test (Viterbi correctness, span extraction, UDS round-trip, envelope shape).
ML detection layer · neurosymbolic

AVM is not "just regex" — and the ML in AVM is not a replacement for rules. The classify pipeline is a neurosymbolic gate: deterministic rules decide, ML annotates. Same shape Thor's ThorAdvancedClassifier already ships in /thor-refactored/agents-complete/, with the security-critical inversion: in Thor the LLM wins on low rule confidence; in AVM the rule always wins.

Neurosymbolic gate — rules first, ML advisory. Top: rules (regex, ACL, behavioural, consequence) are authoritative and produce decision allow/confirm/block. Bottom: ML (avm-classifier-7b, openai/privacy-filter) emits hint with severity + rationale, may downgrade allow to confirm only. Block always wins.

ML can restrict (allow → confirm). ML cannot relax (block stays block). Same input always produces the same gate decision regardless of ML availability.

The invariant, in one paragraph

The rule engine computes the gate decision. Always. The ML sidecars run in parallel with a hard timeout and write a hint to the audit row — severity score, rationale string, model version. The hint can downgrade an allow to confirm; it cannot upgrade a block to allow. If the ML is absent, slow, drifted, poisoned, or just wrong, the gate behaves identically to a build with no ML at all. Every block cites a rule. Every test is reproducible.

Thor RLM ↔ AVM — same idea, opposite cost asymmetry
 Thor advanced classifierAVM classifier
Rule confidence highuse ruleuse rule
Rule confidence lowfall back to LLMstill use rule; LLM annotates audit
LLM disagrees with ruleLLM wins (it's smarter)rule wins (it's auditable)
LLM unavailablefewer good answersidentical gate behaviour + audit row
Operating cost of bad callworse classification qualityprod data deletion / credential leak

The cost asymmetry is what flips the rule. A wrong "creative" classification in Thor costs a marginal user-experience point. A wrong "allow" in AVM is a PocketOS incident. ML stays advisory.

Detection layers, in order

Tier 1 · Regex (rules)

91+ injection patterns, 24 PII patterns, 73 destructive vendor mutations, 32 supply-chain patterns. Deterministic, fast, audit-pinned. Catches what we knew was bad on the build date.

Tier 2 · ACLs (rules)

Per-agent tool / file / network allowlists. Deterministic. Authoritative.

Tier 3 · Behavioural (rules)

Per-agent statistical anomaly profile, persisted across restarts. Spikes trigger confirm. Frequentist — no neural net.

Tier 4 · Consequence (rules)

Pattern-based check that the action's intent matches the agent's stated task. Will be ML-augmented in Tier 5.

Tier 5a · openai/privacy-filter

Production. 1.5B-param Apache 2.0 model from OpenAI (released 2026-04-22). Advisory: spans + categories returned to the rule engine, which decides. Eight categories of unstructured PII regex can't reach.

Tier 5b · avm-classifier-7b

Composer shipped (v2.1.1); model in training. The neurosymbolic compose(rule, ml_hint) function lives in src/classifier_advisory.rs with 10 property tests pinning the invariant that ML can never relax a rule block. Qwen 2.5 7B + LoRA model trains on the corpus the avmctl defs generate-adversarial subcommand produces.

Why a custom action classifier on top of regex

Three attack classes the rule tiers cannot reliably catch:

Failure modes · MlAdvisoryDisagreed as a drift signal

The most useful telemetry from this architecture is disagreement. When the rule says allow and the model would have said block, the daemon writes an MlAdvisoryDisagreed row to the audit chain (live in v2.1.1, see audit_logger::EventType). Aggregated over a week, the disagreement set is the queue of attacks the rules are missing. Operators triage, write new regex, ship.

If the sidecar is absent, slow, OOM, or returns malformed output, AVM fails open with audit. An MlAdvisoryUnavailable row goes to the audit log so silent loss of detection capability surfaces in dashboards. The classifier_advisory::compose function is pure and synchronous: any failure path on the ML side converges on MlHint::Unavailable, which the composer treats as identical to "no ML at all" — same gate decision either way.

Training avm-classifier-7b
avm-classifier-7b training pipeline — five corpus sources (regex mutation 30K, frontier adversarial 50K, compositional 10K, real opt-in traces 10K, boring negatives 100K) feed Qwen 2.5 7B + LoRA, then eval harness covering PocketOS / litellm / adversarial.rs, then ONNX sidecar with Q4 quantisation. Held-out test never seen during training.

Five corpus sources, Qwen 2.5 7B + LoRA on a single H100 (Vega pod), eval harness gating release. Scaffold + corpus generator + 28-example held-out test set shipped in v2.1.1; first checkpoint pending.

Hardware path — Vega pod first, P2P for iterations
 Local H100 (Vega pod)Distributed P2P (RLM-1 path)
Time to first checkpoint~24 h, 1×H100, LoRA r=16~1 week, ~30 nodes
Data sovereigntyfull (pod is yours)partial (workers see redacted shards)
Reproducibilityhighmedium (worker variance)
Best forfirst checkpoint, sensitive corpusscaling iterations

Recommendation: ship checkpoint 1 on the Vega H100, iterate on P2P. The corpus contains real attack patterns — you don't want them shard-distributed across the network on the first pass.

Synthetic corpus — five sources
SourceTargetHow
Regex corpus mutation30KAll 73 destructive patterns + 91 injection patterns × mutations: paraphrase, base64-wrap, eval-wrap, language-translate, add-benign-context. Deterministic from seed; reproducible.
Frontier adversarial50KClaude Opus + Grok-4 prompted: "rephrase this destructive command N ways", "give 10 novel ways to delete prod data on Vendor X via REST/GraphQL/CLI". Validated: regex must miss it, frontier model says "yes destructive".
Compositional multi-stage10KSequences (write→chmod→exec, fetch→decode→pipe). Each command alone passes regex; the plan gets labelled consequence_mismatch.
Real opt-in agent traces10KCursor / Claude Code logs, redacted via privacy-filter sidecar before training, labelled by current gate decision. Hardest to source; highest signal.
Boring negatives100KRandom sample of tests/integration_e2e.rs allow-path commands plus shell history corpora. Without this the model overfits to "everything looks dangerous."

Held-out test set — never seen during training: PocketOS exact wire format, all litellm post-install scan inputs, every catch in tests/adversarial.rs. Floor metrics: 0.99 recall on the catch set, 0.999 precision on boring-allow, no regression on the 4 documented honest-misses.

Training recipe
# LoRA fine-tune on 1xH100 (Vega pod) base_model: Qwen/Qwen2.5-7B-Instruct method: LoRA (r=16, alpha=32, target=q_proj,k_proj,v_proj,o_proj) objective: classification head + auxiliary rationale generation classes: safe / destructive / exfiltration / injection / consequence_mismatch / supply_chain / privacy optimizer: AdamW, lr=2e-4, cosine schedule, warmup 200 batch: per-device 8, accumulation 8, effective 64 epochs: 3 with early stop on eval recall plateau eval_every: 1000 steps; abort training on regression vs held-out floor checkpoint: LoRA weights only (~450 MB, matches RLM-1 size) export: ONNX, Q4 quantisation, ~4.5 GB resident at inference
Drift detection · production telemetry

Once a checkpoint ships behind ml.classifier.enabled = true, the daemon logs the per-day disagreement-with-rules count and confidence distribution. Two-sigma shifts week-over-week trigger an operator alert; quarterly retraining cycle uses the disagreement set as new training data.

Full plan in AVM_CLASSIFIER.md — eval harness, drift detection, rollout gates.

Audit chain
Hash-chained tamper-evident audit — five connected blocks, each with seq number, event type, and hash; prev_hash arrows linking them; avmctl audit verify confirms verified

Each row's hash includes the previous row's full serialisation. One backdated edit invalidates the chain from that row onward.

Every classify, register / deregister, gas debit, install, autoupdate, config reload, and tool_gate decision writes a JSONL row whose prev_hash includes the previous row's serialisation. Tamper-evident: any retroactive edit invalidates the chain from that row onward.

# An audit row { "seq": 1542, "timestamp": "1714430400.123", "event_type": "ClassifyRequest", "agent_id": "cursor-1", "details": { "command": "rm -rf /tmp/foo", "action": "confirm" }, "hash": "a3f1c8e2...", "prev_hash": "e7b4f0a1..." } # Verify the chain offline avmctl audit verify # {"verified":true,"events":1542,"path":"~/.hyperspace/audit.jsonl"}
Rotation + retention

Daily rotation triggered by audit.max_bytes. Rotated archives gzipped via shell-out (no flate2 dep). Retention sweep deletes archives older than audit.retention_days. avmctl audit_archives lists rotated files.

avmctl audit verify # walk the chain avmctl call audit_rotate '{}' # force rotate now avmctl call audit_archives '{}' # list rotated files avmctl logs --tail 50 # read recent rows avmctl logs --filter "volumeDelete" # grep across rows
Resource governor

cgroup v2

Per-process CPU / memory / PIDs caps under /sys/fs/cgroup/avm/<name>/. Graceful no-op when not available.

1-Hz sampler

Per-agent /proc/{pid}/{stat,status,io} reader. Computes USER_HZ-aware CPU%, RSS, vsize, threads, I/O bytes. Caps at 100 * num_cpus.

Gas budget

Per-agent metering. Every CPU ms / KB of memory or network / tool call / file op debits a budget. Saturating math; GasExhausted reports the shortfall on overflow.

Persistent state

agent_registry + gas budgets + behavioural profiles snapshotted to ~/.hyperspace/state/. Atomic JSON write (tempfile + rename, mode 0o600). Closes the restart-resets-baseline bypass.

Outbound network proxy
Outbound network proxy — agent points HTTPS_PROXY at avmd, CONNECT-only host ACL check writes audit row, denied hosts hit 403; bodies stay encrypted (no MITM)

CONNECT-only. Bodies stay encrypted — no MITM. Denied hosts return 403 + audit row.

Closes the bypass where an agent skips the preloader (raw sockets, FFI, child without preloader env) by routing every TLS handshake through a network-ACL-gated proxy.

# Enable in policy: "network_proxy": { "enabled": true, "bind_addr": "127.0.0.1:18089" } # Then point the agent's HTTPS_PROXY at the listener: HTTPS_PROXY=http://127.0.0.1:18089 my-agent

The proxy gates reachability + audit; the preloader gates payload contents via privacy_egress_ml. Together they're complete.

JSON-RPC reference (35+ methods)

Speak line-delimited JSON to ~/.hyperspace/avmd.sock. Every typed SDK method maps 1:1 to a method below. Every CLI subcommand wraps one of these.

Gates
MethodPurpose
tool_gateSingle-call gate — tool ACL + file ACL + network ACL + classify + egress + tool approvals in one round-trip
classifyRaw 16-module destructive pipeline
review_outputSymmetric injection scan on tool / sandbox results
permission_check_toolACL primitive — tool name
permission_check_fileACL primitive — filesystem path
permission_check_networkACL primitive — outbound host
Privacy
MethodPurpose
privacy_classifyRegex-only PII / credentials / financial classifier
privacy_classify_mlRegex + Privacy Filter ML ensemble
privacy_redactReplace classified spans with category tokens
privacy_egressApply egress policy to outbound bytes (regex)
privacy_egress_mlApply egress policy to outbound bytes (ensemble)
privacy_filter_statusHealth probe for the ML sidecar
Agent lifecycle
MethodPurpose
register_agentRegister with framework / pid / uid / tools / tier
deregister_agentRemove from registry
agent_statusOne agent or list all registered
record_credentialLog that an agent accessed a credential
clear_agentWipe per-agent state
agent_profileBehavioural profile summary
Resource governance
MethodPurpose
resources_statusPer-agent + system CPU / RSS sampler snapshot
resources_attachRegister agent_id → pid for sampling
resources_detachStop sampling an agent
gas_statusShow gas budget for one agent
gas_debitDebit gas (with optional initial_budget)
gas_resetReset / establish a budget
set_rate_limitSet custom rate limits
grant_networkGrant network capability to an agent
Tool approvals
MethodPurpose
approve_toolApprove a tool for an agent (once / session / permanent)
deny_toolDeny a tool
list_approvalsList tool approvals for an agent
Operations
MethodPurpose
pingHealth check — returns "pong"
sandbox_statusActive sandbox tiers + hardening flags
config_reloadRe-read AVM policy from disk
audit_verifyVerify audit-log hash chain
audit_rotateForce-rotate the active log
audit_archivesList rotated archives
autoupdate_checkManual signed-binary update probe
installSandboxed package install with supply-chain protection
install_scanSupply-chain pattern scan over a directory
executeExecute code in a sandboxed environment
traceGet execution trace for a task
metricsPrometheus-format snapshot
redactRedact secrets/PII from text (legacy regex redactor)
Worked examples

tool_gate — the one call most integrations make. Combines tool ACL, file ACL, network ACL, classify pipeline, egress-ML, and tool approvals in one round-trip.

# Request { "jsonrpc":"2.0","id":1,"method":"tool_gate","params":{ "agent_id": "cursor-1", "tool": "curl", "command": "curl -X POST https://backboard.railway.app/graphql/v2 -d 'mutation { volumeDelete(...) }'", "kind": "auto", "network_host": "backboard.railway.app" } } # Response { "jsonrpc":"2.0","id":1,"result":{ "action": "block", "stage": "classify", "reason": "destructive_detector matched volumeDelete (level 4 DESTROY)", "audit_seq": 1543, "detectors": ["destructive_detector","network_scope","behavioral_detector"] } }

privacy_egress_ml — redact unstructured PII before the wire.

# Request { "jsonrpc":"2.0","id":2,"method":"privacy_egress_ml","params":{ "text": "Customer: Bob Smith, 1 Main St, sk_live_abc123", "direction": "out" } } # Response { "jsonrpc":"2.0","id":2,"result":{ "action": "block", "redacted": "Customer: <PERSON>, <ADDRESS>, <CREDENTIAL>", "categories": { "pii":"warn", "location":"warn", "credentials":"block" } } }

register_agent — called once per agent process.

{ "jsonrpc":"2.0","id":3,"method":"register_agent","params":{ "id": "cursor-1", "framework": "cursor", "pid": 12345, "uid": 1000, "tools": ["bash","curl","fetch"], "tier": "docker" } }

install — sandboxed package install with supply-chain guard.

{ "jsonrpc":"2.0","id":4,"method":"install","params":{ "manager": "pip", "packages": ["litellm"], "agent_id": "pip", "lockfile_sha256": "e3b0c44298fc1c149afbf4c8996fb924..." } }

audit_verify — offline integrity check. Reads ~/.hyperspace/audit.jsonl, walks the chain, fails on any prev_hash mismatch.

{ "jsonrpc":"2.0","id":5,"method":"audit_verify","params":{} }

For the full set, see the source — every method has at least one integration test in tests/protocol.rs.

avmctl — operator CLI

One binary, every operator workflow. Subcommands are grouped by what they touch: the daemon over its 0o600 socket, or the local filesystem (no daemon needed). Every daemon-touching subcommand wraps one of the JSON-RPC methods above.

Health, status, and inventory
avmctl ping # {"result":"pong","id":1} avmctl status # { # "sandbox": { "docker": true, "process": true, "direct": true }, # "hardening": { "no_new_privs": true, "rlimit_set": true, "seccomp_bpf": true }, # "cgroup_v2": true, "num_cpus": 16, "version": "2.5.0" # } avmctl agents # list every registered agent + tier + uid + tools_granted avmctl agents --json | jq '.[].id' # pipeable avmctl sandbox status # active sandbox tiers + flags only avmctl metrics # Prometheus exposition format avmctl metrics --json # raw counters as JSON
Test the gate without an agent
# Bash command (default kind) avmctl gate "rm -rf /" # {"action":"block","stage":"classify","level":4,"reason":"local destruction"} # With an explicit tool ACL check first avmctl gate "ls -la" --tool ls --agent cursor-1 # With strict.json: {"action":"block","stage":"tool_acl","reason":"deny by default"} # GraphQL mutation as kind=http avmctl gate 'mutation { volumeDelete(volumeId:"...")}' \ --kind graphql --host backboard.railway.app --tool curl # {"action":"block","stage":"classify","level":4,"detector":"destructive_detector"} # Allow-path baseline avmctl gate "echo hello" --tool bash --agent cursor-1 # {"action":"allow"}
Resources, gas, rate-limits
avmctl resources # system + per-agent sampler snapshot avmctl resources --agent cursor-1 # one agent only avmctl gas show cursor-1 # {"agent":"cursor-1","budget":1000000,"used":34812,"remaining":965188} avmctl gas reset cursor-1 5000000 # raise budget to 5M units avmctl call gas_debit '{"agent":"cursor-1","amount":100}' avmctl call set_rate_limit \ '{"agent":"cursor-1","tokens_per_minute":120,"burst":20}' avmctl call grant_network \ '{"agent":"cursor-1","host":"api.openai.com"}'
Audit log
avmctl logs --tail 20 # last 20 audit rows avmctl logs --filter "volumeDelete" # grep across rows avmctl logs --since 1h --filter "ToolCalled" avmctl logs --path ~/.hyperspace/audit.20260427.jsonl avmctl audit verify # {"verified":true,"events":1542,"path":"~/.hyperspace/audit.jsonl"} avmctl call audit_rotate '{}' # force rotate now avmctl call audit_archives '{}' # list rotated files
Privacy
avmctl privacy classify "My SSN is 123-45-6789, call 555-1234" # {"detections":[{"category":"pii","span":[10,21],"text":"123-45-6789"}, # {"category":"pii","span":[30,38],"text":"555-1234"}]} avmctl privacy classify --ml "Bob Smith lives at 1 Main St" # Uses the openai/privacy-filter sidecar (regex + ML ensemble) avmctl privacy redact "key=sk_live_abc123" # {"redacted":"key=<CREDENTIAL>","categories":["credentials"]} avmctl privacy filter-status # {"online":true,"backend":"rust-onnx","p50_ms":2.4,"loaded":"openai/privacy-filter"} avmctl call privacy_egress_ml \ '{"text":"export AWS_SECRET=AKIA...","direction":"out"}'
Supply chain — install + scan
# Sandboxed install (Docker + cap-drop + lockfile sha256 + post-install scan) avmctl call install \ '{"manager":"pip","packages":["litellm"],"agent_id":"pip"}' # {"success":false,"quarantined":["litellm-X.Y.Z"], # "audit":["supply_chain: blocked by behavioural scan"]} # Scan an existing site-packages tree without installing anything avmctl install scan ~/.venv/lib/python3.11/site-packages # {"scanned":284,"suspicious":2,"matches":[{"pkg":"...","pattern":"reads ~/.aws"}]}
Agent lifecycle
avmctl call register_agent '{ "id":"cursor-1","framework":"cursor","pid":12345, "uid":1000,"tools":["bash","curl","fetch"],"tier":"docker" }' avmctl call agent_status '{"id":"cursor-1"}' avmctl call agent_profile '{"id":"cursor-1"}' # behavioural baseline avmctl call deregister_agent '{"id":"cursor-1"}' avmctl call clear_agent '{"id":"cursor-1"}' # wipe per-agent state avmctl call approve_tool \ '{"agent":"cursor-1","tool":"docker","ttl":"session"}' avmctl call list_approvals '{"agent":"cursor-1"}' avmctl call deny_tool '{"agent":"cursor-1","tool":"sudo"}'
Policy
# Local — no daemon needed (offline schema validation + explanation). avmctl policy lint ~/.hyperspace/avm-policy.json # {"errors":[],"warnings":[ # "rule W003: tools.default=allow with no allowlist (consider deny)"]} avmctl policy explain "docker volume rm prod-data" \ --policy ~/.hyperspace/avm-policy.json --tool docker # Explains which rule blocks/allows the command. avmctl policy schema > avm-policy.schema.json # JSON schema for VS Code / IntelliJ editor support. avmctl config reload # re-read policy from disk
Signed defs + classifier corpus generator
# Sign a defs payload (Ed25519, hex or file path). avmctl defs sign defs/2026-04-28.json --key $HYPERSPACE_DEFS_PRIVKEY \ > defs/2026-04-28.signed.json avmctl defs verify defs/2026-04-28.signed.json # {"verified":true,"signer":"","schema_version":1} # Deterministic synthetic training corpus for avm-classifier-7b. # JSONL on stdout (or --out PATH). Same seed = identical corpus — # reproducible across builds. See AVM_CLASSIFIER.md for the contract. avmctl defs generate-adversarial --count 50000 --seed 42 \ --out training/data/regex_mutation.jsonl # {"wrote":"training/data/regex_mutation.jsonl","count":50000,"seed":42}
Auto-update + raw RPC escape hatch
avmctl autoupdate check # {"current":"2.5.0","available":"2.6.0","manifest_signature_ok":true} # Any RPC method directly — useful in CI, in scripts, or while exploring. avmctl call <method> '{... json params ...}' avmctl call privacy_filter_status '{}' avmctl call resources_attach '{"agent":"cursor-1","pid":12345}' avmctl call review_output '{"text":"<tool result>"}'
tip · pipe-friendly

Every command takes --json for machine-readable output. avmctl logs --filter X --json | jq + avmctl metrics --json | prom2json are common operator pipelines.

SDKs
TypeScript — @hyperspace/avm-client

Zero runtime deps (Node 18+ stdlib only). Typed wrappers + escape hatch.

import { AvmClient } from "@hyperspace/avm-client"; const avm = new AvmClient(); const decision = await avm.toolGate({ agentId: "cursor-1", tool: "curl", command: "curl -X POST .../graphql -d 'mutation { volumeDelete(...) }'", kind: "auto", networkHost: "backboard.railway.app", }); if (decision.action !== "allow") { throw new Error(`AVM blocked at ${decision.stage}: ${decision.reason}`); }
Python — hyperspace-avm-client

Stdlib only. Same API surface, snake_case to match Python idioms.

from hyperspace_avm import AvmClient avm = AvmClient() decision = avm.tool_gate( agent_id="aider-1", tool="curl", command="curl -X POST .../graphql -d 'mutation { volumeDelete(...) }'", kind="auto", network_host="backboard.railway.app", ) if decision["action"] != "allow": raise RuntimeError(f"AVM blocked at {decision['stage']}: {decision.get('reason')}")
Deploy / systemd / launchd
Linux (systemd user service)

Hardened: NoNewPrivileges, ProtectSystem=strict, ProtectHome=read-only with ~/.hyperspace as the one writable path, PrivateTmp, PrivateDevices, Protect{KernelTunables,KernelModules,KernelLogs,ControlGroups,Clock,Hostname}, RestrictAddressFamilies, RestrictNamespaces, MemoryDenyWriteExecute, RestrictRealtime, SystemCallFilter=@system-service ~@privileged ~@mount ~@reboot ~@swap. MemoryHigh=512M, MemoryMax=1G.

# Auto-installed by install.sh; verify: systemctl --user status avmd systemctl --user restart avmd journalctl --user -u avmd -f
macOS (LaunchAgent)
# ~/Library/LaunchAgents/space.hyper.avmd.plist (auto-installed) launchctl list | grep avmd launchctl unload ~/Library/LaunchAgents/space.hyper.avmd.plist launchctl load ~/Library/LaunchAgents/space.hyper.avmd.plist tail -f /tmp/avmd.err.log
Auto-update (signed)
Signed binary auto-update — fetch manifest.json, check Ed25519 signature, download tarball verify sha256, atomic rename old to .old new in place, service manager restarts the binary; no code download without signature verification

Off by default. When enabled, no code download proceeds without signature verification.

avmd pulls a signed manifest, verifies its Ed25519 signature against a compile-time-pinned public key, downloads the matching platform binary, sha256-verifies, atomically swaps current_exe(), exec-replaces. No reqwest / native HTTP stack — shells out to curl.

Test suites
SuiteCountPurpose
lib unit tests458Per-module unit tests (audit, classify, gas, sandbox, ...)
avmctl unit tests16argv parsing, hex round-trip, schema valid-JSON, predict_action
destructive vendor matrix22One regression test per vendor whose destructive APIs we gate
adversarial / red-team17 + 4 ignoredObfuscation-bypass corpus: eval, base64-pipe-shell, curl-pipe-shell, printf-hex, python -c, node -e, IFS, X=rm, PowerShell -enc, plus 4 honest-misses
integration_e2e6Boots a real avmd binary in a temp HOME and verifies wire contracts (ping, classify-blocks-rm-rf, tool_gate-blocks-PocketOS, audit-verify, metrics, persistence-survives-restart)
toctou7Multi-stage / TOCTOU: write-then-exec, base64-to-shell wrapper, curl-piped-bash, eval-curl, install_scan against fake site-packages
avmd-mcp sidecar10Tool catalog, dispatch, error paths, stub-RPC routing
privacy-filter-rs Viterbi5BIOES decoder, span extraction, constraint repair
privacy-filter-rs --self-test4Wire-protocol round-trip, envelope shape (gates every release)
Python SDK9Stub-server round-trips for ping, tool_gate, error paths, classify, egress
TypeScript SDK8Same shape against a stub Unix-socket server (node:test)
fuzz harness4 targetscargo-fuzz: classify never panics, tool_gate never panics, policy_lint never panics, audit_chain never panics
Run them
# Daemon + avmctl + integration + toctou + adversarial: cd packages/avmd cargo test --release -- --test-threads=2 # MCP sidecar: cd packages/avmd/sidecars/avmd-mcp cargo test --release # Privacy-filter Rust sidecar (incl. --self-test gate): cd packages/avmd/sidecars/privacy-filter-rs cargo test --release cargo run --release -- --self-test # Python SDK: cd packages/avmd/sdks/python python3 -m unittest discover tests # TypeScript SDK: cd packages/avmd/sdks/typescript node --test tests/ # Fuzz (nightly toolchain): cd packages/avmd cargo +nightly fuzz run classify_does_not_panic -- -max_total_time=300
Performance

Bench numbers from cargo bench --bench classify on a recent x86_64 host. Each input is a complete classify call through the full 16-module pipeline running on a single thread.

ls -la
0.8 µs
rm -rf /
1.5 µs
kubectl get pods
3.0 µs
rm -rf /tmp/foo
3.2 µs
aws ec2 terminate
3.9 µs
PocketOS curl + GraphQL
7.6 µs

p50 ~3 µs, p99 ~8 µs, ~200–300k classifies/sec single-threaded. Adding AVM to a Cursor session adds ~10 µs of latency to every shell command and HTTP call the agent issues.

Memory under load
Threat model summary

STRIDE-style coverage in THREAT_MODEL.md. Headlines:

Trusted base

The user's kernel, the avmd binary (after sha256 + Ed25519 signature verification), the user's local disk for the audit chain, libonnxruntime when the ML sidecar is enabled.

Not trusted

The agent (any agent), the agent's framework, the model behind the agent, system-prompt-only safety rules, outbound API surfaces, inbound tool results.

Out of scope (stated explicitly)
Telemetry contract
TL;DR

avmd collects nothing by default. No analytics service. No anonymous deployment ID. No audit-log upload. No phone-home. Enforced by absence of code, not a flag.

If a future avmd release adds opt-in telemetry, the contract in TELEMETRY.md says it must be: off by default, aggregate-only (never per-event), endpoint configurable in policy, every POST audit-rowed, avmctl telemetry pause + erase operator controls. Audit logs / policy snapshots / PII / crash dumps are never in scope.

avmd-cloud (design notes)

Local AVM gates what touches the user's machine. When a vendor hosts an agent runtime (Claude Code in claude.ai, OpenAI Assistants), the cloud agent also takes actions inside the cloud that local avmd never sees.

The design hold for a future server-side companion lives in CLOUD_DAEMON.md. Shared contracts: policy schema, audit row format, RPC method names, signed defs format, privacy posture. What avmd-cloud must NEVER do: ingest local audit logs, relax local policy, share telemetry cross-tenant, run untrusted code on shared kernel.

MCP server — avmd-mcp

Single Rust binary that wraps the avmd Unix-socket protocol as a Model Context Protocol tools/* interface. One JSON entry in Cursor / Claude Code settings; no preloader required.

MCP toolavmd RPC
avm.gatetool_gate
avm.classifyclassify
avm.privacy_egressprivacy_egress_ml
avm.review_outputreview_output
avm.audit_verifyaudit_verify
avm.statussandbox_status
Reference policies

policies/strict.json

Fail-closed everywhere. tools.default = "deny". network.allow_domains = []. privacy.ml.fail_open = false. CI runners, untrusted-agent setups.

policies/permissive.json

Sane defaults for a developer's workstation. Reads + writes allowed inside workspace, network egress allowed except private ranges, destructive ops blocked or require approval. PocketOS-style still gets caught.

# Pick one and copy: cp policies/permissive.json ~/.hyperspace/avm-policy.json avmctl config reload avmctl policy lint ~/.hyperspace/avm-policy.json # 11 lint rules avmctl policy schema > ~/.config/jsonschema/avm.json # editor autocomplete
Signed security definitions (hot-reload)

Operators can ship custom destructive patterns / network rules / PII patterns / tool safety overrides via ~/.hyperspace/avm-defs/current.json. avmd verifies the Ed25519 signature, schema-version-pins to [MIN_SCHEMA, MAX_SCHEMA], semantic-validates (every regex compiles, every level ≤ 4, every action in {allow, deny, require_approval}), and persists a last-known-good snapshot. Bad reload → previous defs stay active.

# 1. Author the payload cat > my-defs.json <<'EOF' { "schema_version": 1, "version": "ops-2026-04-28", "destructive_mutations": [ { "name": "deletePrivateBucket", "level": 4, "service": "internal" } ], "destructive_bash_patterns": [], "exfiltration_domains": ["my-attacker.example"], "credential_patterns": [], "network_rules": [ { "host_pattern": "*.internal.corp", "action": "allow", "reason": "trusted internal" } ], "pii_patterns": [], "tool_safety": [] } EOF # 2. Sign with your Ed25519 key avmctl defs sign my-defs.json --key ~/.hyperspace/defs-key.hex \ > ~/.hyperspace/avm-defs/current.json # 3. Verify the envelope round-trips avmctl defs verify ~/.hyperspace/avm-defs/current.json # {"verified":true,"payload_bytes":N,"public_key":"..."} # 4. Daemon hot-reloads on file change (30s polling), or: avmctl config reload
Fuzz harness

cargo-fuzz harness with four targets covering the surfaces that take untrusted input. None of these should ever panic for any input.

TargetProperty
classify_does_not_panicRandom UTF-8 → classify never panics, never returns level > 4
tool_gate_does_not_panicRandom ToolGateParams shape → classify + privacy + ACL helpers don't panic
policy_lint_does_not_panicRandom JSON → policy_lint either rejects parse OR runs all rules cleanly
audit_chain_does_not_panicRandom bytes as audit log → verify_chain returns Ok / Err, never panics
cargo install cargo-fuzz cargo +nightly fuzz run classify_does_not_panic -- -max_total_time=300
What AVM does not catch

Honesty about limits is non-negotiable for a security product. The regex tier alone cannot catch:

The full set of catches and explicitly-documented misses lives in FEATURES.md and tests/adversarial.rs. Every honest-miss has an audit-pinned regression test — the contract is that they remain misses or become catches, never silently start auto-allowing.

Roadmap

Q3 2026 · avm-classifier-7b first checkpoint

Qwen 2.5 7B + LoRA fine-tune for action classification. Training scaffold + 28-example held-out set is shipped today (v2.1.1); next is the actual training run on the Vega H100 once corpus collection hits 100K labelled examples. See Training.

Q3 2026 · microVM sandbox tier

Add a fourth sandbox tier above Docker: Firecracker microVM per agent task. Closes residual container-escape risk; requires only operators who want extra-strong isolation to flip a config field. Roadmap target tied to first vendor-deploying-AVM-as-default ship.

Q4 2026 · cloud daemon (avmd-cloud)

Server-side companion that gates a cloud agent's cloud-internal calls (third-party APIs, cloud worker spawn) the same way local avmd gates the boundary into the user's box. Design lives in CLOUD_DAEMON.md; build gated on first vendor partner.

2027 · vendor adoption

Goal: by end of 2027, every major AI agent platform ships AVM (or an AVM-compatible gate) by default, with a shared open audit format. The way every browser ships V8 today.

Track progress against the public commit log: github.com/hyperspaceai/p2pnetwork/commits/master/packages/avmd. Slipped dates get an honest writeup in the next changelog entry rather than a quiet shuffle.

FAQ

Does AVM slow my agent down?

~3 µs p50, ~10 µs p99 per classify call (single-threaded). On a typical agent that issues 1-10 tool calls per minute, AVM adds well under 1 ms of total latency. The tail-latency budget is in BENCHMARKS.md with reproduction recipe.

What if AVM breaks my workflow?

Flip enabled: false in ~/.hyperspace/avm-policy.json; restart the daemon; AVM is a no-op. For finer control, run in audit-only mode (enforcement: log_only) — classify still writes audit rows, but never blocks. Recommended for the first week of any deploy: see what the gate would have caught before flipping enforcement on.

BSL 1.1 — can I use AVM commercially?

Yes. BSL 1.1 with a Hyperspace Use Limitation: you cannot resell AVM as a hosted competing service. Internal use, integration with your own agent product, vendoring into commercial software, all permitted. The licence converts to Apache 2.0 four years after each release.

How do I roll my own release-signing key?

Replace RELEASE_PUBKEY_HEX in src/autoupdate.rs with your own pubkey, set AVMD_RELEASE_PRIVATE_KEY as a build secret, build, ship to your fleet. Existing installs cannot auto-update from your fork (intended — trust roots don't share). Full procedure in the release-keys README.

What about Apple Silicon vs Intel Mac?

Apple Silicon (darwin-arm64): full support. Intel Mac (darwin-amd64): not in the release matrix — macos-13 GitHub Actions runners are deprecated and frequently leave the build queue indefinitely. Build from source on Intel Mac with cargo build --release; everything works, the published tarball just doesn't ship.

Does AVM need root?

No. The default install runs as a user systemd unit (or LaunchAgent on macOS). seccomp, cgroup v2, and rlimits all work in user-space modes; what's missing without root is full cgroup enforcement (memory caps degrade to monitoring + warn). For root-mode hardening, the systemd unit can be moved to system-level — see deploy/linux/avmd.service.

How do I see what AVM blocked yesterday?

avmctl logs --since 1d --filter "action=block" — readable JSONL, one decision per line, hash-chained against the previous row. Run avmctl audit verify to confirm the chain is intact (offline, no daemon required).

What does the ML sidecar phone home to?

Nothing. Both ML sidecars (openai/privacy-filter and the in-development avm-classifier-7b) run as separate processes on the user's host, talk only to avmd over a 0o600 Unix socket. Model weights are local. No telemetry. Verifiable in PRIVACY.md.

Wire your runtime into AVM — copy-paste

Three ways every agent runtime integrates: preloader (env var), SDK (npm/pip), or MCP (settings.json). Pick the one that matches your runtime; copy-paste the snippet; you're done.

Tool-using assistants with MCP support

The cleanest path. One JSON entry adds avmd as an MCP server; every tool call the assistant makes routes through it.

# ~/.cursor/mcp.json or ~/.claude/mcp.json or any MCP-speaking client { "mcpServers": { "avmd": { "command": "/usr/local/bin/avmd-mcp", "args": [] } } }

Six tools become available: avm.gate, avm.classify, avm.privacy_egress, avm.review_output, avm.audit_verify, avm.status. The assistant calls avm.gate before any other tool; if the gate denies, the call is refused at the model layer.

Node.js agents (any framework)
# Wraps every child_process.spawn / fetch / require('http').request NODE_OPTIONS="--require /usr/local/lib/avmd/preload/avmd-preload.cjs" my-agent # Or with the SDK for explicit gating npm install @hyperspace/avm-client import { AvmClient } from "@hyperspace/avm-client"; const avm = new AvmClient(); const decision = await avm.toolGate({ agentId: "my-agent-1", tool: "curl", command: userIntent, kind: "auto", }); if (decision.action !== "allow") throw new Error(decision.reason);
Python agents (LangChain, AutoGen, LlamaIndex, custom)
# Preloader-style — wraps subprocess.run / urllib.* / requests / httpx PYTHONPATH="/usr/local/lib/avmd/python:$PYTHONPATH" python my_agent.py # Or with the SDK pip install hyperspace-avm-client from hyperspace_avm import AvmClient avm = AvmClient() decision = avm.tool_gate( agent_id="my-agent-1", tool="curl", command=user_intent, kind="auto", ) if decision["action"] != "allow": raise RuntimeError(decision["reason"])
Hyperspace CLI users (since v5.40.x)

Nothing to do. hyperspace start spawns avmd as a child process automatically; the daemon picks up its policy from ~/.hyperspace/avm-policy.json on first start. Inspect with hyperspace avm status.

Anything else (custom runtimes, build tools, CI)

Speak JSON-RPC over the 0o600 Unix socket directly. The wire format is line-delimited JSON; every method documented in the RPC reference takes plain JSON params. Three lines of bash:

echo '{"jsonrpc":"2.0","id":1,"method":"tool_gate","params":{"tool":"curl","command":"...","kind":"auto","agent_id":"ci-runner"}}' \ | socat - UNIX-CONNECT:$HOME/.hyperspace/avmd.sock
Trust & transparency

Local-first · never phones home

The audit chain lives on your disk. Telemetry — if any — is per-user opt-in via avmctl telemetry enable. Default is off. Verify in TELEMETRY.md.

Reproducible signed releases

Ed25519-signed manifest committing per-asset sha256. The signing private key is held off-tree (encrypted backup at packages/avmd/.release-keys/avmd-release-key.hex.enc) + GitHub Actions secret. The compile-time pubkey baked into autoupdate.rs is the trust root.

Open-source and auditable

Every line of avmd is in this repo (BSL 1.1, converts to Apache 2.0 four years after each release). The 554-test suite is your floor: read tests/ before installing.

Responsible disclosure

security@hyper.space. PGP key in THREAT_MODEL.md. 90-day embargo for confirmed vulns; CVE assignment for severity ≥ medium. No bug bounty currently — if that changes, this card updates.

All documentation
DocumentWhat's in it
README.mdInstall + use + RPC surface
INTEGRATION.mdVendor playbook — how to ship with AVM
FEATURES.mdWhat AVM catches; honest misses
PRIVACY.mdPrivacy posture, layer by layer
THREAT_MODEL.mdSTRIDE coverage, trusted base, out of scope
BENCHMARKS.mdPublished latency / throughput numbers + reproduction
TELEMETRY.mdWhat avmd collects (nothing, by default)
CLOUD_DAEMON.mdDesign hold for a future server-side companion
CHANGELOG.mdRelease history
policies/strict + permissive reference policies
Changelog
CLI v5.40.0 — avmd as a CLI sidecar (delivered to all nodes)April 2026
v2.1.2 — linux binaries on ubuntu-22.04 (GLIBC 2.35) for portabilityApril 2026
v2.1.1 — signed auto-update infrastructure liveApril 2026
v2.1.0 — neurosymbolic gate + classifier-7b training scaffoldApril 2026
v2.x — productionApril 2026
v2.x — defs hot-reload safety + TOCTOU tests
v2.x — outbound proxy + adversarial corpus + CI release
v2.x — persistent state + sidecar supervisor + e2e
v2.x — perf bench + Prometheus metrics + audit rotation + MCP
v2.x — production launch (April 2026)
v0.2.0 — supply-chain protection (early 2026)
v0.1.0 — foundational gate (early 2026)

Full changelog: CHANGELOG.md