Skip to content

ARMS Storage ​

Persist guardrail run data through the ARMS Backend API, with automatic routing to MongoDB, DynamoDB, or ClickHouse depending on your deployment.

Overview ​

Guardrails can buffer check results, tool authorization events, rate-limit events, and LLM generate metadata, then flush them to the ARMS Backend at the end of each run. The Backend writes to whichever database your ARMS deployment uses — you do not configure a database driver in guardrails YAML.

Key points:

  • Guardrails alone (storage.enabled: false) never writes to a database.
  • Storage requires ARMS Backend credentials (API_BASE_URL, ELSAI_ARMS_API_KEY).
  • The active database type is resolved automatically via GET /api/v1/db_type.
  • Run data is posted to POST /api/v1/guardrails/{db_type} where db_type is mongodb, dynamodb, or clickhouse.

Supported Databases ​

DatabaseBackend routeNotes
MongoDB/api/v1/guardrails/mongodbDefault for many ARMS deployments
DynamoDB/api/v1/guardrails/dynamodbAWS-hosted ARMS stacks
ClickHouse/api/v1/guardrails/clickhouseAnalytics-oriented deployments

The guardrails SDK discovers the active type from the Backend. You do not pass a backend: mongodb setting in policy — direct database clients were removed in v0.1.5.

How It Works ​

  1. Enable storage in guardrails policy and provide Backend credentials.
  2. GuardrailsStorageHook buffers events in memory during a logical run.
  3. Input checks, output checks, generate results, tool auth, and rate-limit events are collected.
  4. On end_run() (or when LLMRails.generate() completes), the hook builds a run document and POSTs it to the Backend.
  5. The Backend persists the document to the configured database using your ARMS project context.

Persisted Run Document ​

Each flushed run includes correlation ids and check outcomes:

  • project, project_id, run_id, session_id, trace_id
  • user_input, final_response, llm_response (optionally redacted)
  • input_check, output_check, pii_input_check, pii_output_check
  • exfiltration_output_check
  • rate_limit_check, token_budget_* checks
  • tool_authorization_events, rate_limit_events
  • blocked, block_reason, latency_ms, arms_correlated

Set store_raw_text: false to store SHA-256 digests instead of raw prompt/response text.

Configuration ​

Enable Storage ​

yaml
guardrails:
  storage:
    enabled: true
    project: my-app
    store_raw_text: true
    fail_soft: true
    unique_run_per_project: false
    arms_correlation: true

Backend credentials are read from the same environment variables as ARMS, not duplicated in guardrails YAML:

VariableRequiredDescription
API_BASE_URLYesARMS Backend base URL
ELSAI_ARMS_API_KEYYesAPI key for Backend authentication
ARMS_MASTER_KEYNoOptional master key sent as X-MASTER-KEY

You may override credentials in policy when needed:

yaml
guardrails:
  storage:
    enabled: true
    api_base_url: "https://arms-backend.example.com"
    api_key: "your-api-key"
    master_key: "optional-master-key"

Storage Options ​

OptionTypeDefaultDescription
enabledboolfalseEnable persistence via ARMS Backend
projectstr"default"Logical project name stored on run documents
store_raw_textbooltrueStore full text; false stores SHA-256 digests
fail_softbooltrueLog warnings on write failure instead of raising
unique_run_per_projectboolfalseBackend upsert behavior for run ids
arms_correlationbooltrueAuto-link run_id / project_id from ARMS env vars

Environment Overrides ​

VariableDescription
GUARDRAILS_STORAGE_ENABLEDForce storage on/off (true / false)
GUARDRAILS_ARMS_RUN_IDARMS run id for correlation
GUARDRAILS_ARMS_PROJECT_IDARMS project id for correlation
GUARDRAILS_ARMS_CORRELATIONEnable/disable automatic ARMS correlation
GUARDRAILS_PROJECTDefault project name

ARMS Correlation ​

Storage writes require a project_id. Link guardrails to an active ARMS run using one of these approaches:

Call immediately after creating both clients, before any guardrail checks:

python
from elsai_guardrails.guardrails import LLMRails
from elsai_arms import ElsaiARMS  # optional companion package

arms = ElsaiARMS(project_name="my-app")
rails = LLMRails.from_config("config.yml")

rails.guardrail_system.link_arms(arms)

response = rails.generate(
    messages=[{"role": "user", "content": "Hello"}]
)
rails.guardrail_system.end_run()

Pass ids explicitly when you manage correlation yourself:

