Arcus Docs

Introduction

How it works

One HTTP call from your agent, five enforcement checks, one queued delivery, and two durable records — the mutable operational log and the sealed audit event. This page follows a single request all the way through.

The shape of the system#

Arcus has three moving parts and two datastores.

PartRole
Gateway APIExpress service. Authenticates the key, runs the five enforcement gates, writes the decision, enqueues the delivery.
Delivery workerBullMQ consumer. Performs the outbound HTTP POST to your target, with retries, and records the outcome.
DashboardNext.js app. Keys, policies, guardrails, logs, the approval queue, audit verification and billing.
PostgreSQLAccounts, keys, policies, guardrail rules, baselines, transfer logs, the audit chain.
RedisRate-limit counters, quarantine state, and the delivery queue.

Enforcement is synchronous and delivery is asynchronous. That split is the whole latency story: your agent waits for the decision, never for the target.

The request lifecycle#

1. Your agent calls Arcus#

A single POST to /v1/dispatch with a bearer key. The agent does not hold the target's credentials and does not know the route is being governed.

bash
curl -X POST https://your-arcus-host/v1/dispatch \
  -H "Authorization: Bearer ark_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "target": "https://api.internal.example.com/webhook/triage",
    "from": "intake-agent",
    "message": "Patient reports severe pain in the lower left molar.",
    "data": { "priority": "urgent", "caseId": "C-4471" }
  }'

2. The key is authenticated#

The raw key is SHA-256 hashed and looked up by hash. Arcus never stores the key itself — only the hash and a short display prefix. A missing header, a malformed header, an unknown key or a revoked key all return 401 before any other work happens.

The lookup resolves the owning account, which is what makes everything after this point tenant-scoped.

3. The five gates run, in order#

Order matters, and it is not arbitrary — each gate is cheaper and more fundamental than the one after it. See The enforcement pipeline for the full reasoning.

1

Threat detection — 429

A Redis counter per (tenant, agent). Over 60 dispatches in 60 s quarantines the agent for 300 s. Runs first because a flooding agent should cost one Redis read, not a policy evaluation and a payload scan.

2

Identity binding — 401

If the key is bound to an agent id, the request's from must equal it. Everything downstream reasons about the sender, so the sender has to be settled before it is trusted.

3

Policy — 403

Matches (sender, target) against your policies, most specific first. No match is a denial. Answers may this agent talk to this endpoint at all — cheaper than reading the payload.

4

DLP — 422

Walks the payload looking for credential-shaped values and sensitive field names. Runs only for routes policy already authorized, so a blocked route never pays for a scan.

5

Parameter guardrails — 422

Conditions on values inside the payload, plus a statistical baseline per (agent, field). Last because it is the most expensive and the most specific.

4. The decision is written#

Every terminal decision — allow, block, hold — writes two rows in a single database transaction:

  • a TransferLog row: the mutable operational record the dashboard reads (status, attempts, HTTP status, delivery time).
  • an AuditEvent row: append-only and hash-sealed, chained to the previous event in your chain.

Because both writes share one transaction, there is no state in which a decision was made but not recorded, or recorded but not sealed.

Why two records and not one

TransferLog legitimately changes as a delivery progresses — attempts, HTTP status, deliveredAt. A hash over a row that changes cannot tell an authorized update from tampering. So the audit chain never updates a row: a delivery outcome appends a new event instead of editing the old one. See Audit chain.

5. Delivery is queued#

An allowed request is enqueued to BullMQ and Arcus returns 202 Accepted immediately. Your agent is not waiting on the target.

json
{ "ok": true, "transferLogId": "clx8m2p...", "queued": true }

The enqueue itself has a 2-second timeout. If Redis is unreachable the request is still recorded — as FAILED with Queue unavailable — rather than being silently dropped.

6. The worker delivers#

The worker POSTs the validated payload to target as JSON, with a 10-second timeout.

BehaviourValue
Attempts3
BackoffExponential, starting at 1 s
Per-attempt timeout10 s
Worker concurrency5
SuccessAny 2xx from your target
FailureNon-2xx, timeout or connection error — retried, then FAILED

Each attempt appends delivery.succeeded or delivery.failed to the audit chain, so the number of attempts and the reason for each failure are part of the sealed record.

Your receiving endpoint sees the same JSON body the agent sent, with content-type: application/json.

What your target receives#

The delivered body is the validated dispatch payload — the same object, including target itself, so a receiver can distinguish a governed delivery from a direct call.

python
import json
from http.server import BaseHTTPRequestHandler, HTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        length = int(self.headers.get('Content-Length', 0))
        body = json.loads(self.rfile.read(length) or b'{}')

        print('from:', body.get('from'))
        print('message:', body.get('message'))
        print('data:', body.get('data'))

        # Return 2xx or Arcus will retry with backoff.
        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        self.wfile.write(json.dumps({'status': 'received'}).encode())

HTTPServer(('0.0.0.0', 4101), Handler).serve_forever()

Return a 2xx or expect a retry

Any non-2xx response is treated as a delivery failure and retried up to three times with exponential backoff. Make your receiver idempotent, and acknowledge before you do slow work if that work can take longer than 10 seconds.

Latency budget#

The synchronous portion is what your agent experiences:

  • one Redis read for the quarantine check,
  • one indexed Postgres read for the policy set,
  • an in-process payload walk for DLP and guardrails — no network call, no third-party service, no model inference,
  • one transaction to write the decision,
  • one Redis write to enqueue.

There is no per-check round trip. Adding DLP does not add a hop; adding guardrails does not add a hop. That is what "one pass at the edge" means in practice.

Failure behaviour, stated honestly#

Component downWhat happens
RedisRate limiting is skipped and the request proceeds — a throttle is not an authorization boundary, and a Redis outage must not take the gateway down. Policy, DLP and guardrails still run. The skip is logged loudly. Enqueue then fails, so the request is recorded as FAILED rather than accepted-and-lost.
PostgreSQLRequests fail. Arcus will not forward a request it cannot record — an unlogged dispatch is worse than a refused one.
Your targetRetried three times with backoff, then FAILED. The gateway is unaffected.
Worker stoppedRequests are accepted and queue up. They deliver when the worker returns; nothing is lost.

This asymmetry is deliberate and is covered in full in the Security model.