Skip to main content

Architecture

Technical reference for the Vectimus evaluation pipeline. This document covers component responsibilities, data flow and integration schemas.

TODO: Add architecture diagrams (evaluation pipeline, hook integration flow, server mode topology).

Evaluation flow

AI Agent (tool call) -> vectimus hook (stdin JSON) -> Normaliser -> Cedar PolicyEngine -> Decision
                                                                                             |
                                                                                  Audit Log (JSONL) + Signed Receipt

The default path is entirely local. The vectimus hook --source <tool> command reads JSON from stdin, evaluates via cedarpy and returns a decision via stdout JSON. No server, no network required.

Daemon mode

On the first hook call the daemon auto-starts as a background process, keeping the Cedar policy engine warm in memory. Subsequent evaluations skip the ~200ms Python startup cost. The daemon shuts down after 30 minutes of inactivity.

vectimus daemon status   # check if running
vectimus daemon start    # manual start (auto-starts on first hook call)
vectimus daemon stop     # manual stop
vectimus daemon reload   # flush cached engines (picks up config changes)

Platform differences. On Unix/macOS the daemon listens on a Unix domain socket at /tmp/vectimus-<uid>.sock with filesystem permissions handling authentication. On Windows it uses TCP localhost with an OS-assigned port and a random auth token stored in ~/.vectimus/daemon.json (user-only permissions). Unix daemonizes via double-fork. Windows spawns a detached child process.

The daemon caches policy engines per project path and refreshes them every 5 minutes. CLI commands that change config (rule disable, rule enable, rule enforce, pack enable, pack disable, mcp allow, mcp deny, policy update) automatically send a reload to the daemon so changes take effect immediately. If the daemon is unavailable, hooks fall back to inline evaluation transparently.

Temporary rule disables. The daemon holds an in-memory map of temporary rule disables created with vectimus rule disable <rule> --for <duration>. These are scoped per-project and auto-expire after the specified duration. No config files are written. If the daemon restarts all temp disables are lost (fail-closed). The engine cache is invalidated when temp disables are added, cleared or expire so policy changes take effect on the next evaluation.

When receipts are enabled the daemon runs receipt retention cleanup 30 seconds after the first request per project to avoid unbounded disk growth.

Cryptographic receipts

Every Cedar policy evaluation produces a signed governance receipt. Receipts provide tamper-evident proof of what was evaluated, what decision was made and which policies matched.

Each receipt contains:

  • Receipt ID (included in deny messages for traceability)
  • Event hash (SHA-256 of RFC 8785 canonical JSON of the input event)
  • Decision, reason and matched policy IDs
  • Timestamp and evaluation duration
  • Ed25519 signature over the canonical receipt JSON

Receipts are stored per-project in .vectimus/receipts/YYYY-MM-DD/ as individual JSON files. Signing keys are generated automatically on first use and stored at ~/.vectimus/signing-key.pem.

vectimus verify <receipt-file>     # verify a single receipt signature
vectimus receipts prune --days 30  # delete receipts older than 30 days
vectimus receipts prune --all      # delete all receipts

Receipts are retained for 7 days by default. The daemon runs cleanup automatically after the first request per project. Configure retention via [receipts] retention_days in config, or manage manually with vectimus receipts prune --days 30 or vectimus receipts prune --all.

Receipts are enabled by default. Disable via [receipts] enabled = false in config or VECTIMUS_RECEIPTS_ENABLED=false.

Server mode

The optional HTTP server (vectimus server start) adds centralised policy evaluation for teams. See Running the server for setup and configuration.

Data models

All models use Pydantic v2 BaseModel. See src/vectimus/core/models.py for the full definitions.

VectimusEvent is the normalised event that Cedar policies evaluate. Key fields:

  • source (SourceInfo) — where the event came from (tool name, version, session)
  • identity (IdentityInfo) — who triggered it (principal, persona, groups)
  • action (ActionInfo) — what is being attempted (action_type, command, file_path, file_content, script_content, etc.)
  • context (ContextInfo) — environmental context (repo, branch, cwd)

