Skip to content

Reeflex — Architecture

This document describes the decision flow and the two deployment variants. For the recorded decision on deployment sequencing and open-core boundary, see docs/adr/0001-deployment-model.md.


Decision flow

An adapter intercepts a backend-specific action, normalizes it into a universal Action Envelope, and posts it to reeflex-core. The engine evaluates the envelope against OPA/Rego policy and returns a deterministic decision. The adapter enforces that decision before the backend action executes. On require_approval, the adapter stores a hold instead of executing; resolving that hold is a separate handover step, covered in Hold resolution (HIL, HOTL, AIL) below.

flowchart LR
    A[AI Agent] --> B["Adapter\n(normalize to\nAction Envelope)"]
    B -->|"POST /v1/decide\n{ActionEnvelope}"| C["reeflex-core\n/v1/decide"]
    C --> D{Decision}
    D -->|allow| E["Adapter:\nexecute action"]
    D -->|deny| F["Adapter:\nblock, surface reason"]
    D -->|require_approval| G["Adapter:\nstore hold"]
    G --> H{{"Resolved by the principal\nyou designate\n(human / agent / automation)\nunder your resolution policy"}}
    H -->|approved| I["Adapter:\nre-submit envelope\napproval.present = true"]
    I --> C
    H -->|rejected| F
    C --> AU[("Audit log\n(append-only)")]

Key invariants:

  • Zero LLM in the decision path. The engine is OPA/Rego plus classical logic. Free text, markdown, and OKF documents are never decision inputs.
  • Fail-closed. If the engine is unreachable or OPA cannot be invoked, the adapter denies or holds. There is no configuration that changes this.
  • Deterministic. Same Action Envelope in, same Decision out — every call, every deployment.
  • Core never executes. On require_approval, reeflex-core persists a hold and hands control back to the adapter. Whether the action ultimately runs, is blocked, or is re-submitted after resolution is always decided and carried out by the adapter, never by core.

Interception seams

reeflex-core knows nothing about where an action came from. Every adapter attaches this same decision flow at a different seam:

Adapter Seam Placement
reeflex-claude PreToolUse hook (every Claude Code tool call) Source-side (in the agent)
reeflex-wordpress WP_Ability::execute() (WordPress Abilities API) Resource-side (in the backend)
n8n-nodes-reeflex a gate node before a risky workflow step Source-side (in the workflow)
reeflex-mcp JSON-RPC tools/call, in front of any MCP upstream (stdio or streamable-HTTP) Network-boundary (between an MCP client and the server(s) it talks to)

reeflex-mcp is the newest of these: a transparent MCP proxy that aggregates and namespaces every configured upstream's tools, intercepts tools/call, normalizes it into the same Action Envelope shown above (via declarative per-server mappings or a conservative heuristic fallback — see docs/mcp-gateway.md), and enforces the same /v1/decide verdict. It adds no second decision engine and no state of its own — R5's cumulative ledger, holds, and audit stay in reeflex-core, keyed by the same agent.session_id this document describes throughout.


Hold resolution (HIL, HOTL, AIL)

require_approval means hold — the principal the operator designates (human, agent, or automation) resolves it before the action can run. The naming and rationale for the three oversight modes (HITL / HOTL / AIL) live in why-reeflex.md#ail; this section shows only the mechanics.

Shipped in core v0.1.5 (HIL Phase 1): GET /v1/holds, GET /v1/holds/{id}, POST /v1/holds/{id}/resolve. The reeflex-holds MCP server (see reeflex-holds/README.md) exposes the same three calls as MCP tools to any MCP client — the socket an AIL principal plugs into.

Hold resolution — decide → hold → resolve (principal) → re-submit → allow → execute

Adapter: require_approval, hold_id, expires_ts Note over Core: hold persisted · TTL-bound · single-use Adapter->>Principal: surface the hold (GET /v1/holds) Principal->>Core: POST /v1/holds/{id}/resolve (approve) Note over Requester,Core: actor ≠ approver — enforced in core Core→>Principal: decided_by, decided_ts (written to the audit trail) Adapter->>Core: re-submit ActionEnvelope (approval.present=true, hold_id) Core→>Adapter: allow Adapter->>Requester: action executes →

Two guarantees hold no matter which principal the operator designates:

  • actor ≠ approver. The agent whose action raised the hold can never resolve it — enforced on identity, inside reeflex-core, on every surface (adapter re-submission and the reeflex-holds MCP surface alike).
  • R3 (irreversible_systemic_prod) is terminal: a systemic action is a deny, never a hold — it must be re-scoped and resubmitted, never approved as-is.

