Metadata-Version: 2.4
Name: vorim
Version: 3.23.0
Summary: Official Python SDK for Vorim AI — AI Agent Identity, Permissions & Audit
Project-URL: Homepage, https://vorim.ai
Project-URL: Repository, https://github.com/Vorim-AI-Labs/vorim-protocol
Project-URL: Issues, https://github.com/Vorim-AI-Labs/vorim-protocol/issues
Project-URL: Documentation, https://vorim.ai/docs
Author-email: Vorim AI <hello@vorim.ai>
License-Expression: MIT
Keywords: agent-identity,ai-agent,audit,crewai,ed25519,langchain,openai,permissions,trust,vorim
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: cryptography>=42
Requires-Dist: httpx>=0.25.0
Provides-Extra: all
Requires-Dist: anthropic>=0.30.0; extra == 'all'
Requires-Dist: crewai>=0.80.0; extra == 'all'
Requires-Dist: langchain-core>=0.3.0; extra == 'all'
Requires-Dist: openai>=1.0.0; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.30.0; extra == 'anthropic'
Provides-Extra: crewai
Requires-Dist: crewai>=0.80.0; extra == 'crewai'
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.22; extra == 'dev'
Provides-Extra: hermes
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.3.0; extra == 'langchain'
Provides-Extra: openai
Requires-Dist: openai>=1.0.0; extra == 'openai'
Description-Content-Type: text/markdown

# vorim

