Operator authority
Cross-agent pre-edit enforcement for every coding agent on this VPS.
What this page is
Section titled “What this page is”A wiki orientation page describing the operator-authority system — the program that converts Steelmoth’s hard rules, architecture invariants, and protected paths from documentation into mechanical enforcement. See PRD 0042 / RFC 0079 / ADRs 0110..0118 for the full design; this page is the repo-orientation summary.
The problem
Section titled “The problem”Steelmoth has 9+ HARD RULES in CLAUDE.md, an architecture-invariants
file, engineering standards, system-wiki orientation pages, threat
models, and per-agent rule files. These describe what coding agents
on this VPS — Claude Code, Codex, Morpheus, plus future
dashboard-coding agents — must and must not do. Until 2026-05-23 the
enforcement posture was ~40% pre-action, ~40% documentation, ~20%
post-fact detection — and a real incident on that date (an agent
materialised a local secret cache at /etc/agent-vault/* in
violation of INV-024, HARD RULE 10, and ADR 0105) showed the gap.
The shape
Section titled “The shape”One canonical policy + one shared gate + per-agent shims + kernel floor + GitHub enforcement + dashboard control plane + CI invariant tests.
engineering-standards/AGENT_POLICY.yaml ← single source of truth │ ▼ steelmoth_runtime.operator_authority.gate ← one program, one API │ ┌───────────┬───────┴────────┬──────────┬──────────┐ ▼ ▼ ▼ ▼ ▼Claude Code Codex shim Morpheus Moth Dashboardhooks + (no bypass, scope guard read-only approvalLandlock Landlock consumes (no write gatefloor floor) the same path) manifest ▲ │ GitHub branch protection + CODEOWNERSWhere things live
Section titled “Where things live”- Policy:
engineering-standards/AGENT_POLICY.yaml(machine-readable) andengineering-standards/PROTECTED_PATHS.md(human-readable, derived from / cross-checked against the YAML).- In-container the runtime process runs from cwd
/app(not the repo root), soload_policy()’s cwd walk-up cannot find the policy. Thesteelmoth-apiservice therefore setsSTEELMOTH_AGENT_POLICY_PATH=/app/repo/engineering-standards/AGENT_POLICY.yaml(the baked repo) so policy loads resolve to the canonical file viapolicy.py:_resolve_policy_path. Without it the self-serve scope flow (decide_task_plan) fails closed totask-approvedonly. Set incompose.steelmoth.private.yml.
- In-container the runtime process runs from cwd
- Gate:
steelmoth-runtime/src/steelmoth_runtime/operator_authority/Python package —policy.py,manifest.py,landlock.py,gate.py,approval.py,violations.py,posthoc.py. - Shims:
~/.local/bin/codex(host) +/usr/local/bin/codex(steelmoth-api container) +~/.local/bin/claude(host). Source-of-truth scripts atscripts/operator-authority-codex-shim.pyandscripts/operator-authority-claude-code-shim.py. - Manifests:
/var/lib/operator-authority/scopes/<worktree-hash>.json— per-worktree scope grants. Operator-only writes. - Violation log:
/var/log/operator-authority/violations.jsonl— append-only JSONL. Per-file Landlock allow-rule for the gate to write its own audit. - Dashboard control plane:
dashboard/app/agent-authority/pagesroutes_operator_authority.pybackend +operator_authority.*Postgres schema. The sidebar section (its own top-level Agent Authority category) is led by the Overview page (dashboard/app/agent-authority/overview/page.tsx, adminOnly, added 2026-07-25): an at-a-glance all-time stats landing backed byGET /v1/operator-authority/stats(require_admin_read) — realCOUNT(*)aggregates over the gate tables plus per-day/per-month trend series (repository.compute_stats()), never a bounded-window extrapolation, with approvals counted in operator-queue scope so the numbers match the Protected Paths page; a/agent-authority/overview/detailssubpage carries the full breakdown.
Coding agents in scope
Section titled “Coding agents in scope”- Claude Code — Hooks at
~/.claude/settings.jsonPreToolUse for Write / Edit / Bash. Landlock applied via the Python shim at~/.local/bin/claude. - Codex — Native command-approval rules at
.codex/rules/default.rules(restored to active by removing--dangerously-bypass-approvals-and-sandbox). Landlock applied via the Python shim that wraps the real Codex binary. - Morpheus — Existing
morpheus_scope_guard.pymigrates to read fromAGENT_POLICY.yamlvia a back-compat shim. Landlock applied at the morpheus-drain-daemon entrypoint to cover terminal-bridge runs. - GLM + Kimi — the
glm(~/.glm) andkimi(~/.kimi) terminal launchers each carry the same PreToolUse gate hooks in their ownsettings.json. Since 2026-07-24 theirsettings.json/settings.local.json/hooks/**are protected paths inAGENT_POLICY.yaml(mirroring the.claudeset, categoryagent-policy-config), closing the self-disable gap where either agent could edit its own settings and switch its own gate off. - Future dashboard-coding agents — The Agent Authority dashboard
surface (its own top-level sidebar category, led by Overview) and the
operator_authority.sessions.agent_kindenum are designed to accommodate additional agents without rework. Add anagent_kindenum value, register a shim or hook, consume the same YAML.
Runtime users of the durable approval queue
Section titled “Runtime users of the durable approval queue”The operator_authority.approval_requests queue also serves two
runtime (non-coding-agent) askers. Both kinds are owner-scoped
(migration 0132): the create route stamps owner_id from the
gateway’s own canonical email, the owner-facing read
(GET /v1/operator-authority/approvals/mine,
repository.list_owner_browser_action_approvals) returns only the
caller’s own rows of these kinds, and the decide route authorizes
the row’s owner (or an admin) — kind-agnostic.
browser_action(migration 0068; ADR 0197) — the browser agent pauses before a consequential page action and files a durable ask.background_action(migration 0135; ADR 0204) — a background run (PRD 0058 / RFC 0116) that reaches a mutating step mid-run. The chat chip’s 5-minute TTL cannot gate a minutes-long job, so the chip-emission path reroutes: the run canonicalizes and mints its approval token through the same per-pair emitter the chat uses (long TTL — ask timeout + slack), files the durable request, flips its ledger row toawaiting_approval(claim lease extended past the approval window so the restart reaper leaves it alone), and blocks its worker thread on the outcome. One Telegram DM per ask, from the request’son_createdcallback. Approve → the token redeems through the exactroutes_write_actionexecute + audit path a chat approval uses and the run returns torunning; deny / timeout / unreachable → the token is cancelled and the run fails with an honest error, never a mutation. A run orphaned mid-wait by a restart is reclaimed once the extended lease lapses: the reaper resolves the stale request toexpiredand cancels the run’s still-issued tokens (keyed by itsbgrun_<run_id>message id).
No chat chip hint is emitted or persisted for a background ask; the approvals panel labels the two kinds (“Website sign-in” / “Background task”) on the owner’s Governance page.
Agents NOT in scope
Section titled “Agents NOT in scope”- Moth (in-product chat assistant) — READ-only for code by
design. Phase 6 test scans
steelmoth-runtime/src/steelmoth_runtime/moth/**for filesystem writes outside the YAML’smoth_allowed_writesallowlist; CI fails the build on any new write target. The/self-code/*sidebar routes stay reachable under Advanced per HARD RULE 7-bis but are observability for any external self-edit runner (Morpheus today; not Moth). - Dispatch Team workers (Lloyd) — removed 2026-06-16 (dispatch subsystem deleted in #354/#363/#367; ADR 0036 superseded). Historically out of scope: they operated on tenant data and never edited repo source.
Authority order (when these rules apply)
Section titled “Authority order (when these rules apply)”- Current operator instruction in this session.
- Safety / security invariants: INV-024, HARD RULES 1/2/3/4/6/7-bis/8/9/10/11.
- Engineering standards: branch-per-phase, plan-mode before push, pre-phase bundles ship as one PR.
- Live evidence: source code, schemas, tests, scripts, runtime state.
- Wiki prose (including this page).
Common operations
Section titled “Common operations”Granting a scope token to an agent for a task
Section titled “Granting a scope token to an agent for a task”Today (pre-Phase-8): edit the per-worktree scope manifest by hand
at /var/lib/operator-authority/scopes/<worktree-hash>.json. The
hash is sha256(realpath(toplevel))[:12]. Schema:
{ "task_id": "phaseN-feature-name", "agent_identity": "claude-code|codex|morpheus", "worktree_path": "/home/mareekkk/steelmoth/.claude/worktrees/feature-name", "allowed_paths": ["src/steelmoth_runtime/feature_name/**"], "scope_tokens": ["compose"], "expires_at": "2026-05-24T18:00:00+08:00", "issued_by": "mark@canarybuilds.com"}Post-Phase-8: Agent Authority → Scope Manifests panel in the dashboard.
Approving a mid-task scope widen
Section titled “Approving a mid-task scope widen”Today (pre-Phase-8): the agent reports the deny; operator edits the scope file; agent retries.
Post-Phase-8: the agent’s gate calls the dashboard’s approval API and exits 2; operator sees the request in Approval Queue; click approve; gate resumes; action proceeds.
Investigating a blocked attempt
Section titled “Investigating a blocked attempt”tail -f /var/log/operator-authority/violations.jsonl for live view.
Post-Phase-8: Agent Authority → Violation Log panel.
Adding a new protected path
Section titled “Adding a new protected path”Edit AGENT_POLICY.yaml’s protected_paths: list with the glob +
category + required_scope + reason. The matching prose entry in
PROTECTED_PATHS.md should also be added in the same PR (CI check
verifies consistency). The PR is operator-authored or requires
#scope:meta per the existing Morpheus scope guard.
Adding a new denied command pattern
Section titled “Adding a new denied command pattern”Edit AGENT_POLICY.yaml’s denied_commands: list with the regex +
reason + optional exception_path + optional required_scope.
Adding a new architecture invariant with enforcement
Section titled “Adding a new architecture invariant with enforcement”Edit engineering-standards/ARCHITECTURE_INVARIANTS.md: write the
prose for the new INV-NNN and include a ### enforcement block
per ADR 0118 format. The Phase 9 fitness-test runner picks it up
on the next CI run.
Failure modes
Section titled “Failure modes”See docs/operations/FAILURE_MODES.md rows 303–311 for the
operator-authority-specific failures: YAML parse failure, manifest
missing, Landlock install fails, dashboard unreachable during
approval, post-hoc sweep reverts legitimate change, PATH ordering
bypass, CI check failing for unrelated reason, fitness-test regex
bug, new agent without schema integration.
Threat model
Section titled “Threat model”See docs/operations/THREATS.md “Operator Authority” section for
STRIDE coverage: agent self-escalation via scope-manifest forgery,
shim bypass via absolute path, YAML tampering, gate module
replacement, Bash-pattern bypass, audit log integrity, denial of
service via flooding.
Related design docs
Section titled “Related design docs”docs/prds/0042-operator-authority.md— PRD (program-level)docs/rfcs/0079-operator-authority-architecture.md— RFC (system design)docs/adr/0110-canonical-agent-policy-yaml.mddocs/adr/0111-shared-preflight-gate.mddocs/adr/0112-landlock-floor-codex-and-claude-code.mddocs/adr/0113-codex-bypass-removal.mddocs/adr/0114-morpheus-migration-to-shared-policy.mddocs/adr/0115-moth-read-only-code.mddocs/adr/0116-github-enforcement.mddocs/adr/0117-dashboard-control-flip.mddocs/adr/0118-invariant-fitness-tests.md
Phasing
Section titled “Phasing”Bundle (this PR) → Phases 1..9 ship in subsequent PRs per the branch-per-phase workflow. See PRD §Phasing for the per-phase branch names and dependencies.

