Docs · Pipelines

Compose workers, top to bottom.

Declare the pipeline once. Every request walks the same ordered list. Workers can pass, mutate, enrich, block, or branch. Declarative where it suffices, inline code where you need it.

Pipeline shape

A pipeline is an ordered list of workers grouped by stage. The stages are pre-request, post-response, and on-error. Within a stage, workers run in declaration order.

# config.yaml
workers:
  - id: pii-scrub
    stage: pre-request
  - id: credential-inject
    stage: pre-request
  - id: metering
    stage: post-response
  - id: audit-log
    stage: post-response
  - id: rewrite-on-failure
    stage: on-error

Dispositions

  • Pass: return the request unchanged. Default for a worker that returns nothing.
  • Mutate: return a modified envelope. Writes are applied after the worker yields.
  • Enrich: attach headers or metadata without changing the body.
  • Block: short-circuit with a 4xx that the client sees directly. The remaining workers in the stage do not run.
  • Branch: rewrite the backend target. The pipeline restarts from pre-request with the new target (subject to the max_branches limit).

Example: basic passthrough + metering

workers:
  - id: metering
    stage: post-response

# Metering reads res.body.usage, computes cost, writes to the ledger.
# No other logic. The simplest useful pipeline.

Example: PII-safe external call

workers:
  - id: pii-scrub
    stage: pre-request
    config:
      redact:
        - email
        - phone
        - "\\b\\d{3}-\\d{2}-\\d{4}\\b"   # SSN
  - id: metering
    stage: post-response
  - id: audit-log
    stage: post-response

# Redacts PII before the request leaves the gateway.
# Metering and audit record the full receipt downstream.

Example: rewrite on failure

workers:
  - id: rewrite-on-failure
    stage: on-error
    config:
      on_status: [500, 502, 503, 504]
      fallback_upstream: anthropic-direct

routes:
  - match: 'request.model == "gpt-4o"'
    upstream: openai
    circuit_breaker:
      failure_threshold: 5
      reset_after: 30s

Example: inline R1 fallback

workers:
  - id: inline-r1-classify
    stage: pre-request
    runtime: node
    code: |
      import { r1 } from '@relayone/r1-client';
      export async function onRequest(req, ctx) {
        if (req.body.messages.length < 3) return req;
        const intent = await r1.call('classify-intent', {
          prompt: req.body.messages.at(-1).content
        });
        ctx.annotate({ intent });
        if (intent === 'code') req.upstream = 'anthropic-direct';
        if (intent === 'data') req.upstream = 'openai';
        return req;
      }
  - id: metering
    stage: post-response

Runtime selection

Workers declare their runtime with runtime: wasm | node | python. Default is wasm. Node workers use an isolated sandbox; Python workers use an embedded interpreter. Choose based on ecosystem needs, not performance: WASM is fastest, Node has the largest ecosystem, Python is best for data logic.

Error handling

If a worker throws, RelayGate records the error in the receipt and continues with the remaining workers unless the worker declared fail_closed: true. A failing pre-request worker with fail_closed blocks the request; a failing post-response worker still lets the upstream response reach the client.

Tests

The relaygate test command replays a fixture request against your pipeline and asserts on the receipt. Use it in CI to catch policy drift before it reaches production.

relaygate test --fixture tests/req-01.json --expect '.redactions >= 1 and .overhead_us < 500'

Next: CEL policy reference →