Skip to content

Architecture diagrams

The single-path system overview (agent -> adapter -> core -> decision) is on the Concepts page. This page goes one level deeper: the /v1/decide round-trip, the hold lifecycle, where things run, and the honest trade-off between the two ways to place an adapter. For the prose architecture (seams, guarantees, traceability), see the architecture reference.

The decision round-trip

sequenceDiagram
    autonumber
    participant Ag as AI agent
    participant Ad as Adapter
    participant Co as reeflex-core
    participant Op as OPA/Rego
    Ag->>Ad: attempt a backend action
    Ad->>Ad: normalize to Action Envelope
    Ad->>Co: POST /v1/decide (envelope)
    Co->>Co: inject cumulative session ledger
    Co->>Op: evaluate policy (pure, no LLM)
    Op-->>Co: allow / deny / require_approval
    Co->>Co: append audit record
    Co-->>Ad: Decision (+ decision_id)
    Ad->>Ad: enforce - run, block, or hold
    Ad-->>Ag: result, or a reason it can read

The /v1/decide round-trip. The adapter never touches the backend until the verdict is in; reeflex-core decides deterministically over the per-session ledger and records an audit entry either way. On require_approval the adapter stores a hold instead of executing (next diagram). Same envelope in, same decision out.

Hold lifecycle

stateDiagram-v2
    [*] --> pending: require_approval creates a hold
    pending --> approved: an approver you trust resolves it
    pending --> rejected: approver rejects
    pending --> expired: TTL elapses
    approved --> consumed: adapter re-submits, envelope-hash matches
    rejected --> [*]
    expired --> [*]
    consumed --> [*]

A hold is single-use and time-bound. Core enforces actor != approver (the agent that raised the hold can never resolve it), the TTL (expires_ts), and envelope-hash binding (the approved action is the exact one submitted). Resolve holds from wp-admin, the reeflex-holds MCP server, or the resolution API. See HIL / HOTL / AIL for who may resolve what.

Deployment: self-hosted, adapters call core

flowchart TB
    subgraph net["Your network / infrastructure"]
        core["reeflex-core (stateless container)"]
        subgraph src["Source-side adapters"]
            cc["Claude Code hook"]
            n8["n8n gate node"]
        end
        subgraph res["Resource-side adapters"]
            wp["WordPress gate"]
            gw["MCP gateway proxy"]
        end
    end
    cc -- "POST /v1/decide" --> core
    n8 -- "POST /v1/decide" --> core
    wp -- "POST /v1/decide" --> core
    gw -- "POST /v1/decide" --> core

The only production-supported topology is on-prem: everything runs inside your own network and no decision data leaves it. reeflex-core is a stateless container; every adapter reaches it over one HTTP call. (An opt-in public eval endpoint exists for trying it - see Getting started.)

Adapter placement: source-side vs resource-side

flowchart LR
    subgraph SRC["Source-side (adapter in the agent)"]
        AG["one agent"] --> AD1["adapter"]
    end
    AD1 --> B1["Postgres"]
    AD1 --> B2["S3"]
    AD1 --> B3["files"]
    subgraph RES["Resource-side (adapter in the backend)"]
        AD2["adapter"] --> DB["one backend"]
    end
    C1["any caller"] --> AD2
    C2["another agent"] --> AD2

The honest trade-off. A source-side adapter (Claude Code, n8n) governs one agent wherever it acts - across every backend it touches - but only that agent; another agent hitting the same backend is ungoverned. A resource-side adapter (WordPress, MCP gateway) governs every caller of one backend, but only that backend. Neither is strictly better; place adapters at the seam that matches your threat model, and combine them for defense in depth.