Skip to main content

Vendor-neutral control plane for AI agents — governance, audit, and (soon) runtime enforcement.

Project description

Veragent Python SDK

The client library that makes Veragent a vendor-neutral control plane instead of a single bespoke integration. Instrument any agent — custom, LangChain, LangGraph, OpenAI Agents SDK, MCP, and CrewAI — in a couple of lines, and have its activity captured in one place under your policies.

The core SDK is dependency-free (standard library only). Framework adapters pull in just what they need.

Install

pip install veragent                 # core
pip install "veragent[langchain]"    # + LangChain adapter
pip install "veragent[openai-agents]" # + OpenAI Agents SDK adapter
pip install "veragent[mcp]"           # + MCP interceptor
pip install "veragent[crewai]"        # + CrewAI adapter

Quick start

from veragent import Veragent

va = Veragent(agent="my-agent")      # reads VERAGENT_API_KEY from the environment
va.track("task completed", severity="info")

Custom agents — decorator + run correlation

@va.track_action("broker.place_order")
def place_order(symbol, size): ...

with va.run("nightly-cycle") as run:
    run.track("fetched markets", severity="info")
    place_order("AAPL", 10)          # captured automatically, errors included

LangChain / LangGraph — no changes to your agent

from veragent.integrations.langchain import VeragentCallback

chain.invoke(payload, config={"callbacks": [VeragentCallback(va)]})

Every LLM call, tool call, and error in the run is captured and normalized. Tool calls — the actions with side effects — are tracked as event_type="tool_call", which is the surface that matters for governance.

OpenAI Agents SDK — no changes to your agent

from veragent.integrations.openai_agents import instrument

instrument(va)   # register once, before running any agents
# ... build Agents and call Runner.run() exactly as before ...

instrument(va) adds a tracing processor, so every run, generation, tool/function call, handoff, and guardrail is captured. It's additive — your existing OpenAI tracing keeps working. Function (tool) calls are tracked as event_type="tool_call".

MCP — capture (and optionally enforce) tool calls

from veragent.integrations.mcp import instrument_mcp

instrument_mcp(session, va)                 # capture every tool call on this session
# instrument_mcp(session, va, enforce=True) # also gate each call via /api/authorize

MCP is the framework-agnostic tool layer, so instrumenting session.call_tool captures the governable surface no matter what drives the session. With enforce=True, each call is authorized first and a denied call is blocked before the real tool runs (it returns an isError result). Enforcement stays inert until the client's enforcement_enabled is on, so it's safe to wire in now.

CrewAI — no changes to your crew

from veragent.integrations.crewai import instrument

instrument(va)   # register once, before kicking off any crew
# ... build your Crew and call crew.kickoff() exactly as before ...

instrument(va) registers a listener on CrewAI's event bus, so every crew, task, agent, tool, and LLM event is captured. Tool calls are tracked as event_type="tool_call".

Authorize — ask permission before acting

va.authorize(...) is the pre-action decision point. It calls the live /api/authorize endpoint with the same agent name + API key you configured the client with — you never re-pass credentials. It stays inert (reporting-only, allowed=True) until you turn enforcement on, so it's safe to wire in first:

va = Veragent(agent="crypto-paper-trader", enforcement_enabled=True)

Blocking (the simple case)

decision = va.authorize(
    "polymarket.place_order",
    context={"market": "BTC-100k", "size": 250},
)

if decision.allowed:
    place_order(...)            # ✅ authorized
else:
    # MUST handle the not-allowed branch — these mean different things:
    if decision.status == "denied":
        log.warning("blocked by policy: %s", decision.reason)
    elif decision.status == "timed_out":
        log.warning("no human answered in time; server fail mode applied")
    elif decision.status == "error":
        log.error("couldn't reach Veragent: %s", decision.reason)
    skip_or_rollback()

If the action is auto-decided, the answer comes back on the first call with zero added latency. If it escalates to a human, authorize() blocks and polls until the decision is terminal or timeout_seconds (default 300, server clamps 10–3600) elapses.

Async (high-throughput loops)

Don't block a hot loop on a human. Fire the request, do other work, poll later:

decision = va.authorize("refund.issue", context={"order": "A-1001"}, wait="async")
# returns immediately — terminal OR status="pending"