python
rails.guardrail_system.link_run_context(
    run_id="abc123",
    project_id="proj-456",
    project="my-app",
)

3. Environment variables ​

Set before starting the process:

bash
export GUARDRAILS_ARMS_RUN_ID=abc123
export GUARDRAILS_ARMS_PROJECT_ID=proj-456

When arms_correlation: true (default), the storage hook picks up these ids automatically.

Usage Patterns ​

LLMRails (automatic buffering) ​

LLMRails wires the storage hook when storage is enabled. Each generate() call buffers results; flushing happens according to your run lifecycle:

python
from elsai_guardrails.guardrails import LLMRails

rails = LLMRails.from_config("config.yml")
rails.guardrail_system.link_run_context(
    run_id="run-1",
    project_id="project-1",
)

with rails.guardrail_system.storage_run_context(session_id="sess-1"):
    rails.generate(messages=[{"role": "user", "content": "Hi"}])

GuardrailSystem (manual lifecycle) ​

python
from elsai_guardrails.guardrails import GuardrailSystem, GuardrailConfig
from elsai_guardrails.guardrails.guardrail_policy import GuardrailPolicy

policy = GuardrailPolicy.from_yaml("config.yml")
guardrail = GuardrailSystem(
    config=GuardrailConfig(),
    guardrail_policy=policy,
)._with_storage_hook()

guardrail.link_run_context(run_id="run-1", project_id="project-1")
guardrail.begin_run()
guardrail.check_input("user message")
guardrail.end_run()  # flushes to Backend

Fail-soft vs fail-hard ​

With fail_soft: true (default), Backend write failures are logged and the application continues. Set fail_soft: false to raise StorageWriteError when persistence fails.

Database Selection Flow ​

mermaid
sequenceDiagram
    participant App as Guardrails App
    participant Hook as GuardrailsStorageHook
    participant Sink as BackendGuardrailSink
    participant API as ARMS Backend
    participant DB as MongoDB / DynamoDB / ClickHouse

    App->>Hook: check_input / generate / end_run
    Hook->>Hook: buffer events
    Hook->>Sink: save_run(run_doc, ctx)
    Sink->>API: GET /api/v1/db_type
    API-->>Sink: {"db_type": "mongodb"}
    Sink->>API: POST /api/v1/guardrails/mongodb
    API->>DB: persist run document
    API-->>Sink: {"run_id": "..."}

Troubleshooting ​

IssueCauseFix
Storage silently disabledMissing API_BASE_URL or ELSAI_ARMS_API_KEYSet env vars or policy credentials
Cannot persist without project_idNo ARMS correlationCall link_arms() or set env ids
Legacy backend: mongodb warningOld direct-DB configMigrate to Backend storage (see below)
Unsupported db_typeBackend returned unknown typeUpgrade Backend or contact ARMS admin

Migration from Direct Database Storage ​

In v0.1.5, direct guardrails-to-database clients were removed. Policies that set storage.backend: mongodb|dynamodb|clickhouse with connection strings no longer persist data — the SDK logs a warning and storage stays disabled.

Before (removed):

yaml
guardrails:
  storage:
    backend: mongodb
    connection_string: "mongodb://..."

After (ARMS Backend):

yaml
guardrails:
  storage:
    enabled: true
    project: my-app
    arms_correlation: true
bash
export API_BASE_URL=https://your-arms-backend
export ELSAI_ARMS_API_KEY=your-api-key
export GUARDRAILS_ARMS_PROJECT_ID=your-project-id

Link each run with link_arms(), link_run_context(), or the correlation environment variables before calling end_run().

Examples ​

See Basic Examples and Integration Examples for complete code samples.

python
import os
from elsai_guardrails.guardrails import GuardrailConfig, GuardrailSystem
from elsai_guardrails.guardrails.guardrail_policy import GuardrailPolicy

os.environ["API_BASE_URL"] = "https://your-arms-backend"
os.environ["ELSAI_ARMS_API_KEY"] = "your-api-key"

guardrails = GuardrailSystem(
    config=GuardrailConfig(),
    guardrail_policy=GuardrailPolicy.from_file("config.yaml"),
)._with_storage_hook()

guardrails.link_run_context(
    run_id="demo-run-001",
    project_id="demo-project-001",
    project="demo-app",
)
guardrails.begin_run(session_id="sess-1")
guardrails.check_input("Hello")
guardrails.check_output("Hi there!")
saved_run_id = guardrails.end_run()  # POST to Backend

Next Steps ​

Released under the MIT License.