v3/docs/adr/ADR-324-agentic-policy-engine-codex-swarm.md
Status: Accepted
Date: 2026-07-28
Implementation: Complete on feat/adr-324-agentic-policy-engine
Related: ADR-053 (AgentDB), ADR-101 (federated claims), ADR-131 (tool-output guardrail), ADR-144 (authorization propagation), ADR-145 (memory governance), ADR-176 (self-benchmarking loop), ADR-322/A/B/C (MetaHarness proposer, promotion transaction, receipts)
Ruflo already contains useful but disconnected controls:
AgentAuthorizationPropagator, whose monotonic AuthScope was not wired into production dispatch.AGENTS.md, but whose concurrent workers could share one checkout and whose generated defaults did not define a single authorization authority.No component answered the general question:
May this identity perform this action on this resource, with this evidence, authority, cost, concurrency, and delegation chain, right now?
The missing authority also made concurrency unsafe. A coordinator could record work, but it could not prevent child agents or MetaHarness proposers from widening tools, network access, namespaces, spend, or promotion rights.
Ruflo adopts @claude-flow/security's AgenticPolicyEngine as the local authorization authority.
identity + action + resource + evidence + capability envelope
+ approval + budget + policy version
|
allow | deny | require_approval
|
hash-chained decision receipt
The engine is deterministic and side-effect free except for its state transaction. Adapters translate existing subsystem concepts into this request model. Existing specialized controls remain responsible for their domain mechanics; they no longer act as independent authorization authorities.
| Mode | Logical decision | Runtime behavior |
|---|---|---|
legacy | Existing behavior/default allow | allow and issue receipt |
observe | Evaluate default-deny policy | allow, record would-deny |
enforce | Evaluate default-deny policy | enforce allow/deny/approval |
Existing installations are auto-migrated to a versioned policy state in legacy mode. Migration is additive and detects legacy AgentDB/RVF locations and strict-mode flags without rewriting memory rows, indexes, receipts, namespaces, controller state, ReasoningBank, SkillLibrary, or HNSW data.
New policy fields default to absent/unknown. ANN index refreshes must never replace authoritative AgentDB rows. CLI/MCP command names and existing namespaces remain valid.
No automatic update may:
AGENTS.md text or unknown TOML keys;Generated configuration may raise a persisted mode (legacy → observe →
enforce) but cannot lower it. A downgrade is an explicit, interactive local
administrator action.
Rules match action/resource patterns, principals, identity types, roles, environments, provenance, and hard constraints. Evaluation order is:
legacy, observe, enforce).Unmatched actions allow only in legacy; observe records default-deny but does not block; enforce blocks.
An envelope may constrain actions, resources, tools, MCP servers, read/write namespaces, environments, network/destructive access, cost, tokens, concurrency, expiry, and delegation depth.
Every child envelope must be a monotonic reduction. A child cannot:
Envelope violations are always enforced, including in legacy and observe;
those modes apply to policy-rule rollout, not delegated authority boundaries.
Agents and optimizers cannot approve themselves. Approvals are scoped, expiring, revocable, and limited-use.
Approval creation requires a deployment-supplied authenticated issuer
verifier. An arbitrary issuedBy string, environment allowlist, or local TTY
is not an authority credential. The default local CLI therefore cannot issue
approvals. The unregistered administrative MCP adapter derives the issuer from
authenticated user context and cannot accept a caller-supplied issuer.
Budgets match principal, action, and resource and limit USD/tokens per fixed window. Authorization and usage update occur under one cross-process policy-state lock. A denied action does not consume budget. Domain-specific budget providers may reserve additional resources but cannot weaken this ceiling.
Cost, token, and concurrency fields are declared reservations supplied by a trusted adapter, not measurements from a provider invoice. A rule or budget that constrains one of these dimensions fails closed when its value is absent. Adapters must reserve before execution and reconcile separately; provider-side hard caps remain the final ceiling against a compromised or under-reporting adapter.
Every decision binds:
Canonical JSON sorts object keys and rejects non-finite numbers. SHA-256 content identifiers and a hash chain make alteration evident. A configured local key adds an HMAC authentication tag. Cross-install/federated policy bundles continue to require their existing Ed25519 trust model; a local HMAC is not represented as third-party attestation.
When a signing key is configured, unsigned receipts are invalid. Conversely, a
signed ledger cannot be declared valid without the matching verification key.
Signed policy evidence uses a trusted key selected by keyId from
CLAUDE_FLOW_POLICY_EVIDENCE_KEYS; its HMAC binds id, provenance, attestor,
observation time, content hash, and key id. The legacy caller-authored
signed: true field is retained for transport compatibility but confers no
authority.
The first authoritative surface is the CLI MCP dispatcher: policy evaluates before tool handlers can produce side effects. The standalone MCP registry exposes the same middleware contract and can require an authorizer at construction; required mode cannot later remove its authorizer. Deployments must opt into that required mode. This ADR does not claim that every independently embedded MCP server or native Codex/Claude action is automatically wired.
Learning, metrics, and pattern storage remain fail-open. Policy enforcement is synchronous and authoritative:
ADR-323 provenance values are valid evidence types:
user_claim, agent_output, system_observation, tool_result, unknown.
Policy adapters preserve all previous AgentDB capabilities and add namespace decisions without changing retrieval algorithms. tool_result and system_observation may be required for factual or production actions. A user claim is not silently upgraded into an observation.
Ruflo is the ledger/coordinator/policy decision point. Codex workers execute.
Read-only workers may share a checkout. Writers may not. A designated integration agent owns shared manifests and lockfiles. Dependency cycles, missing dependencies, shared writer paths, non-zero worker exits, and dependency failures are errors rather than implicit success.
[swarm.automation] defaults:
enabled = false
max_concurrent = 4
max_writers = 2
worktree_isolation = true
dependency_failure = "cancel"
agent_timeout_seconds = 1800
max_output_bytes = 1048576
Worktree cleanup removes only registry-owned, clean worktrees. Dirty worktrees remain for recovery. No force removal or broad path deletion is permitted.
The worker preflight, sanitized environment, declared envelope, and Codex sandbox are workflow controls, not an operating-system security boundary. A locally compromised child process can invoke tools outside Ruflo unless the host also applies container/VM, filesystem, network, and provider controls. For Ruflo MCP calls made from a linked worktree, the authoritative policy root is derived from Git's canonical common directory, so the worker cannot accidentally initialize an independent legacy policy state.
MetaHarness/Darwin proposes; Ruflo policy and ADR-322 dispose.
promote or materialize capability.metaharness.flywheel.run and metaharness.candidate.promote are distinct policy actions.Legacy v1 Flywheel receipts remain verifiable byte-for-byte. ADR-324 receipts are additional authorization evidence; they do not rewrite the Flywheel ledger.
The design addresses:
It does not claim that a local HMAC protects against compromise of the host or signing key. Remote trust requires the established Ed25519 attestation paths.
Positive:
Costs:
The normative implementation is split across existing package boundaries:
| Concern | Implementation |
|---|---|
| Rules, envelopes, approvals, budgets, canonical receipts | @claude-flow/security/src/policy |
| Cross-process transaction, migration, CLI/MCP adapter | @claude-flow/cli/src/services/policy-runtime.ts |
| Administrator CLI | ruflo policy status/init/evaluate/rule/budget/revoke/audit/verify; approval issuance requires an authenticated adapter |
| Policy MCP tools | Remote default: policy_evaluate, policy_status; administrative handlers exist for authenticated deployments but are not registered by default |
| CLI MCP enforcement | @claude-flow/cli/src/mcp-client.ts |
| Standalone MCP enforcement contract | ToolRegistry.setAuthorizer / MCPServer.setToolAuthorizer |
| Bounded MetaHarness evaluation | bounded-worker-pool.ts, harness-flywheel.ts, harness-flywheel-runtime.ts |
| Codex automation | bounded dual-mode orchestrator plus the worktree coordinator |
| Upgrade behavior | additive Codex settings migration and CLI policy-state auto-migration |
The policy state lives at .claude-flow/policy/state.json. Mutations acquire
.claude-flow/policy/state.lock using exclusive creation and replace state by
atomic rename. Decision, approval consumption, budget usage, and receipt append
therefore commit as one transaction. A stale lock may be recovered after 30
seconds; lock acquisition otherwise fails after five seconds.
When enforcement is first enabled, an HMAC key and state authentication record
are stored outside the workspace under
~/.config/ruflo/policy-trust/<project-hash>/, where the project hash uses
the canonical real path and cannot be redirected by a worker environment
variable. Subsequent state loads authenticate the complete state. On first
enforcement the anchor is written before enforce state, so any crash leaves
either a valid pair or a fail-closed mismatch. A later crash
between state and anchor replacement may fail closed and require administrator
recovery; it cannot produce a silent downgrade.
Generated and migrated Codex configurations install the dedicated
claude-flow-mcp binary, preserve unrelated MCP servers, set policy mode to
legacy, and leave swarm automation disabled. Windows uses cmd /c; POSIX
uses npx directly. Existing AGENTS.md content is not replaced by runtime
migration.
Policy administration is not exempt from authorization. Bootstrap and recovery
mutations use the interactive local ruflo policy command. Remote MCP registers
only evaluation and status by default; administrative handlers require a
deployment-specific authenticated human identity adapter. Approval issuance
also requires the issuer allowlist. A standalone MCP server can set
requireToolAuthorization = true, in which case construction fails unless an
authorizer is supplied.
Validated on 2026-07-28:
git diff --check: clean.The policy microbenchmark records:
pure evaluation: 1,590,091 decisions/second (0.629 µs/decision)
transaction receipt: 43,251 decisions/second (23.121 µs/decision)
receipt ledger: valid
These figures are local microbenchmarks, not a production latency guarantee. The receipt number excludes filesystem lock contention and disk latency.
legacy.maxUses, and revoked approval fail.