SDK

Agent-side SDKs.

Thin wrappers around the three runtime endpoints. Typed, retried, and shipped with offline receipt verification plus a one-shot migration tool for existing logs.

Node

@cobound/prova-sdk

npm install @cobound/prova-sdk. Requires Node 18+.

Python

prova-sdk

pip install prova-sdk. Requires Python 3.10+.

These are separate from the legacy verifier SDKs (also named prova-sdk) shipped before the control-plane pivot. The control-plane SDKs cover ingest, gateway-check, and register and replace the verifier-era client for new integrations.

Try it locally, no account

Run loop detection and cost estimation on your own machine with no API key and no network. Install the SDK, point it at a trace, read the report. Nothing leaves the process.

cli (python)

bash

pip install prova-sdk
prova-local --file trace.ndjson   # add --fail-on-loop to gate CI

The trace is newline-delimited JSON, one event per line in the shape the SDK emits. The loop algorithm is identical to the server's, so a loop you see here is the loop a signed receipt would report. When you want the signed receipt and the dashboard, send the same events to Prova. In Node, call analyzeLocal(events) for the same report in-process.

1. Send your first receipt

node

ts

import { ProvaClient } from '@cobound/prova-sdk';

const prova = new ProvaClient({ apiKey: process.env.PROVA_API_KEY! });

await prova.ingest({
  kind: 'model_call',
  source: { org_id: 'YOUR_ORG', framework: 'langgraph', app_id: 'claims-orchestrator' },
  model: { provider: 'openai', name: 'gpt-4o' },
  payload: { messages, response },
});

python

py

from prova_cp import ProvaClient

prova = ProvaClient(api_key=os.environ["PROVA_API_KEY"])
prova.ingest({
    "kind": "model_call",
    "source": {"org_id": "YOUR_ORG", "framework": "langgraph", "app_id": "claims-orchestrator"},
    "model": {"provider": "openai", "name": "gpt-4o"},
    "payload": {"messages": messages, "response": response},
})

Both clients retry transient 429 / 5xx responses with exponential backoff (4 attempts by default) and respect the per-request Idempotency-Key header. Pass verifyReceipts: true (Node) or verify_receipts=True (Python) to verify the Ed25519 signature of every returned receipt before the call resolves.

2. Check before you act

For pre-execution enforcement, call gatewayCheck / gateway_check with the prospective payload. The response carries an explicit action.

node

ts

const { action, findings } = await prova.gatewayCheck({
  kind: 'model_call',
  payload: { messages },
});
if (action === 'block') {
  throw new Error('blocked: ' + findings.map(f => f.detector).join(', '));
}

3. Catch coordination loops as they form

The callback handler (Node and Python) auto-instruments every node, LLM call, and tool call. It accumulates the agent trace, emits one agent_run receipt per run so the server-side coordination_loop detector fires, and by default logs a warning the moment a persistent loop forms. You see the loop in real time, not just later in the dashboard.

The default warns, it does not stop the run. A structural loop is also what a healthy planner and executor iteration looks like: the planner writes a plan, the executor reads it and writes a result, the planner reads the result and writes the next plan. Stopping every cycle would break agents that are working correctly. When you want the run halted, pass break_on_loop=True (Python) or breakOnLoop: true (Node) and catch the error. The run stops at the step the loop became persistent instead of running to your framework's recursion limit. The signed agent_run receipt is flushed before the exception propagates, and with cost attribution on (next section) that receipt carries exactly what the stopped run cost.

python

py

from prova_cp import ProvaCallbackHandler, CoordinationLoopError

# Default: warns the moment a loop forms, never stops the run.
handler = ProvaCallbackHandler(prova, app_id='claims-orchestrator')

# Opt in to stopping the run instead:
handler = ProvaCallbackHandler(prova, app_id='claims-orchestrator', break_on_loop=True)
try:
    graph.invoke(inputs, config={'callbacks': [handler]})
except CoordinationLoopError as e:
    log.error('stopped a loop across %s after %s steps',
              e.match['agents'], e.match['persistence_steps'])

node

ts

import { ProvaCallbackHandler, CoordinationLoopError } from '@cobound/prova-sdk';

// Default warns; pass breakOnLoop to stop the run instead.
const handler = new ProvaCallbackHandler(prova, {
  appId: 'claims-orchestrator',
  breakOnLoop: true,
});
try {
  await graph.invoke(inputs, { callbacks: [handler] });
} catch (e) {
  if (e instanceof CoordinationLoopError) {
    console.error('stopped a loop across', e.match.agents);
  }
}

4. Cap runaway spend with a budget

Pass budget_usd to the handler and the run stops the moment its estimated spend crosses the cap. A budget is a hard limit you set, so unlike loop detection it stops the run by default. Combine it with max_steps and break_on_loop and you have a circuit breaker for runaway agents in one constructor.

python

py

from prova_cp import ProvaCallbackHandler, BoundaryViolationError

handler = ProvaCallbackHandler(
    prova,
    app_id='claims-orchestrator',
    budget_usd=0.50,     # stop if the run's estimated spend exceeds $0.50
    max_steps=40,        # stop after 40 agent steps
    break_on_loop=True,  # stop on a coordination loop
)
try:
    graph.invoke(inputs, config={'callbacks': [handler]})