Official Python SDK for [Vorim AI](https://vorim.ai) — the identity, permissions, and audit layer for AI agents.

Vorim AI provides cryptographic agent identities (Ed25519), fine-grained permissions (7 scopes), immutable audit trails, and trust scoring (0-100) for production AI agent deployments. Built for the traceability and audit-trail obligations of frameworks like the EU AI Act.

[![PyPI](https://img.shields.io/pypi/v/vorim)](https://pypi.org/project/vorim/)
[![Python](https://img.shields.io/pypi/pyversions/vorim)](https://pypi.org/project/vorim/)
[![License](https://img.shields.io/pypi/l/vorim)](https://github.com/Vorim-AI-Labs/vorim-protocol/blob/main/LICENSE)

> **API key**: create one in the Vorim dashboard under Settings, API keys. No account yet? Request access at [vorim.ai](https://vorim.ai).
> **[Documentation](https://vorim.ai/docs)** — Full API reference, framework integrations, and examples.
> **[Quick Start](https://vorim.ai/quickstart)** — Set up in under 5 minutes.

## Install

Requires Python 3.10+.

```bash
pip install vorim
```

With framework integrations (keep the quotes, zsh treats unquoted square brackets as a glob):

```bash
pip install "vorim[langchain]"    # LangChain / LangGraph
pip install "vorim[crewai]"       # CrewAI
pip install "vorim[openai]"       # OpenAI Agents SDK
pip install "vorim[all]"          # All integrations
```

## Quick Start

The key for this quick start needs the `agents:read`, `agents:write`, `permissions:read`, `audit:read` and `audit:write` scopes. Scopes are independent, so `agents:write` does not include `agents:read`.

```python
from vorim import Vorim

vorim = Vorim(api_key="agid_sk_live_...")

# Register an agent (returns Ed25519 keypair, private key shown once)
result = vorim.register(
    name="invoice-processor",
    capabilities=["read_documents", "extract_data"],
    scopes=["agent:read", "agent:execute"],
)
print(result.agent.agent_id)    # agid_acme_a1b2c3d4
print(result.agent.trust_score) # 50

# Check permissions (<5ms via Redis)
check = vorim.check(result.agent.agent_id, "agent:execute")

if check.allowed:
    # Emit audit event
    vorim.emit(
        agent_id=result.agent.agent_id,
        event_type="tool_call",
        action="process_invoice",
        resource="INV-2026-0042",
        result="success",
        latency_ms=142,
    )

# Verify any agent's trust (public, no auth required)
trust = vorim.verify(result.agent.agent_id)
print(f"Trust score: {trust.trust_score}/100")
```

## Async Client

```python
import asyncio

from vorim import AsyncVorim


async def main():
    async with AsyncVorim(api_key="agid_sk_live_...") as vorim:
        result = await vorim.register(
            name="my-agent",
            capabilities=["search"],
            scopes=["agent:read"],
        )

        trust = await vorim.verify(result.agent.agent_id)
        print(f"Trust score: {trust.trust_score}/100")


asyncio.run(main())
```

## API Reference

### `Vorim(api_key, base_url?, timeout?, auto_sign=True)`

| Method | Description |
|--------|-------------|
| `register(name, capabilities, scopes)` | Register an agent; caches the returned Ed25519 private key for auto-signing |
| `check(agent_id, scope)` | Check if agent has permission (sub-5ms) |
| `emit(agent_id, event_type, action, ..., sign=None)` | Emit an audit event. Auto-signed if the agent's key is in the keyring |
| `emit_batch(events, sign=None)` | Emit up to 1,000 audit events. Auto-signs each one |
| `verify(agent_id)` | Verify agent identity and trust score (public) |
| `get_agent(agent_id)` | Get agent details |
| `list_agents(status?, page?, per_page?)` | List agents with filtering |
| `revoke(agent_id)` | Permanently revoke an agent |
| `grant(agent_id, scope, valid_until?, rate_limit?)` | Grant a permission scope |
| `use_agent_key(agent_id, private_key_pem)` | Restore a private key into the in-memory keyring after process restart |
| `forget_agent_key(agent_id)` | Remove a private key from the keyring |

Module-level helpers: `canonical_payload_v1(event)` and `canonical_payload_v0(event)` (return the bytes that get signed) and `sign_payload(payload, private_key_pem)` (returns `ed25519:<base64>`).

`AsyncVorim` has the same interface with `await` on all methods.

### Per-event signing (auto-signing)

From v3.7.0, every audit event is signed at source with the agent's Ed25519 private key — no code change required. `register()` caches the returned key in memory and `emit()` attaches the signature transparently.

**Canonical form.** Since 3.7.0 the SDK defaults to **v1** canonical form: RFC 8785 JSON Canonicalization Scheme (JCS) over the whole event minus `signature` and `canonical_form`. v1 brings metadata, replayable-evidence fields (`model_version`, `tool_catalogue_hash`, `system_prompt_hash`, `prev_event_hash`), and delegation context (`on_behalf_of`, `delegator_agent_id`, `delegation_chain_id`, `delegation_depth`) under the signature. The previous v0 form was a pipe-joined six-field string `event_type|action|resource|input_hash|output_hash|result` and is now deprecated — passing `canonical_form="v0"` explicitly still works for verifier-compat scenarios but logs a deprecation warning. Use `@vorim/verify@0.2.0+` (or the v1 helpers in this package) to verify v1 events offline.

```python
result = vorim.register(name="agent", capabilities=[], scopes=["agent:execute"])

# Auto-signed. The signature is attached before the request leaves the process.
vorim.emit(
    agent_id=result.agent.agent_id,
    event_type="tool_call",
    action="transfer_funds",
    result="success",
)
```

To verify signatures server-side, the API operator sets `VORIM_VERIFY_AUDIT_SIGNATURES=true`.

**Restoring keys across process restarts.** The in-memory keyring is lost on restart. Load the private key from your secret store:

```python
vorim.use_agent_key(agent_id, private_key_pem)
vorim.forget_agent_key(agent_id)  # revoke from memory
```

**Opting out.** Per event with `sign=False`, or globally with `auto_sign=False`:

```python
vorim.emit(agent_id=..., event_type=..., action=..., result=..., sign=False)
vorim = Vorim(api_key=..., auto_sign=False)
```

### HTTP Message Signatures (RFC 9421)

An agent can sign the HTTP requests it sends, so the service on the other end can check which agent made the call and that nobody changed it on the way. The signature follows RFC 9421 and goes in the `Signature-Input` and `Signature` headers. When the request has a body, the SDK adds an RFC 9530 `Content-Digest` header and puts it under the signature, which ties the body to it too.

Signing uses the key that `register()` or `use_agent_key()` cached for that agent. `keyid` is the agent_id, the algorithm is `ed25519`, and `tag` is `vorim`.

```python
import json
import httpx

body = json.dumps({"task": "summarise", "doc_id": "d_91"})
request = {
    "method": "POST",
    "url": "https://agent-b.example.com/tasks",
    "headers": {"content-type": "application/json"},
    "body": body,
}

signed = vorim.sign_http_request(agent_id, request)
httpx.post(request["url"], headers={**request["headers"], **signed["headers"]}, content=body)
```

On the receiving side, `verify_http_request` sends the request to Vorim. Vorim looks up the signing agent's registered public key and refuses agents that are suspended or revoked. The result also carries the agent's org name, status and trust score. Pass the raw body if you want the digest checked; `content_digest_checked` tells you whether it was.

```python
result = vorim.verify_http_request(
    {"method": req.method, "url": str(req.url), "headers": dict(req.headers), "body": raw_body},
    max_age_seconds=300,
)
if not result["valid"]:
    raise PermissionError(result["reason"])
print(result["agent_id"], result["trust_score"])
```

You can also verify locally with a public key you already hold, with no call to Vorim. The module also signs with ECDSA P-256 keys (`ecdsa-p256-sha256`), which is the curve iOS Secure Enclave and Android StrongBox keys use.

```python
from vorim.http_signatures import verify_http_request

result = verify_http_request(
    request,
    public_key=agent_public_key_pem,  # SPKI PEM, Ed25519 or P-256
    max_age_seconds=300,
    required_components=["@method", "@target-uri", "content-digest"],
)
```

`AsyncVorim` has the same two methods; `verify_http_request` is awaitable there. Signatures are byte-compatible with the TypeScript SDK, and the module passes the Ed25519 test vector in RFC 9421 Appendix B.2.6. It supports every derived component in the RFC, and the `req`, `name` and `bs` component parameters. `sf`, `key` and `tr` raise an error because they aren't supported yet.

### Runtime Control (gate actions before they happen)

Ask Vorim whether an action should proceed **before** your agent performs it.
`before_action()` returns a typed decision and, by default, **raises on deny**
(a denial is in the response body, not the HTTP status).

```python
from vorim import Vorim, VorimDeniedError

vorim = Vorim(api_key="agid_sk_live_...")

try:
    decision = vorim.before_action(
        agent_id="agid_acme_a1b2c3d4",      # always the public agid_* id
        action_type="tool_call",
        action_target="sendEmail",
        required_scope="agent:communicate",
        payload={"to": "customer@example.com", "body": "..."},
    )

    payload = decision.modified_payload or {"to": "customer@example.com"}

    if decision.decision in ("allow", "modify"):
        send_email(payload)
    elif decision.decision == "escalate":
        resolved = vorim.wait_for_decision_resolution(decision.decision_id)
        if resolved.decision == "allow":
            send_email(payload)

    # Link the post-action audit event back to the decision.
    vorim.emit(
        agent_id="agid_acme_a1b2c3d4",
        event_type="tool_call",
        action="sendEmail",
        result="success",
        decision_id=decision.decision_id,   # correlates audit ↔ decision
    )
except VorimDeniedError as err:
    print("Action denied:", err.decision.reason)
```

**Verdicts:** `allow` · `deny` (raises `VorimDeniedError` by default) ·
`modify` (use `decision.modified_payload`) · `escalate` (poll
`wait_for_decision_resolution`) · `fallback`.

> **`modify` is client-cooperative.** Vorim returns the sanitised
> `modified_payload`; your agent must send it in place of the original. The
> platform does not sit inline and does not currently enforce that you do —
> carry `decision_id` into the matching `emit()` so the action stays auditable.

**Fail-open:** if the decision API is unreachable, `before_action()` returns a
synthetic `fallback` decision. Pass `runtime_fail_open=False` to the constructor
to fail closed. A reachable server returning `deny` always denies. `AsyncVorim`
exposes the same async methods.

> Requires an API key with the `runtime:decide` scope and a Growth+ plan.
> `modify` verdicts come from policy rules provisioned by Vorim (rule-authoring
> API ships in a later release).

### Permission Scopes

| Scope | Description |
|-------|-------------|
| `agent:read` | Read data on behalf of owner |
| `agent:write` | Write or modify data |
| `agent:execute` | Trigger actions or tool calls |
| `agent:transact` | Financial or contractual actions |
| `agent:communicate` | Send messages or emails |
| `agent:delegate` | Sub-delegate to other agents |
| `agent:elevate` | Request permission elevation |

## Payload Privacy

Your prompts and model outputs never have to reach Vorim. The audit event carries
a digest of them, computed in your process.

A bare SHA-256 of a prompt is not a commitment, it is a lookup key: prompts are
low-entropy and usually templated, so anyone holding the digest and the template
recovers the content by trying candidates. Use a keyed commitment for anything a
human or a model wrote.

```python
from vorim import Vorim, commit_payload

key = os.environ["VORIM_COMMITMENT_KEY"]   # never sent to Vorim

v.emit(
    agent_id=agent.agent_id,
    event_type="tool_call",
    action="invoice.refund",
    result="success",
    input_hash=commit_payload(prompt, key),        # hmac-sha256:...
    output_hash=commit_payload(completion, key),
)
```

Keep `hash_payload` for content that is already high-entropy. To disclose later,
hand the verifier the content and the key; they recompute and compare.

Refuse to send content at all:

```python
v = Vorim(api_key=..., no_payload=True)          # no_payload_max_metadata_chars=256
v.emit(..., metadata={"prompt": full_prompt})    # raises VorimError PAYLOAD_BLOCKED
```

Not the same as the server-side Zero Data Retention setting, which drops metadata
when we persist it, after the content has crossed the network. This means it never
left your process.

Every emit returns a stable content digest beside the event id, and
`event_digest(event)` recomputes it locally. Byte-identical to `@vorim/sdk` and
`@vorim/verify`.

## Framework Integrations

### LangChain / LangGraph

```python
from vorim import Vorim
from vorim.integrations.langchain import vorim_tool, VorimCallbackHandler

vorim = Vorim(api_key="agid_sk_live_...")

@vorim_tool(vorim, agent_id="agid_acme_...", permission="agent:execute")
def search(query: str) -> str:
    """Search documents."""
    return f"Results for {query}"

# search() is now a standard LangChain tool with built-in permission checks + audit
```

### CrewAI

```python
from vorim import Vorim
from vorim.integrations.crewai import register_crew

vorim = Vorim(api_key="agid_sk_live_...")

crew = register_crew(vorim, {
    "crew_name": "content-pipeline",
    "members": [
        {
            "role": "researcher",
            "name": "crew-researcher",
            "capabilities": ["web_search"],
            "scopes": ["agent:read", "agent:execute"],
        },
    ],
})
```

### OpenAI Function Calling

```python
from openai import OpenAI
from vorim import Vorim
from vorim.integrations.openai_agents import VorimToolRegistry

vorim = Vorim(api_key="agid_sk_live_...")
client = OpenAI()

registry = VorimToolRegistry(vorim=vorim, agent_id="agid_acme_...")
registry.add(
    name="search",
    description="Search documents",
    parameters={"type": "object", "properties": {"query": {"type": "string"}}},
    execute=lambda args: f"Results for {args['query']}",
)

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Search for AI papers"}],
    tools=registry.to_openai_tools(),
)

# Permission checked + audited automatically
tool_messages = registry.execute_tool_calls(
    response.choices[0].message.tool_calls or []
)
```

## Resources

- [Full Documentation](https://vorim.ai/docs)
- [Protocol Specification](https://github.com/Vorim-AI-Labs/vorim-protocol)
- [TypeScript SDK (@vorim/sdk)](https://www.npmjs.com/package/@vorim/sdk)

## License

MIT
