Runtime safety layer for autonomous AI agents
Agent Vault is an open-source control plane that helps you run AI agents with clear boundaries.
It tracks whether work is progressing, re-checks approvals against live state,
and turns observed behavior into enforceable policy.
You deploy an autonomous AI agent to refactor a codebase. Within minutes:
- It loops on the same file, burning tokens without progress
- It writes to production config that was only approved for staging
- It calls a shell command your policy explicitly forbids
- The state it was approved to modify has changed since the human said "yes"
- Nobody notices until the damage is done
Agent Vault exists to catch every one of these before they happen.
| Module | What it solves | How it works |
|---|---|---|
| Sentinel | Agent is looping, stalled, or regressing | Tracks semantic progress via embeddings. Compares each step's state against the goal vector. Detects when cosine similarity stops improving (stall) or drops (regression). Escalates: Warn → Downgrade → Pause → Deny. |
| Witness | Approved state has changed since approval | Captures a SHA-256 hash of the state at approval time. Before execution, re-hashes the current state and compares. If the world changed, blocks execution with WITNESS_HASH_MISMATCH. |
| Learner | Writing security rules by hand is tedious and incomplete | Observes agent behavior via NATS telemetry spans. Builds statistical baselines of "normal" tool usage, cost patterns, and action frequencies. Generates policy candidates you can review and promote to enforceable rules. |
Agent Vault has two layers that work together:
┌─────────────────────────────────────────────────────────────────┐
│ Your Agent / Host Process │
│ │
│ from agentvault import PolicyEvaluator, WitnessGuard │
│ from agentvault.sentinel import SentinelTracker │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────────────────┐ │
│ │ Policy │ │ Sentinel │ │ Witness Guard │ │
│ │ Evaluator │ │ Tracker │ │ (capture + verify) │ │
│ │ │ │ │ │ │ │
│ │ Before every │ │ Each step: │ │ At approval: hash │ │
│ │ tool call → │ │ progress │ │ current state. │ │
│ │ Allow/Deny/ │ │ check → │ │ Before execution: │ │
│ │ Require │ │ Warn/Block │ │ re-hash → match? │ │
│ │ Approval │ │ if looping │ │ Yes → proceed │ │
│ │ │ │ │ │ No → block │ │
│ └──────────────┘ └──────────────┘ └───────────────────────┘ │
│ All three run IN-PROCESS (no network latency) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Learner Client → emits telemetry spans to NATS │ │
│ └─────────────────────────┬────────────────────────────────┘ │
└─────────────────────────────┼────────────────────────────────────┘
│
HTTP / gRPC calls for:
• Loading policies from server
• Creating runs, approvals, incidents
• Resolving approvals (human-in-the-loop)
• Querying learner baselines & candidates
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Agent Vault Control Plane │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Policy │ │ Run │ │ Approval │ │ Incident │ │
│ │ Service │ │ Service │ │ Service │ │ Service │ │
│ │ │ │ │ │ │ │ │ │
│ │ Store & │ │ Track │ │ Human- │ │ Track rule │ │
│ │ version │ │ agent │ │ in-the- │ │ violations │ │
│ │ rules │ │ runs │ │ loop │ │ with sev & │ │
│ │ │ │ │ │ + witness│ │ remediation │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Learner Service │ Span Ingest │ Baseline Aggregator │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │PostgreSQL│ │ClickHouse│ │ NATS │ │ Redis │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
| Component | Where it runs | Network needed? | What it does |
|---|---|---|---|
| PolicyEvaluator | In your agent process | No | Evaluates rules locally against each action. Microsecond latency. |
| SentinelTracker | In your agent process | No | Embeds step summaries, computes progress delta, detects anomalies. |
| WitnessGuard | In your agent process | No | SHA-256 hashing and constant-time comparison of state snapshots. |
| LearnerClient | In your agent process | Yes (NATS) | Publishes SpanEvent telemetry to NATS for server-side aggregation. |
| Control Plane | Docker / Kubernetes | Yes (HTTP/gRPC) | Stores policies, tracks runs, manages approvals, processes incidents, learns baselines. |
Python (via PyO3 bindings):
from agentvault.core import PolicyEvaluator, RunContextManager
from agentvault.sentinel import SentinelTracker
from agentvault.witness import WitnessGuard
from agentvault.learner import LearnerClient
# 1. Load policies (from server or local file)
evaluator = PolicyEvaluator()
evaluator.load_rules_json(rules_json)
# 2. Before every tool call: check policy
decision = evaluator.evaluate("TOOL", "execute_shell", context)
if decision == "DENY":
raise BlockedAction("Policy denied this tool")
# 3. Track progress with Sentinel
sentinel = SentinelTracker(config={"stall_window": 5})
sentinel.register_goal(run_id, "Refactor auth to JWT")
result = sentinel.evaluate_step(run_id, step_index, "Reading auth module...")
if result.get("intervention"):
handle_intervention(result["intervention"])
# 4. Capture witness hash for approvals
guard = WitnessGuard()
snapshot = guard.capture_json('{"file": "config.yml", "content": "..."}', "file://config.yml")
# Store snapshot.hash with the approval request
# 5. Before executing approved action: verify state hasn't changed
result = guard.verify_json(snapshot, current_file_content_json)
if not result["valid"]:
raise StaleApproval("State changed since approval: " + result["reason_code"])
# 6. Emit telemetry for Learner
learner = LearnerClient({"nats_url": "nats://localhost:4222", "tenant_id": "..."})
await learner.connect()
await learner.emit_span({"kind": "TOOL_CALL", "tool_name": "read_file", ...})Rust (native):
use agentvault_core::policy::{PolicyEvaluator, PolicyEvaluatorHandle};
use agentvault_core::run::RunContextManager;
use agentvault_sentinel::SentinelTracker;
use agentvault_witness::WitnessGuard;
let evaluator = PolicyEvaluatorHandle::new(/* ... */);
let decision = evaluator.evaluate(&run_context, &action);
let sentinel = SentinelTracker::new(config, embed_handle);
sentinel.register_goal(run_id, "Refactor auth module");
let eval = sentinel.evaluate_step(run_id, 1, "Analyzing file structure...");
let guard = WitnessGuard::new();
let snapshot = guard.capture_json_for_approval(&state_json, "file://config.yml")?;
let result = guard.verify_json_before_execution(&snapshot, ¤t_json)?;Node.js / TypeScript (via NAPI-RS):
import { PolicyEvaluator, RunContextManager, WitnessGuard, LearnerClient } from '@agentvault/node';
const evaluator = new PolicyEvaluator();
evaluator.loadRulesJson(rulesJson);
const decision = evaluator.evaluate('TOOL', 'read_file', context);
const guard = new WitnessGuard();
const snapshot = guard.captureJson(stateJson, 'file://config.yml');
const result = guard.verifyJson(snapshot, currentJson);Your agent talks to the control plane server via HTTP or gRPC for operations that need persistence and coordination:
# These are the API calls your agent makes to the server:
# Load policies for this tenant
GET /v1/policies?scopeId={tenant}&scopeType=ORG
# Report that a run has started
POST /v1/runs { agentId, scope, mode, goal }
# Request human approval for a dangerous action
POST /v1/approvals { runId, witness: { hash }, reasonCode }
# Report a policy violation
POST /v1/incidents { severity, reasonCode, title, runId }
# Mark run complete
POST /v1/runs/{id}/complete { terminalStatus: "COMPLETED" }Here is a concrete scenario showing every component in action. This exact flow has been tested against the running Docker stack.
Step 1 — OPERATOR defines a policy
┌─────────────────────────────────────┐
│ "code-agent-guardrails" │
│ │
│ Default: DENY everything │
│ Rule 1: ALLOW tool "read_file" │
│ Rule 2: ALLOW tool "search_code" │
│ Rule 3: REQUIRE_APPROVAL for writes │
│ Rule 4: DENY tool "execute_shell" │
└─────────────────────────────────────┘
│
Step 2 — AGENT starts a run
Run created: status=PENDING, goal="Refactor auth to JWT"
│
Step 3 — AGENT reads files (policy: ALLOW) ✓
Sentinel: progress 0.0 → 0.34 (healthy)
│
Step 4 — AGENT wants to write src/auth/jwt.rs
Policy evaluator returns: REQUIRE_APPROVAL
Agent captures witness hash of current file state
Agent creates approval request with hash
│
Step 5 — HUMAN reviews and approves ✓
│
Step 6 — Before writing, WITNESS revalidates
Current file hash == approved hash? YES → proceed
│
Step 7 — Meanwhile, another process modifies the file...
│
Step 8 — Agent's SECOND write attempt
Witness revalidation: hash MISMATCH
Result: witnessValid=false, reasonCode=WITNESS_HASH_MISMATCH
Action BLOCKED — stale approval
│
Step 9 — AGENT tries execute_shell
Policy evaluator returns: DENY (reason: SHELL_BLOCKED)
Incident auto-created: severity=HIGH
│
Step 10 — Run completes: status=COMPLETED
Learner has been observing all spans via NATS
Learner proposes tighter policy based on observed behavior
After running ./start.sh, execute these commands to walk through the entire flow:
export API_KEY="your-api-key-here"
export TENANT="your-tenant-uuid"
# Step 1: Create the policy
curl -X POST http://localhost:8080/v1/policies \
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"scope": {"scopeType": "ORG", "scopeId": "'"$TENANT"'"},
"name": "code-agent-guardrails",
"description": "Allow reads, require approval for writes, deny shell",
"defaultAction": "DENY",
"rules": [
{"ruleId":"00000000-0000-0000-0000-000000000010","priority":10,
"targetType":"TOOL","targetSelector":"read_file",
"action":"ALLOW","reasonCode":"SAFE_READ","enabled":true},
{"ruleId":"00000000-0000-0000-0000-000000000030","priority":30,
"targetType":"WRITE_ACTION","targetSelector":"*",
"action":"REQUIRE_APPROVAL","reasonCode":"WRITE_NEEDS_REVIEW","enabled":true},
{"ruleId":"00000000-0000-0000-0000-000000000040","priority":40,
"targetType":"TOOL","targetSelector":"execute_shell",
"action":"DENY","reasonCode":"SHELL_BLOCKED","enabled":true}
]
}'
# Step 2: Start a run
RUN=$(curl -s -X POST http://localhost:8080/v1/runs \
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"scope": {"scopeType":"ORG","scopeId":"'"$TENANT"'"},
"agentId": "11111111-1111-1111-1111-111111111111",
"mode": "enforce",
"goal": "Refactor auth module to use JWT"
}')
RUN_ID=$(echo $RUN | python3 -c "import sys,json; print(json.load(sys.stdin)['runId'])")
echo "Run: $RUN_ID"
# Step 3: Create approval with witness hash (64 hex chars = 32 bytes SHA-256)
WITNESS_HASH="a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
APPR=$(curl -s -X POST http://localhost:8080/v1/approvals \
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"runId": "'"$RUN_ID"'",
"stepIndex": 3,
"policyId": "POLICY_ID_FROM_STEP_1",
"ruleId": "00000000-0000-0000-0000-000000000030",
"reasonCode": "WRITE_NEEDS_REVIEW",
"requestedBy": "agent-codegen",
"witness": {"hash": "'"$WITNESS_HASH"'", "resourceId": "file://src/auth/jwt.rs"},
"ttlSeconds": 1800
}')
APPROVAL_ID=$(echo $APPR | python3 -c "import sys,json; print(json.load(sys.stdin)['approval']['approvalId'])")
echo "Approval: $APPROVAL_ID"
# Step 4: Revalidate witness — state UNCHANGED (should return witnessValid: true)
curl -s -X POST "http://localhost:8080/v1/approvals/$APPROVAL_ID/revalidate" \
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{"witness":{"hash":"'"$WITNESS_HASH"'","resourceId":"file://src/auth/jwt.rs"}}'
# Response: { "witnessValid": true, "reasonCode": "" }
# Step 5: Revalidate witness — state CHANGED (should return witnessValid: false)
curl -s -X POST "http://localhost:8080/v1/approvals/$APPROVAL_ID/revalidate" \
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{"witness":{"hash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","resourceId":"file://src/auth/jwt.rs"}}'
# Response: { "witnessValid": false, "reasonCode": "WITNESS_HASH_MISMATCH" }
# Step 6: Open incident for blocked tool
curl -s -X POST http://localhost:8080/v1/incidents \
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"scope": {"scopeType":"ORG","scopeId":"'"$TENANT"'"},
"severity": "HIGH",
"reasonCode": "SHELL_BLOCKED",
"title": "Agent attempted execute_shell",
"summary": "Agent tried: rm -rf /tmp/build && make deploy",
"runId": "'"$RUN_ID"'"
}'
# Step 7: Acknowledge and manage the incident
INCIDENT_ID="<id-from-step-6>"
curl -s -X POST "http://localhost:8080/v1/incidents/$INCIDENT_ID/acknowledge" \
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{"acknowledgedBy":"operator-alice"}'
# Step 8: Complete the run
curl -s -X POST "http://localhost:8080/v1/runs/$RUN_ID/complete" \
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{"terminalStatus":"COMPLETED"}'
# Step 9: Enable learner to observe
curl -s -X PUT http://localhost:8080/v1/learner/mode \
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{"scope":{"scopeType":"ORG","scopeId":"'"$TENANT"'"},"mode":"MONITOR"}'
# Step 10: Check everything in metrics
curl -s http://localhost:9090/metrics | grep agentvault_request_count| Test | What it validates |
|---|---|
| Create policy + list | Policy CRUD, tenant-scoped storage, rule serialization |
| Create run | Run lifecycle management, status transitions |
| Create approval with witness | Witness hash storage, TTL-based expiration, approval workflow |
| Revalidate (same hash) | witnessValid: true — state integrity confirmed |
| Revalidate (different hash) | witnessValid: false + WITNESS_HASH_MISMATCH — tamper detection works |
| Create incident | Security event tracking, severity classification |
| Acknowledge incident | OPEN → ACKNOWLEDGED state transition |
| Complete run | PENDING → COMPLETED terminal transition |
| Set learner mode | Learner configuration, Redis-backed mode storage |
| Idempotency (same key twice) | Returns cached response, no duplicate creation |
| No API key | Returns 401 with structured error envelope |
| Metrics endpoint | Prometheus counters per method/path/status |
# Security: no auth → 401 with request_id
curl -s http://localhost:8080/v1/policies
# {"error":{"code":"AUTH_INVALID_CREDENTIALS","message":"Invalid credentials.","request_id":"..."}}
# Idempotency: repeat same create with same key → cached response
curl -X POST http://localhost:8080/v1/policies \
-H "x-api-key: $API_KEY" -H "Idempotency-Key: unique-key-123" \
-H "Content-Type: application/json" -d '{...}'
# First call: creates resource. Second call: returns same response from cache.
# Health probes
curl http://localhost:8080/healthz # 200 (liveness)
curl http://localhost:8080/readyz # 200 (DB + Redis + NATS healthy)
# Prometheus metrics
curl http://localhost:9090/metrics | grep agentvault_
# Server logs (structured JSON)
docker logs agentvault-server -f| Layer | Technology |
|---|---|
| Core language | Rust |
| Python bindings | PyO3 |
| TypeScript / Node bindings | NAPI-RS |
| HTTP server | Axum |
| gRPC server | Tonic |
| Primary database | PostgreSQL |
| Analytics database | ClickHouse |
| Event streaming | NATS (JetStream) |
| Caching | Redis / Valkey |
| Embeddings | FastEmbed-rs (ONNX) |
git clone https://github.com/agentvault/agent-vault-control-plane.git
cd agent-vault-control-plane
./start.shThis builds the server, starts all dependencies (Postgres, ClickHouse, NATS, Redis), waits for health, and prints connection info:
═══════════════════════════════════════════════════
Agent Vault Development Stack Running
═══════════════════════════════════════════════════
HTTP API: http://localhost:8080
gRPC: localhost:50051
Metrics: http://localhost:9090/metrics
PostgreSQL: localhost:5432
ClickHouse: http://localhost:8123
NATS: localhost:4222
Redis: localhost:6379
Connect to the running Postgres and insert a key:
# Generate a random key
RAW_KEY="av_$(openssl rand -hex 24)"
KEY_HASH=$(printf '%s' "$RAW_KEY" | shasum -a 256 | cut -d' ' -f1)
KEY_PREFIX=$(echo "$RAW_KEY" | head -c 8)
TENANT_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
# Insert into the database
docker exec agentvault-postgres psql -U agentvault -d agentvault -c \
"INSERT INTO api_keys (tenant_id, name, key_hash, key_prefix, permissions)
VALUES ('$TENANT_ID', 'dev-admin', decode('$KEY_HASH', 'hex'), '$KEY_PREFIX', ARRAY['Admin']::text[]);"
echo "API Key: $RAW_KEY"
echo "Tenant: $TENANT_ID"# Health (no auth needed)
curl http://localhost:8080/healthz
curl http://localhost:8080/readyz
# Authenticated request
curl -s "http://localhost:8080/v1/policies?scopeId=$TENANT_ID&scopeType=ORG" \
-H "x-api-key: $RAW_KEY" | python3 -m json.toolcargo build --workspace
cargo test --workspace
cargo fmt --all
cargo clippy --workspace --all-targets -- -D warningsAgent Vault exposes both REST (HTTP/JSON) and gRPC APIs with full parity. All REST endpoints live under /v1/.
Every /v1/* request requires one of:
| Method | Header |
|---|---|
| API Key | x-api-key: <key> |
| Bearer Token | Authorization: Bearer <key> |
Mutating endpoints (all POST creates) accept an optional Idempotency-Key header. Same key + same operation returns the cached response without re-executing. Keys expire after 5 minutes (configurable via AV_IDEMPOTENCY_TTL_SECS).
| Method | Path | Description |
|---|---|---|
POST |
/v1/policies |
Create a new policy |
GET |
/v1/policies |
List policies for a scope |
GET |
/v1/policies/:id |
Get a policy by ID |
POST |
/v1/policies/:id/activate |
Activate a policy (with optimistic concurrency via etag) |
POST |
/v1/policies/:id/deactivate |
Deactivate a policy |
| Method | Path | Description |
|---|---|---|
POST |
/v1/runs |
Create an agent run |
GET |
/v1/runs |
List runs for a scope |
GET |
/v1/runs/:id |
Get a run by ID |
POST |
/v1/runs/:id/complete |
Mark run as COMPLETED or FAILED |
POST |
/v1/runs/:id/cancel |
Cancel a run |
| Method | Path | Description |
|---|---|---|
POST |
/v1/approvals |
Create an approval request (requires witness hash) |
GET |
/v1/approvals |
List approvals |
GET |
/v1/approvals/:id |
Get an approval |
POST |
/v1/approvals/:id/resolve |
Approve, reject, escalate, or cancel |
POST |
/v1/approvals/:id/revalidate |
Re-check witness validity against current state |
| Method | Path | Description |
|---|---|---|
POST |
/v1/incidents |
Open an incident |
GET |
/v1/incidents |
List incidents |
GET |
/v1/incidents/:id |
Get an incident |
POST |
/v1/incidents/:id/acknowledge |
Acknowledge an incident |
POST |
/v1/incidents/:id/resolve |
Resolve an incident |
| Method | Path | Description |
|---|---|---|
GET |
/v1/learner/mode |
Get learner mode for a scope |
PUT |
/v1/learner/mode |
Set learner mode (MONITOR, ENFORCE, AUTO_PROMOTE_SAFE) |
GET |
/v1/learner/baseline |
Get behavioral baseline signals |
GET |
/v1/learner/candidates |
List policy candidates |
GET |
/v1/learner/candidates/:id |
Get a candidate |
POST |
/v1/learner/candidates/:id/approve |
Approve a candidate (optionally promote to policy) |
POST |
/v1/learner/candidates/:id/reject |
Reject a candidate |
POST |
/v1/learner/generate |
Queue a policy generation job |
| Method | Path | Description |
|---|---|---|
GET |
/healthz |
Liveness probe (always 200) |
GET |
/readyz |
Readiness probe (checks DB, Redis, NATS) |
GET |
/metrics |
Prometheus metrics scrape endpoint |
All errors follow a consistent JSON structure:
{
"error": {
"code": "INVALID_ARGUMENT",
"message": "scope_id is required",
"reason_code": "MISSING_SCOPE",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}The same services are available over gRPC at port 50051 with metadata-based auth (x-api-key) and idempotency (x-idempotency-key). Proto definitions live in proto/agentvault/.
Agent Vault includes a CLI for managing the control plane:
# Set connection info
export AV_API_KEY="av_..."
export AV_SERVER_URL="http://127.0.0.1:50051"
export AV_TENANT_ID="550e8400-..."
# Policy management
agentvault policy list
agentvault policy get <policy-id>
agentvault policy create --name "my-policy" --rules rules.json
agentvault policy activate <policy-id>
agentvault policy validate rules.json # Local validation, no server needed
# Run management
agentvault run list
agentvault run get <run-id>
agentvault run cancel <run-id>
# Incident management
agentvault incident list --severity 3
agentvault incident get <incident-id>
agentvault incident ack <incident-id>
agentvault incident resolve <incident-id> --note "Resolved"
# Approval management
agentvault approval list --status PENDING
agentvault approval approve <approval-id>
agentvault approval deny <approval-id> --reason "Unsafe"
# Witness inspection
agentvault witness inspect <approval-id>
agentvault witness verify <approval-id>
# Learner
agentvault learner status
agentvault learner set-mode MONITOR
agentvault learner candidates
agentvault learner baseline
# Server health
agentvault server health
# JSON output (for scripts and automation)
agentvault --json policy listAll server configuration is via environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
AV_DATABASE_URL |
Yes | — | PostgreSQL connection string |
AV_LISTEN_ADDR |
No | 0.0.0.0:8080 |
HTTP listen address |
AV_GRPC_ADDR |
No | 0.0.0.0:50051 |
gRPC listen address |
AV_METRICS_ADDR |
No | 0.0.0.0:9090 |
Prometheus metrics address |
AV_REDIS_URL |
No | redis://localhost:6379 |
Redis connection (optional; degrades gracefully) |
AV_NATS_URL |
No | nats://localhost:4222 |
NATS connection (optional; degrades gracefully) |
AV_CLICKHOUSE_URL |
No | http://localhost:8123 |
ClickHouse for analytics (optional) |
AV_HTTP_API_ENABLED |
No | true |
Enable REST API alongside gRPC |
AV_MAX_DB_CONNECTIONS |
No | 20 |
PostgreSQL connection pool size |
AV_REQUEST_TIMEOUT_SECS |
No | 30 |
HTTP client timeout for webhooks |
AV_CORS_ORIGINS |
No | * |
Comma-separated allowed origins |
AV_TLS_CERT_PATH |
No | — | TLS certificate path (pair with key) |
AV_TLS_KEY_PATH |
No | — | TLS private key path (pair with cert) |
AV_IDEMPOTENCY_TTL_SECS |
No | 300 |
Idempotency key expiration |
The control plane uses PostgreSQL with 10 tables:
| Table | Purpose |
|---|---|
policies |
Policy metadata (name, slug, tenant scope) |
policy_versions |
Versioned policy content with status lifecycle (draft → active → archived) |
policy_rules |
Individual rules within a policy version |
runs |
Agent run lifecycle (pending → running → completed/failed/cancelled) |
run_steps |
Per-step telemetry and decisions (time-partitioned) |
approvals |
Human-in-the-loop approval requests with witness hashes and TTL |
incidents |
Security incidents with severity, acknowledgement, and resolution workflow |
policy_candidates |
Learner-generated policy candidates awaiting review |
api_keys |
SHA-256 hashed API keys with per-key permissions |
idempotency_keys |
Server-side idempotency replay protection |
Migrations are in migrations/postgres/ and run automatically on server startup.
| Layer | Mechanism |
|---|---|
| Tenant isolation | All data queries are scoped by tenant_id derived from the authenticated API key. Cross-tenant access is impossible by design. |
| RBAC | Per-key permissions: PolicyRead, PolicyWrite, RunRead, RunWrite, ApprovalRead, ApprovalWrite, IncidentRead, IncidentWrite, LearnerRead, LearnerWrite, Admin. |
| API key hashing | Keys are SHA-256 hashed at rest. Only the 8-character prefix is stored in cleartext for identification. |
| TLS | Optional TLS for gRPC via AV_TLS_CERT_PATH / AV_TLS_KEY_PATH. |
| Idempotency | Server-side replay protection prevents duplicate resource creation from retried requests. |
| Witness revalidation | Approvals carry a content hash that is re-checked at execution time, preventing stale approvals from authorizing changed state. |
| Audit log | Append-only, time-partitioned audit trail with immutability triggers (updates and deletes are rejected by the database). |
./start.shdocker compose -f docker/docker-compose.yml up -d postgres clickhouse nats redis
export AV_DATABASE_URL=postgres://agentvault:agentvault@localhost:5432/agentvault
cargo run -p agentvault-serverdocker compose -f docker/docker-compose.yml downdocker compose -f docker/docker-compose.yml down -vagent-vault-control-plane/
├── crates/
│ ├── agentvault-core/ # Core types, policy evaluator, witness guard, run manager
│ ├── agentvault-embed/ # FastEmbed-rs ONNX semantic embeddings
│ ├── agentvault-sentinel/ # Goal-aware progress tracking & intervention engine
│ ├── agentvault-witness/ # State-witness capture, hashing, and revalidation
│ ├── agentvault-learner/ # Behavioral baseline learning & NATS span emission
│ ├── agentvault-server/ # Control plane server (Axum HTTP + Tonic gRPC)
│ ├── agentvault-cli/ # CLI client
│ ├── agentvault-python/ # PyO3 Python bindings
│ └── agentvault-node/ # NAPI-RS Node.js bindings
├── proto/ # Protobuf service definitions (5 services, 37 RPCs)
├── migrations/ # PostgreSQL DDL migrations (auto-run on startup)
├── tests/integration/ # Integration test suite (tenant isolation, parity, etc.)
├── bindings/
│ ├── python/ # Python SDK test suite
│ └── node/ # Node.js SDK test suite
├── docker/
│ ├── docker-compose.yml # Full development stack (5 services)
│ └── .env.example # Environment template
├── adrs/ # Architecture Decision Records
├── docs/assets/ # Architecture diagrams, feature cards, and visual assets
├── Dockerfile # Multi-stage server build (cargo-chef for layer caching)
└── start.sh # One-command dev startup
cd crates/agentvault-python
pip install maturin
maturin develop
# Run tests
cd bindings/python
pytestfrom agentvault.core import PolicyEvaluator, RunContextManager, ReasonCodeRegistry
from agentvault.sentinel import SentinelTracker
from agentvault.witness import WitnessGuard
from agentvault.learner import LearnerClientcd crates/agentvault-node
npm install && npm run build
# Run tests
cd bindings/node
npm testimport { PolicyEvaluator, RunContextManager, WitnessGuard, LearnerClient } from '@agentvault/node';- Logs: Structured JSON via
tracingwithtenant_id,run_id,request_idin every log line - Metrics: Prometheus-compatible at
/metrics— request counts by method/path/status, latencies, DB pool stats, active connections - Tracing: OpenTelemetry-compatible spans via NATS for distributed trace propagation
RUST_LOG=info # Default
RUST_LOG=agentvault_server=debug # Debug server only
RUST_LOG=agentvault_server=trace # Trace everythingContributions are welcome. See CONTRIBUTING.md for setup, style, and the pull request process.
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspaceFor integration tests (requires Docker):
docker compose -f docker/docker-compose.yml up -d
cargo test -p agentvault-integration-tests -- --include-ignoredThis project uses BDFL governance. Interaction is governed by the Contributor Covenant.
MIT License — Copyright (c) 2026 Agent Vault Contributors
If Agent Vault is useful to you, consider giving it a star — it helps others discover the project.