except BoundaryViolationError as e:
    log.error('circuit breaker tripped: %s', e.match['dimension'])

node

ts

const handler = new ProvaCallbackHandler(prova, {
  appId: 'claims-orchestrator',
  budgetUsd: 0.50,
  maxSteps: 40,
  breakOnLoop: true,
});

The local estimate uses a built-in price catalog (override with set_model_price / setModelPrice). The signed receipt carries a separate, canonical figure: the server computes payload.cost_usd from the model name and a maintained catalog and signs it, so the number Finance sees is the number an auditor verifies. The handler attaches normalized token_usage to every model_call receipt so that figure is always present. Set a monthly cap in the dashboard and the monthly_budget_cap policy blocks at the gateway too. View aggregated spend at /dashboard/spend.

5. Declare autonomy boundaries

Boundaries are a declarative contract for an agent run: which tools it may call, how many steps, how much it may spend, which data scopes it may touch. The SDK enforces in-process and raises the moment a dimension is crossed. The server-side boundary_violation policy enforces the same contract on every ingested event and produces a signed audit trail of any breach. The two implementations are pinned to one schema in lib/boundaries/schema.ts and verified by a contract test.

python

py

from prova_cp import ProvaBoundaries, ProvaCallbackHandler, BoundaryViolationError

boundaries = ProvaBoundaries(
    allowed_tools=['search', 'send_email'],
    max_steps=20,
    budget_usd_per_run=0.50,
    data_scopes=['customer_id'],
)
handler = ProvaCallbackHandler(prova, boundaries=boundaries, break_on_violation=True)
try:
    graph.invoke(inputs, config={'callbacks': [handler]})
except BoundaryViolationError as e:
    log.error('boundary breached: %s', e.match['dimension'])

6. Register an integration up front

Declare an integration before its first receipt so the AI Inventory can flag "wired up but never exercised in prod" gaps.

node

ts

await prova.register({
  app_id: 'claims-orchestrator',
  provider: 'openai',
  model_name: 'gpt-4o',
  environment: 'production',
  framework: 'langgraph',
});

7. Verify a receipt offline

The verifier recomputes the canonical hash and checks the Ed25519 signature against the public key. Pass a PEM directly or let the SDK fetch it from /api/v1/keys/{keyId}.

node

ts

import { verifyReceipt } from '@cobound/prova-sdk';
await verifyReceipt(receipt, { publicKeyPem: PUBLIC_KEY_PEM });

python

py

from prova_cp import verify_receipt
verify_receipt(receipt, public_key_pem=PUBLIC_KEY_PEM)

8. Migrate existing logs in one shot

A typed mapper bulk-imports existing observability data. Supported sources today: LangSmith run exports, Langfuse observation exports, and raw OpenAI ChatCompletion responses. Each row becomes a signed AIDecisionEvent with payload._migrated_from set to the source; idempotency keys derive from the source row id so reruns are safe.

cli (node)

bash

PROVA_API_KEY=prv_... npx prova-migrate \
  --source langsmith \
  --file runs.ndjson

cli (python)

bash

PROVA_API_KEY=prv_... prova-migrate \
  --source langfuse \
  --file observations.ndjson

Or call migrate() directly with an async iterable of parsed rows when the source isn't NDJSON on disk. The ingest endpoint accepts up to 1000 events per batch; the SDK defaults to 200 and surfaces per-batch progress through onProgress.

Any runtime: AutoGen, custom loops

No LangChain callbacks (AutoGen, a hand-rolled orchestrator)? RunGuard gives the same loop, budget, and step protection from two calls you place wherever your runtime advances. It imports no framework and makes no network calls.

python

py

from prova_cp import RunGuard, CoordinationLoopError, BoundaryViolationError

guard = RunGuard(budget_usd=0.50, max_steps=40, break_on_loop=True)
try:
    for turn in run_agent():            # AutoGen reply hook, or your own loop
        guard.observe_step(turn.agent, reads=turn.state_in, writes=turn.state_out)
        guard.observe_model_call(turn.model, turn.token_usage)
except (CoordinationLoopError, BoundaryViolationError) as e:
    log.error('circuit breaker tripped: %s', e)

node

ts

import { RunGuard } from '@cobound/prova-sdk';

const guard = new RunGuard({ budgetUsd: 0.5, maxSteps: 40, breakOnLoop: true });
for (const turn of runAgent()) {
  guard.observeStep(turn.agent, turn.stateIn, turn.stateOut);
  guard.observeModelCall(turn.model, turn.tokenUsage);
}

The loop algorithm is the same one the server runs. Send the observed events to Prova for signed receipts and the dashboard.

Which SDK should I install?

Two packages share the prova-sdk name for legacy reasons. Pick by what you're building.

  • Instrumenting agent runs, model calls, tool uses, or want signed receipts in your audit trail: install @cobound/prova-sdk (Node) or prova-sdk (Python 3.10+). This page.
  • Verifying a single reasoning chain end-to-end (the legacy verifier product): see the verifier API reference.