Skip to main content

Hybrid Deployment

Overview

Behavry supports splitting its single-process architecture into a control plane and one or more data planes. This hybrid model addresses three common enterprise requirements:

  • Latency -- Data planes run in the same network as agents, eliminating round-trip hops to a central cloud for every tool call evaluation.
  • Data sovereignty -- Agent payloads, audit logs, and tool call content never leave the data plane's network. Only policy bundles and lightweight heartbeats cross the boundary.
  • Horizontal scale -- Each data plane runs its own proxy pipeline, OPA instance, and PostgreSQL database. Adding capacity means deploying another data plane, not scaling a monolith.

Three Deployment Modes

Behavry uses a single environment variable to select the deployment mode:

ModeBEHAVRY_DEPLOYMENT_MODEDescription
Standalonestandalone (default)All services active. Used for development, demos, and single-node self-hosted deployments.
Control Planecontrol-planeDashboard, policy management, identity management, tenant provisioning, alert aggregation, OPA bundle serving, JWKS endpoint. No proxy pipeline.
Data Planedata-planeMCP proxy, OPA (bundle-polling), behavioral monitor, audit storage, escalation queue, rate limiter. No policy CRUD, no super-admin, no dashboard.

The mode is evaluated at startup. Changing it requires a restart.


Architecture

                        CONTROL PLANE
┌──────────────────────────────────────────┐
│ Dashboard Policy CRUD │
│ Tenant Provisioning SIEM Destinations │
│ Super-Admin API Alert Aggregation │
│ JWKS (.well-known) License Validation │
│ OPA Bundle Serving Data Plane Tokens │
└──────────┬───────────────────┬─────────────┘
│ HTTPS │ HTTPS
┌───────────┘ └───────────┐
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ DATA PLANE (A) │ │ DATA PLANE (B) │
│ │ │ │
│ MCP Proxy │ │ MCP Proxy │
│ OPA (bundle-poll) │ │ OPA (bundle-poll) │
│ Behavioral Monitor │ │ Behavioral Monitor │
│ Audit (local PG) │ │ Audit (local PG) │
│ Escalation Queue │ │ Escalation Queue │
│ Rate Limiter │ │ Rate Limiter │
└─────────────────────┘ └─────────────────────┘
▲ ▲
Agent traffic Agent traffic
(never leaves (never leaves
this network) this network)

Only two types of traffic cross from data plane to control plane:

  1. OPA bundle polling -- Data plane OPA fetches the latest Rego policies every 30--120 seconds.
  2. Heartbeat + license validation -- The data plane backend posts a heartbeat every 60 seconds and validates its license on startup.

Control Plane Services

When BEHAVRY_DEPLOYMENT_MODE=control-plane, the following route groups are active:

Management Surface (control-plane and standalone)

  • Policy CRUD, DLP pattern management, inbound scanner rules
  • Blast radius configuration
  • Data protection settings
  • Super-admin tenant provisioning
  • Kill switch
  • Agent enrollment and identity management
  • Workflow and delegation management
  • AI asset discovery
  • SIEM destination management
  • Policy candidate review (Red Team loop)
  • OPA bundle endpoint (for data plane consumption)

JWKS Endpoint

The control plane exposes the RS256 public key as a standard JWK Set:

GET /.well-known/jwks.json

Data planes fetch this on startup and cache it (1-hour Cache-Control header) for agent JWT validation. This allows data planes to verify agent tokens without holding the private key.

Response:

{
"keys": [
{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"kid": "behavry-rs256",
"n": "<base64url-encoded modulus>",
"e": "AQAB"
}
]
}

License Validation

Data planes call this endpoint on startup to confirm their license is valid and retrieve plan limits:

POST /api/v1/licenses/validate

Request body:

{
"license_key": "lic_...",
"deployment_id": "dp-us-east-01",
"data_plane_token": "dp_...",
"reported_agent_count": 12,
"version": "1.4.0"
}

Response:

{
"valid": true,
"plan_tier": "enterprise",
"expires_at": "2027-03-01T00:00:00Z",
"max_agents": -1,
"grace_period_days": 7,
"update_available": false,
"update_notes": null
}

If the license key does not match the tenant's stored key, the response returns valid: false with a 7-day grace period before enforcement mode activates.


