# Causalor Quickstart

Govern an AI agent in about two minutes. This document is the canonical
integration reference and is written to be read by a human or by a coding agent.

Every snippet here was executed against the production control plane before
publication. Package name, endpoints and method signatures are verified.

---

## What Causalor does

Two independent channels:

| Channel | What it does | Where it runs | Cost |
|---|---|---|---|
| **Hard** | Deterministically blocks an action that violates a typed constraint, before it executes, and emits a replayable `ViolationProof` | In your process (SDK) or inline at the gateway | Free forever |
| **Soft** | Detects semantic drift from the agent's policy and computes a correction to inject into the next prompt | Detection on the control plane | Paid after trial |

The hard channel is deterministic: the same inputs always produce the same
verdict and the same `state_snapshot_hash`. That is the property that makes it
auditable, and it is the difference between this and asking a model to judge
another model.

---

## Choose a route

Pick by trust boundary. All three run the same enforcement engine.

| Route | Use when | Code change |
|---|---|---|
| **Hosted gateway** | Trying it out | Change `base_url` |
| **Sidecar gateway** | Production, model traffic must stay in your VPC | None, run a container |
| **In-process SDK** | Maximum control, no proxy in the model path | Add `pre_commit()` at irreversible actions |

---

## Step 1: Get a key

Sign in at https://app.causalorlabs.com. On first sign-in you are provisioned a
tenant and a `cal_` key, **shown once**. Copy it.

---

## Route A: Hosted gateway

Point your existing OpenAI or Anthropic client at the gateway. You bring your own
model key in a header; it is forwarded and never stored.

```bash
# .env
CAUSALOR_API_KEY=cal_...        # your Causalor key
CAUSALOR_UPSTREAM_KEY=sk-...    # your OpenAI key; never stored
```

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://gw.causalorlabs.com/v1",
    api_key=os.environ["CAUSALOR_API_KEY"],
    default_headers={
        "X-Causalor-Upstream-Key": os.environ["CAUSALOR_UPSTREAM_KEY"],
        "X-Causalor-Agent": "my-agent",
    },
)
```

Anthropic works the same way, with `base_url="https://gw.causalorlabs.com"` and
`auth_token` set to the `cal_` key.

After the first call the agent appears in the console. Open **Guardrails**:
Causalor reads the agent's own system prompt and tool schemas and proposes typed
constraints bound to your real field names. Edit, then **Activate**.

---

## Route B: In-process SDK

```bash
pip install causalor
```

The package is **`causalor`**. Do not install `causalor-sdk`, which is an
internal development package.

```bash
# .env
CAUSALOR_API_KEY=cal_...
CAUSALOR_API_URL=https://gw.causalorlabs.com
CAUSALOR_POLICY_ID=your-policy   # the policy you Activated in the console
```

```python
from causalor import Causalor

c = Causalor(agent_id="refund-agent")      # key, URL and telemetry from the env
c.load_policy_constraints_from_env()       # pull the guardrails you activated

# --- hard channel: deterministic block ---
result = c.pre_commit("customer-42", "issue_refund", state={"refund_amt": 900})
if result.allowed:
    issue_refund(...)                      # does not run when policy is violated

# --- soft channel: you report, Causalor diagnoses and fixes ---
c.track_action("customer-42", agent_action)   # the action the agent actually took
correction = c.inject("customer-42")          # "" when nothing is wrong
if correction:
    prompt += correction                       # you did not write this
c.acknowledge("customer-42", success=True)     # makes effectiveness measurable
```

### Inspecting a block

```python
print(result.allowed)              # False
print(result.status)               # ConstraintStatus.VIOLATION
print(result.state_snapshot_hash)  # sha256:v1:...  identical for identical inputs
for p in result.proofs:
    print(p.to_dict())