Decision is the governance result:

  • decision — “allow”, “deny” or “escalate”
  • reason — human-readable explanation (mandatory for deny/escalate)
  • suggested_alternative — what the agent should try instead (mandatory for deny)
  • matched_policy_ids — which policies triggered

The verdict depends on the matched policy’s @enforcement annotation:

  • deny (default) — hard block, the agent cannot proceed
  • escalate — blocks with a descriptive [escalate] message in local mode. In server mode the server can route to external approval workflows (PagerDuty, Slack) before returning allow/deny
  • observe — logs the match but returns allow, useful for testing new policies before enforcing them

Config-based overrides (via [rules.enforcement] in config.toml) take precedence over annotations. Global observe mode overrides everything.

AuditRecord pairs a VectimusEvent with its Decision for the audit log.

Action types

Normalised across all tools:

Action typeExamples
shell_commandBash, terminal, shell execution
file_writeWrite, Edit, MultiEdit, file creation
file_readRead, Grep, Glob, file access
web_requestWebFetch, curl, HTTP calls
mcp_toolAny MCP server tool invocation
package_operationnpm, pip, cargo, yarn
git_operationgit push, commit, branch operations
infrastructureterraform, kubectl, docker, cloud CLI
agent_spawnTask, subagent creation

Shell commands are further classified: commands starting with terraform/kubectl/docker map to infrastructure, npm/pip/cargo/yarn to package_operation, git to git_operation.

Shell commands that perform file I/O are reclassified to file_read or file_write with the target file path extracted for Cedar policy matching. This covers output redirects (echo x > file), tee, sed -i, cp/mv, dd of= and inline scripts (python3 -c "open('f','w').write('x')", node -e, ruby -e, perl -e). This prevents agents from bypassing file policies by wrapping file operations in shell commands.

Content inspection (double evaluation)

When an agent writes a file or executes a script, Vectimus runs a second Cedar evaluation pass treating the content as a shell command. Every existing shell_command policy automatically applies to file writes and script execution, closing the write-then-execute bypass where an agent writes a malicious script in one step and runs it in the next.

How it works:

  1. The normaliser extracts file_content from Write/Edit tool payloads and resolves script_content by reading the target file when the command matches a script execution pattern (bash deploy.sh, python setup.py, ./run.sh).
  2. If the primary evaluation allows the action and content is present, the evaluator splits the content into lines and evaluates each non-empty, non-comment line against all shell_command policies.
  3. If any line triggers a deny, the entire action is blocked. The deny reason includes which policy matched and whether it was caught via file content or script content inspection.

Content is limited to 5000 lines by default (configurable via VECTIMUS_CONTENT_MAX_LINES or [limits] content_inspection_max_lines in config). Line-based limits prevent the padding bypass where an attacker fills a file with harmless comments to push malicious commands past a byte boundary. Scripts are read line by line to avoid loading entire large files into memory. Unreadable files fail open (the primary evaluation result stands). No new policy syntax is needed. Your existing rules cover both direct commands and content automatically.

Normaliser input schemas

The normaliser accepts tool-specific JSON and produces VectimusEvent objects. New tools are added by registering a normaliser function.

Claude Code

{
  "tool_name": "Bash",
  "tool_input": { "command": "rm -rf /tmp/build" },
  "tool_use_id": "uuid",
  "session_id": "uuid",
  "cwd": "/home/user/project",
  "hook_event_name": "PreToolUse"
}

Tool name mapping:

Tool nameAction type
Bashshell_command
Write, Edit, MultiEditfile_write
Read, Grep, Globfile_read
WebFetch, WebSearchweb_request
Taskagent_spawn
mcp__*mcp_tool

Cursor