Data Plane Services

When BEHAVRY_DEPLOYMENT_MODE=data-plane, the following route groups are active:

  • MCP proxy pipeline (Streamable HTTP + stdio backends)
  • OpenAI, Anthropic, Gemini, and Ollama API proxies
  • Browser extension ingest
  • Session management
  • Audit log storage and export
  • Behavioral monitor and risk scoring
  • Escalation queue and HITL resolution

Startup Sequence

  1. License validation -- The backend calls POST /api/v1/licenses/validate on the control plane. If the license is invalid and the grace period has expired, the process logs an error but continues (degraded mode).
  2. Heartbeat loop -- A background task posts to POST /api/v1/data-planes/{deployment_id}/heartbeat every BEHAVRY_HEARTBEAT_INTERVAL seconds (default: 60).

Local OPA with Bundle Polling

The data plane OPA instance runs in bundle-polling mode rather than loading policies from disk. It fetches a .tar.gz bundle from the control plane at a configurable interval (30--120 seconds).

OPA configuration (docker/opa/opa-data-plane.yaml):

services:
behavry-control:
url: ${CONTROL_PLANE_URL}
headers:
Authorization: "Bearer ${DATA_PLANE_TOKEN}"

bundles:
behavry:
resource: /api/v1/opa/bundle/${TENANT_ID}
service: behavry-control
polling:
min_delay_seconds: 30
max_delay_seconds: 120

decision_logs:
console: true

status:
console: true

When a policy is created, updated, or activated on the control plane, the data plane's OPA picks up the change on its next polling cycle.


Data Plane Token Management

Data plane tokens authenticate bundle polling, heartbeat, and license validation requests. They are issued by a tenant admin on the control plane and passed to the data plane as a configuration secret.

Issue a Token

curl -X POST https://control-plane.example.com/api/v1/admin/data-plane-tokens \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"label": "US East production",
"region": "us-east-1",
"expires_days": 365
}'

Response (token shown once):

{
"id": "tok-uuid",
"label": "US East production",
"tenant_id": "tenant-uuid",
"deployment_id": null,
"region": "us-east-1",
"created_at": "2026-03-17T00:00:00Z",
"expires_at": "2027-03-17T00:00:00Z",
"last_seen_at": null,
"revoked_at": null,
"token": "dp_<random-48-char-urlsafe>"
}

Save the token value immediately. It is not stored in plaintext -- only a SHA-256 hash is persisted.

List Tokens

curl https://control-plane.example.com/api/v1/admin/data-plane-tokens \
-H "Authorization: Bearer $ADMIN_TOKEN"

Returns all tokens for the current tenant, including last_seen_at, active_agent_count, and opa_bundle_version from the most recent heartbeat.

Revoke a Token

curl -X DELETE https://control-plane.example.com/api/v1/admin/data-plane-tokens/{token_id} \
-H "Authorization: Bearer $ADMIN_TOKEN"

Revocation is immediate. The data plane's next OPA bundle poll or heartbeat will receive a 401 response.


OPA Bundle Serving

The control plane serves OPA bundles at:

GET /api/v1/opa/bundle/{tenant_id}
Authorization: Bearer <data_plane_token>

The response is a .tar.gz archive containing:

  • .manifest -- OPA bundle manifest with roots: ["behavry"] and a timestamp-based revision.
  • behavry/<category>/<policy>.rego -- All active Rego policy files from the policies/ directory (base, inbound, requester, delegation, workflows, discovery, and any auto-generated policies).

Test policies (directories containing "test" in the name) are excluded from the bundle.

Each request updates the token's last_seen_at timestamp. The response includes:

  • X-Bundle-Revision header with the build timestamp.
  • Cache-Control: no-cache to ensure OPA always checks for updates.

Environment Variables

All Sprint W variables use the BEHAVRY_ prefix:

VariableModeDefaultDescription
BEHAVRY_DEPLOYMENT_MODEAllstandalonestandalone, control-plane, or data-plane
BEHAVRY_CONTROL_PLANE_URLData plane(none)Base URL of the control plane (e.g., https://cp.example.com)
BEHAVRY_DATA_PLANE_TOKENData plane(none)Token issued by POST /api/v1/admin/data-plane-tokens
BEHAVRY_LICENSE_KEYData plane(none)License key from TenantConfig.license_key
BEHAVRY_DEPLOYMENT_IDData plane(none)Stable identifier for this data plane instance (e.g., dp-us-east-01)
BEHAVRY_HEARTBEAT_INTERVALData plane60Seconds between heartbeat posts to the control plane
BEHAVRY_TENANT_IDData plane OPA(none)Tenant UUID used in the OPA bundle URL path

The control plane does not require any of the data-plane-specific variables. In standalone mode, all variables are ignored.


Docker Compose Setup

Two purpose-built Compose files are provided in docker/:

Control Plane

docker compose -f docker/docker-compose.control-plane.yml up -d

Services: PostgreSQL + TimescaleDB, OPA (authoring mode with local policy files), Behavry backend (BEHAVRY_DEPLOYMENT_MODE=control-plane), dashboard (Nginx).

Required environment variables in docker/.env:

BEHAVRY_ADMIN_PASSWORD=<strong-password>
BEHAVRY_JWT_PRIVATE_KEY=<RS256 private key PEM>
BEHAVRY_JWT_PUBLIC_KEY=<RS256 public key PEM>
BEHAVRY_DB_PASSWORD=<postgres-password>

The control plane exposes ports 8000 (API) and 80/443 (dashboard).

Data Plane

docker compose -f docker/docker-compose.data-plane.yml up -d

Services: PostgreSQL + TimescaleDB (local to the data plane), OPA (bundle-polling mode), Behavry backend (BEHAVRY_DEPLOYMENT_MODE=data-plane).

Required environment variables in docker/.env:

BEHAVRY_CONTROL_PLANE_URL=https://your-control-plane.example.com
BEHAVRY_DATA_PLANE_TOKEN=dp_<token-from-control-plane>
BEHAVRY_LICENSE_KEY=lic_<from-tenant-config>
BEHAVRY_ADMIN_PASSWORD=<local-admin-password>
BEHAVRY_JWT_PUBLIC_KEY=<RS256 public key from control plane JWKS>
BEHAVRY_DB_PASSWORD=<postgres-password>
BEHAVRY_DEPLOYMENT_ID=dp-us-east-01
BEHAVRY_TENANT_ID=<tenant-uuid>

The data plane exposes port 8000 (proxy API). There is no dashboard -- administrative operations are performed on the control plane.

Network Isolation

The data plane Compose file defines three networks:

NetworkPurpose
internalDatabase and OPA -- not reachable from outside the stack
publicBackend proxy -- reachable from agents in the customer's network
externalOutbound HTTPS to the control plane only -- restrict with firewall rules

Health Monitoring

Heartbeat

Every BEHAVRY_HEARTBEAT_INTERVAL seconds, the data plane backend posts:

POST /api/v1/data-planes/{deployment_id}/heartbeat
Authorization: Bearer <data_plane_token>

Payload:

{
"deployment_id": "dp-us-east-01",
"active_agent_count": 8,
"proxy_rpm_1m": 142.5,
"escalations_pending": 2,
"opa_bundle_version": "20260317T143022Z",
"uptime_seconds": 86400
}

The control plane updates last_seen_at and stores the latest metrics snapshot on the DataPlaneToken row. Operators can monitor data plane health by listing tokens and checking last_seen_at recency.

Detecting Stale Data Planes

A data plane that misses several consecutive heartbeats may indicate a network partition or process failure. The control plane does not currently send alerts for missed heartbeats, but operators can monitor last_seen_at via the token list endpoint and set up external alerting (e.g., a Prometheus query on a SIEM webhook) for staleness beyond a threshold.


Route Gating Reference

The following table summarizes which route groups are active in each deployment mode:

Route GroupStandaloneControl PlaneData Plane
JWKS, auth, admin, signup, data-plane tokens, licenseYesYesYes
Policy CRUD, DLP, inbound rulesYesYes--
Super-admin, kill switch, enrollmentYesYes--
Workflows, delegations, discoveryYesYes--
OPA bundle endpoint, SIEM, policy candidatesYesYes--
Blast radius, data protectionYesYes--
MCP proxy, AI API proxiesYes--Yes
Audit log, sessions, browser ingestYes--Yes
Behavioral monitor, risk scoringYes--Yes
Escalation queueYes--Yes