Policy Bundles

Bundle structure

A policy bundle is a gzip-compressed tar archive produced by autonomy policy build.

Archive contents:

bundle.tar.gz
├── manifest.json     ← bundle metadata
└── *.rego            ← Open Policy Agent source files (alphabetical order)

manifest.json fields:

Field

Type

Required

Description

policy_bundle_version

semver string

Yes

Bundle version, e.g. "1.2.0"

required_runtime_version

semver range

No

Constraint on runtime binary version

bundle_hash

sha256:<hex>

Set by builder

Hash of sorted filenames + contents

name

string

No

Human-readable identifier

created_at

RFC3339

Set by builder

Build timestamp (UTC)

Build a bundle:

autonomy policy build \
  --in     ./policies \
  --out    bundle.tar.gz \
  --version 1.2.0 \
  --name   my-policy \
  --runtime-version ">=0.1.0 <2.0.0"

Expected output:

policy build: OK
  version:         1.2.0
  hash:            sha256:cd1525064...
  runtime-version: >=0.1.0 <2.0.0
  output:          bundle.tar.gz

Runtime compatibility check

required_runtime_version is a space-separated list of semver constraints:

">=0.1.0 <2.0.0"   # runtime must satisfy both constraints
">=1.0.0"           # runtime must be at least 1.0.0
""                  # no constraint (default) — always compatible

Supported operators: >=, >, <=, <, =.

The current runtime version is 0.1.0.

Incompatible bundle behavior:

autonomy policy load --bundle incompatible.tar.gz
policy load: REJECTED — bundle requires runtime >=99.0.0, have 0.1.0

The load is rejected. Both the current and lkg slots remain unchanged. A policy.bundle.rejected telemetry event is emitted. The currently active policy continues to serve requests.


Loading and the managed cache

policy load installs a bundle into the managed cache at $XDG_CACHE_HOME/autonomyops/policy/managed (override with --manager-dir):

autonomy policy load \
  --bundle      bundle.tar.gz \
  --manager-dir /opt/autonomy/policy
policy load: OK — version=1.2.0 hash=sha256:c...

The managed cache layout:

/opt/autonomy/policy/
├── current/
│   ├── bundle.tar.gz
│   └── meta.json       ← version, digest, loaded_at, runtime_constraint
└── lkg/
    ├── bundle.tar.gz
    └── meta.json

The runtime reads from this directory at startup via --policy-dir.


LKG (last-known-good)

Each policy load promotes the previous current to lkg before installing the new bundle.

Before load:                 After load:
  current → v1.0.0            current → v1.1.0
  lkg     → (none)            lkg     → v1.0.0
Before second load:          After second load:
  current → v1.1.0            current → v1.2.0
  lkg     → v1.0.0            lkg     → v1.1.0

LKG holds exactly one bundle (the one immediately before current). Older versions are not retained.

When LKG is used: LKG is a recovery reference. The runtime does not automatically fall back to LKG on policy evaluation errors; the operator must manually promote LKG to current by loading the LKG bundle explicitly.

To inspect both slots:

autonomy policy status --manager-dir /opt/autonomy/policy
Current: version=1.2.0 digest=sha256:cd1525064... loaded=2026-02-27T20:00:00Z
LKG:     version=1.1.0 digest=sha256:ab3f8e112... loaded=2026-02-27T19:00:00Z

Stale bundle detection

A bundle that has not been updated in more than 30 days is flagged as stale.

Current: version=1.0.0 digest=sha256:... loaded=2025-12-01T00:00:00Z  [STALE]

A policy.bundle.stale telemetry event is emitted when the status command detects staleness. This does not affect runtime behavior; it is an advisory only.


Policy evaluation (OPA/Rego)

The evaluator compiles bundle Rego modules at load and evaluates per request using OPA with query data.autonomy.allow.

Input shape:

{
  "kind": "tool.echo",
  "params": { "message": "hello" }
}

Decision rule:

  • allow == true => request allowed

  • missing/false/invalid result => request denied (fail-closed)

Example Rego deny policy:

package autonomy

default allow := false

allow if {
  input.kind == "tool.echo"
}

Attaching a bundle to an OCI image

Policy bundles travel alongside agent images as OCI sidecar artifacts. See oci.md for the attachment workflow.

After attaching, retrieve and load the bundle in one step:

autonomy oci pull-policy \
  --image localhost:5000/agent:v1 \
  --out   /tmp/bundle.tar.gz

autonomy policy load --bundle /tmp/bundle.tar.gz

Legacy formats (deprecated)

These formats are not used by the demo pipeline or CI acceptance gates. They are retained for backward compatibility only and will emit a deprecation warning when used.

manifest.toml (legacy directory format)

Early versions supported loading a policy bundle directly from a directory containing manifest.toml:

policies/
├── manifest.toml   ← legacy metadata (version, name, policy_ref)
└── *.rego

manifest.toml schema:

Field

Description

version

Bundle version string (e.g. "v1.0.0")

name

Human-readable identifier

policy_ref

OCI reference the bundle was pushed to

created_at

RFC3339 timestamp

This path is still reachable via autonomy policy inspect --dir <dir> for development convenience. A deprecation warning is printed to stderr on every use:

WARNING [policy]: loading bundle from manifest.toml (legacy/deprecated format).
Migrate to canonical .tar.gz: autonomy policy build --in <dir> --out bundle.tar.gz --version <version>

Migration: Replace manifest.toml with a canonical .tar.gz bundle using autonomy policy build. The policy_bundle_version and name fields map directly to --version and --name.

Raw directory loading (policy inspect --dir)

The --dir flag on policy inspect accepts a directory containing either manifest.json (preferred) or manifest.toml (legacy fallback). This flag is deprecated; use --bundle with a .tar.gz archive instead.


Updating policy without downtime

The runtime reads the active bundle at startup. To apply a new bundle to a running instance:

  1. Load the new bundle: autonomy policy load --bundle new.tar.gz

  2. Restart the runtime (sends SIGTERM, waits for in-flight requests to complete)

There is no hot-reload mechanism in 0.1.0.