Skip to content

Scope manifests

The per-task grant that says which tokens an agent currently holds — and the reason the default answer is “no”.

A protected path only opens for an agent that holds the right token. A scope manifest is where those tokens live: a small operator-issued file, one per work area, that lists exactly what the current task is allowed to do. The gate reads it on every write.

Each WIP worktree gets its own manifest, stored at /var/lib/operator-authority/scopes/<worktree-hash>.json. The hash binds the grant to a specific directory: it is the first twelve hex characters of the SHA-256 of the worktree’s resolved absolute path. Move to a different worktree and you get a different hash, hence a different (or absent) manifest. A grant for one task’s directory cannot be reused from another.

Field Meaning
scope_tokens The tokens this task holds (e.g. task-approved, migration).
allowed_paths Specific path globs granted outright, regardless of the policy.
expires_at When the grant lapses. After this, it counts as no grant at all.
task_id The task this grant belongs to.
agent_identity Which agent the grant was issued for.
worktree_path The directory the grant is bound to.
issued_by The operator who granted it.

The crucial property: an agent can read its manifest but cannot write it. The scopes directory is operator-owned (restrictive permissions, an operator-authority group); grants are issued through the runtime’s admin surface — the dashboard’s Agent Authority view — never through the gate or by the agent itself. An agent therefore cannot grant itself a token. That one-way relationship is what makes the token meaningful: it can only come from the operator.

The usual path: the agent hits a protected action, the gate routes it to the approval queue, the operator approves a plan, and that approval writes the task-approved token into this worktree’s manifest — atomically with the approval record. On the agent’s next attempt, the gate reads the now-present token and allows the write. The manifest is the durable memory of the operator’s “yes,” with a built-in expiry so a yes doesn’t last forever.

There is a subtlety the design hides: the gate reads files, but the dashboard and runtime write a Postgres table (operator_authority.scope_manifests). For a long time nothing connected the two — the runtime container had no mount of the host scopes directory — so a grant issued in the dashboard never actually reached the gate; only manifests hand-written on the host did.

A small host daemon now bridges them (operator-authority-scope-projector, a systemd service running as the scopes-dir owner). Each cycle it does a read-only SELECT of the scope_manifests table and materialises every row into the gate’s JSON file, atomically, within a few seconds. It only reaps files it projected (marked _projected_from_db), so hand-written manifests are left untouched. With it in place, the dashboard’s Agent Authority → Scope Manifests form (issue / edit / revoke a grant with a TTL) is finally authoritative for the gate: write a grant, and the agent’s next attempt sees it within ~5 seconds. (Added 2026-06-19.)