Which principal types may resolve which rule is the operator's own choice, configured via REEFLEX_RESOLUTION_POLICY: Reeflex ships human-only by default; AIL is opt-in, per rule, explicit.


Action Envelope — the three axes

Every backend action is normalized onto three universal axes before evaluation. This is what makes coverage backend-agnostic.

Axis Values (ascending risk)
reversibility reversiblerecoverableirreversible
blast_radius singlescopedbroadsystemic
externality internaloutboundphysical

Ascending risk is not ascending restriction: in the base pack physical is read by no rule (RFX-129) and outbound is the member that restricts, via R5's external_sends budget.

A policy rule like irreversible + broad + production → require_approval governs Postgres, S3, and WordPress identically. See reeflex-spec/SPEC.md §4 for the full specification.


Variant A — Full on-prem (available now, free)

Every component runs inside the client's own infrastructure. Decision data — the Action Envelope — never leaves. This is the shipping variant.

flowchart TD
    subgraph CLIENT["Client Infrastructure"]
        direction TB
        AG["AI Agent"]
        AD["Adapter\n(e.g. reeflex-wordpress plugin)"]
        RC["reeflex-core\nPython + OPA/Rego\nHTTP :8080\n/v1/decide + /v1/holds"]
        AU[("Audit log\nJSONL — append-only")]
    end
    AG -->|"backend intent"| AD
    AD -->|"POST /v1/decide\n{ActionEnvelope}"| RC
    RC -->|"write"| AU
    RC -->|"{ allow | deny | require_approval }"| AD
    AD -->|"proceed / block / hold"| AG

Requirements: Python 3.12, OPA 1.x binary, a persistent service process. Does not work on shared hosting (no persistent processes). See INSTALL.md. The holds API (GET /v1/holds, POST /v1/holds/{id}/resolve) is served from this same process, at this same base URL — see Hold resolution (HIL, HOTL, AIL) above for the resolution handover.


Variant B — Hosted / subscription

PLANNED — not yet available. No hosted engine is currently operated. Do not treat this variant as a current or delivered capability.

In this variant the client installs only a thin adapter. The adapter calls a Reeflex-operated engine over HTTPS. Works on any hosting environment, including shared hosting.

flowchart LR
    subgraph CLIENT["Client Infrastructure"]
        AG2["AI Agent"]
        AD2["Adapter\n(thin plugin)"]
    end
    subgraph HOSTED["PLANNED: reeflex.io (hosted by Reeflex)"]
        RC2["reeflex-core\n(hosted engine)"]
        OPA2["OPA/Rego\nevaluation"]
        AU2[("Audit log\nPostgres — roadmap")]
    end
    AG2 -->|"intent"| AD2
    AD2 -->|"POST /v1/decide\n{ActionEnvelope}\nover HTTPS"| RC2
    RC2 --> OPA2
    RC2 --> AU2
    RC2 -->|"Decision"| AD2
    AD2 -->|"enforce"| AG2

In this variant the Action Envelope transits Reeflex-operated infrastructure. A data-processing agreement is required before this variant launches — this is an explicit gate in ADR-0001.

Multi-tenancy, authentication, and billing are part of the closed commercial tier and will never appear in this repository.


Open-core boundary

Tier Components License
Open-source (this repo) reeflex-core, all adapters, base policy packs, reeflex-spec Apache 2.0
Commercial / closed Attest (audit-ready control evidence: NIS2 Art.21(2) — in force, under active EU audit; DORA; EU AI Act Art.12/14 — high-risk obligations from Dec 2027; SOC 2), Fleet (multi-site management), Cloud (hosted) Proprietary — never in this repo

References

  • reeflex-spec/SPEC.md — Action Envelope, Adapter Contract, conformance requirements, §5.1 Approval object semantics (HIL Phase 1)
  • docs/why-reeflex.md — the HITL / HOTL / AIL naming and rationale (source of truth for the coined term; this document only shows the mechanics)
  • reeflex-holds/README.md — the MCP holds surface (reeflex-holds), an AIL-capable resolution socket
  • docs/mcp-gateway.md — the MCP gateway adapter (reeflex-mcp): the network-boundary interception seam, deployment modes, mappings, obligations, lifecycle
  • docs/adr/0001-deployment-model.md — deployment model decision (engine-as-service, open-core, on-prem-first, hosted = roadmap; embedded-engine alternative documented and rejected)
  • docs/adr/0002-no-llm-in-decision-path.md — why zero LLM in /v1/decide. (Its §2 uses the earlier "held for a human reviewer" wording that predates AIL; the current resolution model is why-reeflex.md#ail.)