{
  "conversation_id": "uuid",
  "generation_id": "uuid",
  "command": "rm -rf /tmp/build",
  "cwd": "/home/user/project",
  "hook_event_name": "beforeShellExecution",
  "workspace_roots": ["/home/user/project"]
}

Event mapping: beforeShellExecution -> shell_command, beforeMCPExecution -> mcp_tool, beforeReadFile -> file_read, afterFileEdit -> file_write.

GitHub Copilot / VS Code

{
  "timestamp": "2026-03-08T14:30:00.000Z",
  "cwd": "/home/user/project",
  "sessionId": "uuid",
  "hookEventName": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "rm -rf /tmp/build" }
}

Gemini CLI

{
  "tool_name": "run_shell_command",
  "tool_input": { "command": "rm -rf /tmp/build" },
  "hook_event_name": "BeforeTool",
  "session_id": "uuid",
  "cwd": "/home/user/project"
}

Tool name mapping:

Tool nameAction type
run_shell_commandshell_command
read_filefile_read
write_file, edit_filefile_write
list_directoryfile_read
mcp__*mcp_tool

Claude Agent SDK

The Claude Agent SDK shares the exact same hook mechanism and payload format as Claude Code. No separate normaliser is needed. Hooks defined in .claude/settings.json fire identically for both tools.

Google ADK

Google ADK uses a native Python integration rather than stdin/stdout hooks. See Google ADK integration for the plugin and callback APIs.

The VectimusADKPlugin maps ADK tool names to Vectimus action types:

ADK tool nameAction type
bash, shell, terminalshell_command
file_writefile_write
file_readfile_read
google_search, web_searchweb_request
server__tool (MCP pattern)mcp_tool

LangGraph / LangChain

LangGraph uses a Python middleware integration. See LangGraph integration for the middleware and MCP interceptor APIs.

Cedar schema

The Cedar schema defines entity types, actions and context shapes. See src/vectimus/core/schemas.py for the full definition.

Entity types: User, Agent, Tool.

Each action type (shell_command, file_write, etc.) applies to [User, Agent] principals and Tool resources with a context containing a parameters record.

Cedar policy conventions

Every policy rule must have:

  • @id("vectimus-<domain>-NNN") — unique identifier (e.g. vectimus-infra-001, vectimus-exfil-001)
  • @description("...") — human-readable explanation
  • @incident("...") — real-world incident reference (where applicable)
  • @controls("...") — compliance controls it satisfies (where applicable)

See Writing policies for the full guide.

Server endpoints (opt-in)

Activated via vectimus server start. Not part of the default MVP flow.

MethodPathPurpose
POST/evaluateEvaluate a tool event against policies
GET/policiesList loaded policies with metadata
GET/healthServer status, policy count, uptime
GET/eventsSSE stream of real-time evaluation events

The /evaluate endpoint accepts an X-Vectimus-Source header to identify the source tool. For Claude Code HTTP hooks, the response includes hookSpecificOutput with permissionDecision and permissionDecisionReason.

Configuration

Locations (in order of precedence):

  1. Environment variables (VECTIMUS_PERSONA, VECTIMUS_CONTENT_MAX_LINES, etc.)
  2. .vectimus/config.toml (project-local, version-controllable)
  3. ~/.vectimus/config.toml (user-level global)
  4. Hardcoded defaults
[server]
url = "https://vectimus.internal.example.com"  # omit for local-only

[identity]
persona = "default"
groups = ["engineering", "platform"]
identity_type = "human"

[limits]
content_inspection_max_lines = 5000
excessive_turns_threshold = 50
session_spawn_limit = 10
session_message_limit = 50
session_ttl_seconds = 3600
git_timeout_seconds = 5

[audit]
max_file_size_mb = 100
log_dir = "~/.vectimus"

[rules]
disabled = []

The .vectimus/ directory in the project root is protected by Cedar policy vectimus-fileint-005, preventing agents from modifying governance config.