Metadata-Version: 2.4
Name: causalor
Version: 0.2.2
Summary: Runtime governance for AI agents: block an out-of-policy action before it runs, with a proof that reproduces exactly.
Author: Causalor Labs
License: MIT
Project-URL: Homepage, https://causalorlabs.com
Project-URL: Documentation, https://causalorlabs.com/quickstart
Project-URL: Pricing, https://causalorlabs.com/pricing
Keywords: ai,agent,governance,policy,guardrails,telemetry
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: opentelemetry-api>=1.24.0
Requires-Dist: opentelemetry-sdk>=1.24.0
Requires-Dist: opentelemetry-exporter-otlp>=1.24.0
Provides-Extra: redis
Requires-Dist: redis>=5.0.0; extra == "redis"
Provides-Extra: kafka
Requires-Dist: confluent-kafka>=2.3.0; extra == "kafka"

# Causalor

Runtime governance for AI agents. Two layers of one control:

- **Block.** `pre_commit()` checks a proposed action against typed constraints and
  refuses it before it executes, returning a proof of why. Runs in your process.
  No network call, no model call, no account.
- **Correct.** `track_action()` reports what the agent did, and `inject()` returns
  a correction for its next turn when it has moved off policy, so the
  out-of-policy action is often never proposed. You do not diagnose the drift or
  write the fix.

```bash
pip install causalor
```

The published package is `causalor`. `causalor-sdk` is an internal development
package and is not the one to install.

## Block an action, without an account

This runs offline. No API key, no signup, nothing reaches a server.

```python
from causalor import Causalor, AgentStateSchema, FormalConstraint

schema = AgentStateSchema(version="1.0.0")
schema.register_field("refund_amt", "number")

c = Causalor(agent_id="refund-agent")
c.set_state_schema(schema)
c.register_constraints([FormalConstraint(
    constraint_id="refund_ceiling", variable="refund_amt",
    operator="<=", threshold=500, scope="SAFETY_CONSTRAINT",
)], replace=True)

# the agent decides to refund 900 against a 500 ceiling
result = c.pre_commit("customer-42", "issue_refund", state={"refund_amt": 900})

print(result.allowed)                    # False
print(result.proofs[0].proof_string)     # refund_amt=900 <= 500 -> False
print(result.state_snapshot_hash)
print(result.proofs[0].proof_fingerprint)
```

Output:

```
False
refund_amt=900 <= 500 -> False
sha256:v1:766ccc520ddeb830efe963d4b45434ab38e58dfd2ce41ffb28e59ad86d1e8586
sha256:v1:eb2c6c2242180923744ba837d376e58601bb02efeeebf60b57dc7aed754ba608
```

Those two hashes are not examples. Run the snippet and you will get the same
values, on your machine, today or in a year, because the verdict is computed from
the state and the rule rather than inferred by a model. If you get something
else, that is a bug worth reporting.

(`evaluated_at` is a wall-clock timestamp and does change. The hashes do not.)

## Correct drift while the agent is still reasoning

Correction needs the control plane, so three environment variables and nothing
else:

```bash
CAUSALOR_API_KEY=cal_...                  # shown once on first login
CAUSALOR_API_URL=https://gw.causalorlabs.com
CAUSALOR_POLICY_ID=your-policy            # the policy you activated in the console
```

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

c.track_action("customer-42", agent_action)   # session id first
correction = c.inject("customer-42")          # "" when nothing is wrong
if correction:
    prompt += correction
```

You can also start with nothing enforced: point a live agent at Causalor with no
limits set and it reports where the agent drifted and which limits it would have
crossed, on your real traffic, before you turn anything on.

## API

| Method | Purpose |
| --- | --- |
| `Causalor(agent_id=, api_url=, api_key=, tenant_id=)` | Key and URL fall back to the environment |
| `set_state_schema(schema)` | Declare the fields rules may reference |
| `register_constraints(constraints, replace=False)` | Register rules locally |
| `load_policy_constraints_from_env(required=None)` | Pull the activated policy instead |
| `pre_commit(user_id, action_name, state=)` | Check before the action runs |
| `track_action(user_id, action_content)` | Report what the agent did |
| `inject(user_id)` | Get a correction for the next turn |
| `register_agent(agent_id, system_prompt, tools=)` | Report an off-path agent |

`pre_commit` returns `allowed`, `status`, `proofs`, `evaluated_constraints` and
`state_snapshot_hash`.

## Three things that cause most integration bugs

- **`track_action(user_id, action_content)` takes the session id first.** Passing
  the action text first silently breaks drift detection rather than erroring.
- **Do not configure OpenTelemetry.** The SDK installs its own exporter from
  `CAUSALOR_API_URL`, and leaves an existing tracer provider alone if it finds
  one. Set `CAUSALOR_AUTO_OTEL=false` to opt out.
- **Do not call `register()`.** Use `register_agent(...)`.

## Deployment routes

The same engine behind all three, so you can move between them without
re-authoring anything:

- **In-process SDK**, this package. Nothing in your model path.
- **Sidecar**, one container in your VPC, so model traffic never leaves it.
- **Hosted gateway**, point an OpenAI or Anthropic client `base_url` at
  `https://gw.causalorlabs.com/v1`.

## Links

- Quickstart: <https://causalorlabs.com/quickstart>
- Plain-markdown quickstart for coding agents: <https://causalorlabs.com/quickstart.md>
- Pricing: <https://causalorlabs.com/pricing>

Blocking is free permanently, on every plan.