```

Each proof is a dict carrying:

| Field | Example |
|---|---|
| `constraint_id` | `refund_ceiling` |
| `variable`, `actual_value`, `operator`, `threshold` | `refund_amt`, `900`, `<=`, `500` |
| `proof_string` | `refund_amt=900 <= 500 -> False` |
| `state_snapshot_hash` | `sha256:v1:766ccc52...` hash of the evaluated state |
| `proof_fingerprint` | `sha256:v1:eb2c6c22...` hash of the proof itself |
| `constraint_version`, `schema_version`, `engine_version` | `1.0.0`, `1.0.0`, `cae:0.1.0` |
| `evaluated_at` | unix timestamp |

`proof_string` is the human-readable line to log or surface. `proof_fingerprint`
and `state_snapshot_hash` are what you compare to prove two evaluations were
identical, which is what makes the record replayable.

### Strict mode

```python
c.pre_commit_strict(...)   # raises PermissionError on anything not a proven PASS
```

`UNEVALUABLE` states and unmediated constraint conflicts deny rather than pass.

### Airgapped

```python
c.load_policy_bundle_file("confirmed-refund-agent.causalor-bundle.json")
c.set_state("customer-42", {"refund_amt": 220})
c.pre_commit("customer-42", "issue_refund")
```

Hard channel only. No telemetry leaves the process, so drift detection and repair
do not run.

---

## Route C: Sidecar gateway

The same gateway image inside your environment, so LLM traffic never leaves it.
One container, reaching a single Causalor host over HTTPS.

```bash
# .env
CAUSALOR_API_KEY=cal_...
GATEWAY_KEY_VALIDATION_URL=https://gw.causalorlabs.com/v1/whoami
CAUSALOR_API_URL=https://gw.causalorlabs.com
SGSG_BASE_URL=https://gw.causalorlabs.com
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=https://gw.causalorlabs.com
OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer cal_...
```

```bash
docker run -d --name causalor-gw -p 8080:8080 \
  --env-file .env  ghcr.io/causalorlabs/causalor-gateway:latest

# then point your agent's SDK at the sidecar, same headers as hosted:
#   base_url="http://localhost:8080/v1"
```

Model traffic and your provider key stay in your VPC. Governance telemetry does
cross to the control plane so drift detection and the observatory work. For zero
egress, drop the `OTEL_*` lines and run hard-channel-only.

---

## Pitfalls that break integrations

These are the mistakes most likely to be made when wiring this up:

1. **Wrong package.** It is `pip install causalor`. Not `causalor-sdk`.
2. **`register()` returns 404.** It posts to `/v1/events`, which is not exposed on
   the gateway host. Use `register_agent(agent_id, system_prompt, tools)`.
3. **`track_action` argument order.** The signature is
   `track_action(user_id, action_content)`. The session id comes first. Reversing
   these silently disables drift detection.
4. **Do not configure OpenTelemetry.** The SDK sets up its own exporter. Manually
   pointing an OTLP exporter at `localhost:4317` will send telemetry nowhere.
5. **`SGSG_BASE_URL` is not needed** for a normal SDK integration.
6. **Without `load_policy_constraints_from_env()`**, `pre_commit()` has no
   constraints to evaluate and enforces nothing.
7. **Short scripts drop telemetry.** Call `flush_telemetry()` before exit.

---

## Verifying it works

```python
# 1. policy loaded
summary = c.load_policy_constraints_from_env()
assert summary["loaded"] and summary["constraints_loaded"] > 0

# 2. a violating action is blocked
assert c.pre_commit("u1", "issue_refund", state={"refund_amt": 900}).allowed is False

# 3. a compliant action passes
assert c.pre_commit("u1", "issue_refund", state={"refund_amt": 200}).allowed is True

# 4. determinism: identical inputs, identical proof
a = c.pre_commit("u1", "issue_refund", state={"refund_amt": 900})
b = c.pre_commit("u1", "issue_refund", state={"refund_amt": 900})
assert a.state_snapshot_hash == b.state_snapshot_hash
```

Then open https://app.causalorlabs.com/dashboard/ and check **Audit** for the
proof and **Repairs** for any correction applied.

---

## Reference

| Method | Purpose |
|---|---|
| `Causalor(agent_id=...)` | Construct. Reads `CAUSALOR_API_KEY` and `CAUSALOR_API_URL` from env. |
| `load_policy_constraints_from_env()` | Pull the policy named by `CAUSALOR_POLICY_ID`. |
| `load_policy_constraints(policy_id=...)` | Pull a named policy explicitly. |
| `load_policy_bundle_file(path)` | Load an exported bundle for offline enforcement. |
| `pre_commit(user_id, action, state=...)` | Hard channel. Returns `.allowed`, `.status`, `.proofs`, `.state_snapshot_hash`. |
| `pre_commit_strict(...)` | Raises `PermissionError` on anything not a proven PASS. |
| `track_action(user_id, action_content)` | Report the action. Feeds drift detection. |
| `inject(user_id)` | Return the computed correction, or `""`. |
| `acknowledge(user_id, success=True)` | Confirm a correction was applied. |
| `register_agent(agent_id, system_prompt, tools)` | Report prompt and tool schemas for guardrail derivation. |
| `flush_telemetry(timeout_millis)` | Force buffered spans to export. |

Support: nischay@causalorlabs.com