# ...later, poll yourself:
decision = va.get_decision(decision.decision_id)
if decision.status == "pending":
    ...        # still waiting on a human; check again later
elif decision.allowed:
    issue_refund(...)

The decision object

Decision(
    allowed: bool,         # the one thing you must check
    status: str,           # "allowed" | "denied" | "timed_out" | "error" | "pending"
    decision_id: str,
    reason: str,
    decided_by: str | None,
    resolved_at: str | None,
)

if decision: works too (__bool__ returns allowed).

Treat authorize() as fallible. It is a network call. A client-side inability to reach Veragent returns status="error", allowed=False (fail-safe — never silently "allowed"), which is not the same as a policy denied. Distinguish denied (a real no) from timed_out (nobody answered → the server's fail mode decided) from error (couldn't get an answer at all). Prefer wait="async" for high-throughput loops, and keep privileged/irreversible actions late and rollback-able so a denial or error is cheap to honor.

Design guarantees

  • Safe. track() only enqueues; all network I/O is on a background worker. Instrumentation never blocks, slows, or crashes the host agent, and never raises into your code path. If Veragent is unreachable, events are dropped with a log line — your agent keeps running.
  • Redaction-first. Inputs and outputs pass through a redactor before they leave the process — sensitive keys (passwords, tokens, emails, IBANs, card numbers, …) are stripped and long strings truncated. On by default. Pass redact=False to disable or supply your own callable. This is what keeps customer data out of governance telemetry.
  • Enforcement-ready. va.authorize(action, context=...) is the pre-action decision point, calling the live /api/authorize endpoint. It is safe to wire in today — while enforcement_enabled is off it returns allowed=True (reporting-only); turn it on to get real allow/deny verdicts. Unlike track(), authorize failures are legible, not silent: if Veragent can't be reached you get status="error", allowed=False so the agent never proceeds thinking it was authorized. See Authorize.

Event envelope

Backward-compatible with the current ingest contract. agent / action / severity stay at the top level (read by the server today); the structured envelope rides in metadata:

{
  "agent": "my-agent",
  "action": "tool call: refund",
  "severity": "info",
  "metadata": {
    "event_type": "tool_call",
    "tool": "refund",
    "inputs": { "order_id": "A-1001", "amount": 49.0 },
    "run_id": "…", "span_id": "…",
    "ts": "2026-…", "sdk": "veragent-python/0.2.0"
  }
}

Adapter roadmap

Adapter Status
Custom agents (decorator, run() context, track()) ✅ shipped
LangChain / LangGraph (VeragentCallback) ✅ shipped
OpenAI Agents SDK (tracing processor) ✅ shipped
CrewAI (event-bus listener) ✅ shipped
MCP interceptor (capture + enforce) ✅ shipped
OpenTelemetry / OpenInference exporter (zero-code for OTel shops) ▢ next
TypeScript / Node SDK (parity) ▢ next

One server-side dependency

The SDK sends agent / action / severity (works against production now) plus a metadata object. To persist the structured fields, the ingest-event Supabase function needs to write the incoming metadata into audit_logs.metadata (the column already exists). Until then the richer data is sent but not stored — the SDK needs no change when that lands.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

veragent-0.3.1.tar.gz (31.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

veragent-0.3.1-py3-none-any.whl (27.5 kB view details)

Uploaded Python 3

File details

Details for the file veragent-0.3.1.tar.gz.

File metadata

  • Download URL: veragent-0.3.1.tar.gz
  • Upload date:
  • Size: 31.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for veragent-0.3.1.tar.gz
Algorithm Hash digest
SHA256 dd8b2d60cae6bfd6b00ab2b7e8ab2d9a7ac4f71cb0372e4f0ad099bab62cc97d
MD5 890d5f084c825ddba91410d525b1b44f
BLAKE2b-256 44c13304d2f67da6c6229906f07bdb8d5d0b5a5e7164a0d1c3943dd435862648

See more details on using hashes here.

File details

Details for the file veragent-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: veragent-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 27.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for veragent-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 0c66ed8de361c8a01fd2e4b6243b08ffa2ba2a0115db2114fbe923f855251c69
MD5 d1fef5fe96dbe24b53ac3e63984cdd1a
BLAKE2b-256 e22f3fab545733bf25e8deae6e2e8c531d31adcbc46958201c6bcec67194c302

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page