Compiled Policy
Compiled Policy
Section titled “Compiled Policy”The Compiled Policy is what Policy turns into.
A signed JSON file with everything one specific Sandbox needs to enforce: which network destinations are allowed, which paths are off-limits, which tools the agent can call, what the interpreter is allowed to import, the budget, the compliance tags.
Each Provider reads it and renders the config its target tool expects — Claude Code's
settings.json, OpenShell's YAML profile, Monty's host-function spec.
At a glance
Section titled “At a glance”| Compiled from | a Policy + a Sandbox-spec, by the OPA cascade |
| Consumed by | every Provider the Runtime selected |
| Format | signed JSON (Ed25519); one per Sandbox; read-only mount into the agent |
| Updated when | Policy changes — pushed live to active Sandboxes within seconds, no agent restart |
What it is, concretely
Section titled “What it is, concretely”You write a Policy (in Rego, or via the wizard). The OPA cascade evaluates it for one Sandbox. The output is the Compiled Policy — JSON, signed with TapPass's Ed25519 key, versioned with a monotonically-increasing policy_version.
The Compiled Policy then travels to the host. Each Provider in this Sandbox's Runtime reads the slices it cares about and renders them into the format its tool understands.
Same Compiled Policy → different rendering per Provider. Same shape across all ecosystems; only the rendering differs. That's what makes one Policy govern Claude Code + OpenShell + Monty + the LLM gateway from a single source.
Sometimes called the "Keyring" in older docs and on-disk paths (keyring.json). Same artifact.
What it's made of
Section titled “What it's made of”{ "version": 1017, "issued_at": "2026-04-28T12:00:00Z", "expires_at": "2026-04-28T12:05:00Z",
"identity": { "agent_id": "claude-code@team-eng.acme.com", "tier": "worker", "chain": ["orchestrator@team-eng.acme.com"], "sandbox_id": "sbx_a1b2c3", "runtime_id": "claude-code-laptop-strict" },
"network": { "allow_domains": ["api.anthropic.com", "github.com", "pypi.org"], "deny_categories": ["paste_services", "webhooks", "dyndns"] }, "filesystem": { "workspace": "/workspace", "read_only": ["/app/read-only-mounts"], "deny_paths": ["~/.ssh", "~/.aws", "~/.kube", "/etc/shadow"] }, "tools": { "allow": ["Bash(git:*)", "Bash(uv:*)", "WebFetch(domain:github.com)", "Skill(*)"], "deny": ["Bash(curl:*)", "Bash(ssh:*)", "Bash(rm -rf:*)"] }, "interpreter": { "host_functions": ["http_get", "http_post", "json_parse"], "memory_mb": 128, "cpu_time_ms": 5000, "stack_depth": 256 }, "budget": { "tokens_per_day": 500000, "dollars_per_month": 200, "tool_calls_per_minute": 60 }, "compliance_tags": ["SOC2:CC6.1", "ISO42001:6.2.3", "EU-AI-Act:Article-15"],
"signature": "ed25519:<TapPass key>:<sig>"}Organized by aspect (network / filesystem / tools / interpreter / budget / compliance), not by enforcement layer. The same aspects get enforced at different rings depending on which provider you assign to each ring — that mapping is the Provider's job, not the Compiled Policy's.
How content maps to enforcement
Section titled “How content maps to enforcement”| Compiled Policy aspect | Where it gets enforced |
|---|---|
network.allow_domains, deny_categories | kernel ring (egress allowlist via openshell / nono / gvisor / etc.) — and gateway (LLM API egress) |
filesystem | kernel ring (Landlock / sandbox-exec rules) |
tools.allow, deny | harness ring (settings.json allow/deny) — and MCP broker (per-call check) |
interpreter.host_functions, memory_mb, … | interpreter ring (Monty / V8 / Wasmtime profiles) |
budget.tokens_per_day, dollars_per_month | gateway (per-call budget check) |
compliance_tags | audit metadata (every action signed and tagged with these) |
The same Compiled Policy fans out to different enforcement positions depending on the runtime. A cursor-laptop runtime might enforce tools.allow only at the harness ring (Cursor's config) without an MCP broker, while a claude-code-laptop runtime enforces it at both harness ring (Claude Code's settings.json) and MCP broker (per-call) — defense in depth.
Lifecycle
Section titled “Lifecycle”[derive] Policy cascade evaluates → Compiled Policy (server-side; deterministic; ecosystem-agnostic) ↓[mint tokens] gateway_token + mcp_session_token issued (5-min TTL) ↓[sign] TapPass Ed25519 signature over the whole bundle ↓[push] Signed payload sent to tappass-host over sync channel (push primary; pull on boot/reconnect; reconciler closes the loop) Host validates: signature, monotonic version, sandbox_id, expires_at ↓[receive] Host writes new keyring.json (atomic rename) Mounts read-only into agent's sandbox namespace ↓[providers render] Each provider in this sandbox's runtime renders target-specific config: • claude-code provider → managed-settings.json + ANTHROPIC_BASE_URL • openshell provider → YAML profile + L7 proxy reload • monty provider → host-function manifest • llm-gateway-anthropic provider → token + endpoint • mcp-broker → per-server policy ↓[apply] Each provider's apply path runs (hot-reload / restart / file rewrite per its update model) ↓[agent observes] Canonical case (sdk-direct provider): tappass-agent SDK reads Compiled Policy via inotify; rebinds clients. Other ecosystems: provider's reload mechanism (Claude Code's settings reload, etc.) ↓[expire] Tokens hit TTL within 5 minutes ↓[refresh] Sync push delivers fresh Compiled Policy before tokens expire (or fail-closed: tokens expire → agent stops being able to act)Engines that operate on the Compiled Policy
Section titled “Engines that operate on the Compiled Policy”| Engine | What it does | Status |
|---|---|---|
| Compiler (Compiled Policy emitter) | Writes the Compiled Policy from Policy + Sandbox-spec + Runtime | concept (policy-to-sandbox-config-builder) |
| Live policy push channel | Delivers signed payloads (push) | concept (live-policy-push-channel) |
| Pull endpoint | Agents fetch their Compiled Policy at boot / reconnect | concept (extends sync channel) |
| Reconciler | Compares desired vs. applied version per agent; re-pushes; flags drift | concept (extends sync channel) |
| Providers ★ | Per-target translators that consume the Compiled Policy | concept (provider card) |
| Runtime instantiator | Composes providers for one runtime; invokes each | concept (within host-runtime-cli) |
| Compiled Policy inspector | Operator read-only view | concept (within tappass-cli) |
Quick-starts
Section titled “Quick-starts”There are no quick-start manifests — manifests are always derived from a Policy + Sandbox-spec + Runtime. The quick-starts live one level up:
- Quick-start Policies → Compliance packs (EU AI Act, OWASP LLM, …)
- Quick-start Sandbox-specs → templates per agent shape (
refund-processor,code-reviewer, …) - Quick-start Runtimes → curated v1 set (
claude-code-laptop,librechat-server, …)
The Compiled Policy's shape is fixed (schema-versioned, forward-compat-guaranteed); its contents are the deterministic output of the compiler.
Surfaces
Section titled “Surfaces”| Persona | Surface | What you can do |
|---|---|---|
| Operator | tappass sandbox manifest <id> | inspect any sandbox's Compiled Policy (read+write for ops) |
| Host owner | tappass-host inspect <name> | inspect the local Compiled Policy + which providers rendered each section |
| Agent (canonical sdk-direct path) | Keyring.load() from tappass-agent SDK | read its own Compiled Policy (read-only) |
| Agent (CLI) | tappass-agent status | sanitized view (scopes shown, tokens redacted) |
The Compiled Policy file on the host machine: /var/run/tappass/<sandbox_id>/keyring.json. Owned by the host UID, mode 0440, read-only mount into the agent's namespace.
Related concepts
Section titled “Related concepts”- derived from → Policy + Sandbox-spec + Runtime
- shaped by → Cascade (org/project/agent merged before derivation)
- delivered via → Sync (signed unidirectional push + pull + reconcile)
- rendered by → Providers (per-target translators; one per ring + cross-cutting)
- enforced at → 3 rings (harness / kernel / interpreter) + cross-cutting LLM gateway + MCP broker
- bound to → Sandbox (one Compiled Policy per sandbox)
Authoritative docs
Section titled “Authoritative docs”| Topic | File |
|---|---|
| Strategic frame | TapPass Strategy Memo v3 §07 — Pillar 2: Control Plane |
| Vision | governed-agents-architecture.md — to be re-aligned with strategy memo terminology (rings vs. layers) |
| Compiler | policy-to-sandbox-config-builder |
| Sync delivery | live-policy-push-channel |
| Provider consumers | provider card |
Status snapshot
Section titled “Status snapshot”| Aspect | Status |
|---|---|
| Compiled Policy schema (canonical IR) | concept (Q2-Q3 2026) — JSON schema published with the compiler |
| Compiler (Policy → Compiled Policy) | concept (Q3 2026; critical-path L) |
| Sync delivery (push + pull + reconcile) | concept (Q3 2026) |
| Provider taxonomy | concept (Q3-Q4 2026 per ecosystem demand) |
sdk-direct (canonical agent path) | concept (within tappass-agent SDK) |
Common confusions
Section titled “Common confusions”- Compiled Policy ≠ Policy. Policy is the source (Rego rules); Compiled Policy is the evaluated, signed IR — what providers consume. The Policy produces the Compiled Policy via the OPA cascade.
- Compiled Policy ≠ ecosystem-specific config. The Compiled Policy is the canonical IR. The ecosystem-specific config (Claude Code's settings.json, Cursor's config, OpenShell YAML) is what the Provider renders from the Compiled Policy. Same Compiled Policy → different rendering per provider.
- Compiled Policy organized by aspect, not by ring. Earlier drafts described a "5-layer keyring" with one slice per ring; the strategy memo's correction is that the Compiled Policy carries content (network/fs/tools/interpreter/budget/compliance), and rings are where it gets enforced via different providers. Cleaner separation.
- Read-only on the agent's side. The host process (or provider) writes; the agent reads. The agent has no API to modify the Compiled Policy — that's the non-escalation property.
- Why two names? "Compiled Policy" is the formal Terraform-aligned name. "Keyring" is the operational alias I've been using and that survives in places (file path:
keyring.json; SDK class:Keyring). Same thing.