# Access Activity Source: https://docs.rotavision.com/api-reference/access/access-activity /api-reference/openapi.json get /api/v1/admin/access-activity The attested log of access-control changes — every rule and toggle edit, who made it and when. 'Who can approve' is change-controlled and evidenced. # Create Rule Source: https://docs.rotavision.com/api-reference/access/create-rule /api-reference/openapi.json post /api/v1/admin/entitlement-rules # Delete Rule Source: https://docs.rotavision.com/api-reference/access/delete-rule /api-reference/openapi.json delete /api/v1/admin/entitlement-rules/{rule_id} # Get Access Config Source: https://docs.rotavision.com/api-reference/access/get-access-config /api-reference/openapi.json get /api/v1/admin/access-config # List Rules Source: https://docs.rotavision.com/api-reference/access/list-rules /api-reference/openapi.json get /api/v1/admin/entitlement-rules # Observed Claims Source: https://docs.rotavision.com/api-reference/access/observed-claims /api-reference/openapi.json get /api/v1/admin/observed-claims Distinct IdP role/group names seen from real logins — seeds the wizard so an admin maps what their IdP actually sends, not a blank form. Names only. # Platform Posture Source: https://docs.rotavision.com/api-reference/access/platform-posture /api-reference/openapi.json get /api/v1/admin/platform Read-only view of the deployment's effective, NON-SECRET configuration — so an admin can verify posture without shell access, and cite it as evidence. Secret VALUES are never returned (only 'configured' / 'not'); these settings are deploy-time env (SAKSHI_*), not runtime-editable here. # Resolve Draft Source: https://docs.rotavision.com/api-reference/access/resolve-draft /api-reference/openapi.json post /api/v1/admin/entitlement-rules/resolve-draft Dry-run against DRAFT (unsaved) rules — the wizard previews a sample user before anything is written. No DB read; resolves the provided rules only. # Resolve Preview Source: https://docs.rotavision.com/api-reference/access/resolve-preview /api-reference/openapi.json post /api/v1/admin/entitlement-rules/resolve Dry-run: given a sample user's raw claims, show the entitlements the current ruleset would grant — the wizard's preview (Slice C uses this). # Update Access Config Source: https://docs.rotavision.com/api-reference/access/update-access-config /api-reference/openapi.json patch /api/v1/admin/access-config # Update Rule Source: https://docs.rotavision.com/api-reference/access/update-rule /api-reference/openapi.json patch /api/v1/admin/entitlement-rules/{rule_id} # Agent Change Source: https://docs.rotavision.com/api-reference/behavior/agent-change /api-reference/openapi.json get /api/v1/behavior/agents/{agent_id}/change Per-dimension behavioural drift for one agent: the last `window` decisions vs the `window` before them. # Create Report Source: https://docs.rotavision.com/api-reference/behavior/create-report /api-reference/openapi.json post /api/v1/behavior/agents/{agent_id}/report Persist a signed behavioural-change report. A SILENT change (a flagged dimension no governance event explains) is the unauthorised-change finding: it flags the agent for re-certification and emits an agent_behavior_changed webhook. A governance act — producing the signed artifact is a sign-off. # Export Report Source: https://docs.rotavision.com/api-reference/behavior/export-report /api-reference/openapi.json get /api/v1/behavior/reports/{report_id}/export The Ed25519-signed change report — the tamper-evident unauthorised-change artifact (signature covers the exact payload bytes). # Fleet Change Source: https://docs.rotavision.com/api-reference/behavior/fleet-change /api-reference/openapi.json get /api/v1/behavior/fleet Behavioural-change summary across the fleet — changed agents first. # Get Report Source: https://docs.rotavision.com/api-reference/behavior/get-report /api-reference/openapi.json get /api/v1/behavior/reports/{report_id} # List Reports Source: https://docs.rotavision.com/api-reference/behavior/list-reports /api-reference/openapi.json get /api/v1/behavior/reports # Activate Kill Switch Source: https://docs.rotavision.com/api-reference/bound/activate-kill-switch /api-reference/openapi.json post /api/v1/bound/kill # Attest Drill Source: https://docs.rotavision.com/api-reference/bound/attest-drill /api-reference/openapi.json post /api/v1/bound/kill/events/{activation_id}/attestation Assemble the drill attestation from the event timeline: activation, what was blocked while halted, release. Para 43/60(ii) evidence — an untested kill switch is a compliance claim, not a control. # Claim Review Source: https://docs.rotavision.com/api-reference/bound/claim-review /api-reference/openapi.json post /api/v1/bound/reviews/{review_id}/claim Marks when a reviewer actually started looking — time-on-task telemetry (#20) measures claim->resolve, not create->resolve. # Create Breaker Source: https://docs.rotavision.com/api-reference/bound/create-breaker /api-reference/openapi.json post /api/v1/bound/agents/{agent_id}/breakers # Create Envelope Source: https://docs.rotavision.com/api-reference/bound/create-envelope /api-reference/openapi.json post /api/v1/bound/agents/{agent_id}/envelope New envelope version; prior versions deactivate but are never deleted — the envelope in force at any past decision stays reconstructable. # Evaluate Action Source: https://docs.rotavision.com/api-reference/bound/evaluate-action /api-reference/openapi.json post /api/v1/bound/evaluate Route one intended action through the agent's active envelope. Every call is persisted — routing itself is evidence (paras 60–63). # Fleet Safety Source: https://docs.rotavision.com/api-reference/bound/fleet-safety /api-reference/openapi.json get /api/v1/bound/safety Fleet-wide safety posture in one view: every agent's halt state + its armed circuit breakers (per-agent detail lives on the agent page). The kill/drill LOG is /kill/events. # Get Active Envelope Source: https://docs.rotavision.com/api-reference/bound/get-active-envelope /api-reference/openapi.json get /api/v1/bound/agents/{agent_id}/envelope # Get Active Envelope Lint Source: https://docs.rotavision.com/api-reference/bound/get-active-envelope-lint /api-reference/openapi.json get /api/v1/bound/agents/{agent_id}/envelope/lint Formal-lint verdict for the agent's ACTIVE envelope (#72). # Get Evaluation Source: https://docs.rotavision.com/api-reference/bound/get-evaluation /api-reference/openapi.json get /api/v1/bound/evaluations/{evaluation_id} # Get Response Policy Source: https://docs.rotavision.com/api-reference/bound/get-response-policy /api-reference/openapi.json get /api/v1/bound/agents/{agent_id}/response-policy The configured policy (if any) plus the effective response — absent a policy, blast radius decides, and the caller should see that too. # Get Review Source: https://docs.rotavision.com/api-reference/bound/get-review /api-reference/openapi.json get /api/v1/bound/reviews/{review_id} # Kill Status Source: https://docs.rotavision.com/api-reference/bound/kill-status /api-reference/openapi.json get /api/v1/bound/kill/status The SDK's polling endpoint (#25): cheap, cacheable, no side effects. # Lint Envelope Spec Source: https://docs.rotavision.com/api-reference/bound/lint-envelope-spec /api-reference/openapi.json post /api/v1/bound/lint-envelope Formally lint an envelope spec WITHOUT publishing (#72) — check for dead rules, escalation-monotonicity violations, and default reach before it governs anything. # List Breakers Source: https://docs.rotavision.com/api-reference/bound/list-breakers /api-reference/openapi.json get /api/v1/bound/agents/{agent_id}/breakers # List Envelope Versions Source: https://docs.rotavision.com/api-reference/bound/list-envelope-versions /api-reference/openapi.json get /api/v1/bound/agents/{agent_id}/envelope/versions # List Evaluations Source: https://docs.rotavision.com/api-reference/bound/list-evaluations /api-reference/openapi.json get /api/v1/bound/evaluations # List Kill Events Source: https://docs.rotavision.com/api-reference/bound/list-kill-events /api-reference/openapi.json get /api/v1/bound/kill/events # List Reviews Source: https://docs.rotavision.com/api-reference/bound/list-reviews /api-reference/openapi.json get /api/v1/bound/reviews # Meta Source: https://docs.rotavision.com/api-reference/bound/meta /api-reference/openapi.json get /api/v1/bound/_meta # Release Kill Switch Source: https://docs.rotavision.com/api-reference/bound/release-kill-switch /api-reference/openapi.json post /api/v1/bound/kill/release # Request Handoff Source: https://docs.rotavision.com/api-reference/bound/request-handoff /api-reference/openapi.json post /api/v1/bound/handoff Customer-triggered human handoff (#65, RBI draft 59(iii)): the agent runtime calls this when a customer asks for a human. Creates a HANDOFF queue item in the agent's org unit — a review a customer requested is still a review, worked in the same queues, and its existence is the disclosure-control evidence. # Resolve Review Source: https://docs.rotavision.com/api-reference/bound/resolve-review /api-reference/openapi.json post /api/v1/bound/reviews/{review_id}/resolve # Run Breaker Check Source: https://docs.rotavision.com/api-reference/bound/run-breaker-check /api-reference/openapi.json post /api/v1/bound/breakers/check Evaluate enabled breakers now; any that trip fire the SAME kill path with actor 'breaker:'. Also run periodically in-process — this endpoint exists for drills, tests, and external schedulers. # Set Response Policy Source: https://docs.rotavision.com/api-reference/bound/set-response-policy /api-reference/openapi.json put /api/v1/bound/agents/{agent_id}/response-policy Append-only like envelopes: a change is a new version. Provenance pin is deliberately absent — that is a human recovery decision made during the investigation, never a standing automatic response. # Telemetry Source: https://docs.rotavision.com/api-reference/bound/telemetry /api-reference/openapi.json get /api/v1/bound/telemetry Override rates, time-on-task, escalation and deep-review disagreement per reviewer and per agent — the anti-rubber-stamp evidence (paras 61-63). # Telemetry Usage Source: https://docs.rotavision.com/api-reference/bound/telemetry-usage /api-reference/openapi.json get /api/v1/bound/telemetry/usage Per-agent token usage + cost (when priced) + incident MTTR (#57). # Toggle Breaker Source: https://docs.rotavision.com/api-reference/bound/toggle-breaker /api-reference/openapi.json patch /api/v1/bound/breakers/{breaker_id} # Create Certification Source: https://docs.rotavision.com/api-reference/certify/create-certification /api-reference/openapi.json post /api/v1/certify/runs Run the certification battery over a candidate agent and issue a signed certificate. A governance act (officer and above): certifying an agent as fit for production is a sign-off, not a read. # Export Certification Source: https://docs.rotavision.com/api-reference/certify/export-certification /api-reference/openapi.json get /api/v1/certify/runs/{cert_id}/export The downloadable, independently verifiable certificate: the Ed25519-signed bundle whose signature covers the exact payload bytes. # Get Certification Source: https://docs.rotavision.com/api-reference/certify/get-certification /api-reference/openapi.json get /api/v1/certify/runs/{cert_id} # Latest For Agent Source: https://docs.rotavision.com/api-reference/certify/latest-for-agent /api-reference/openapi.json get /api/v1/certify/agents/{agent_id}/latest The most recent certification for an agent — the one the C2 gate and the console badge read. # List Certifications Source: https://docs.rotavision.com/api-reference/certify/list-certifications /api-reference/openapi.json get /api/v1/certify/runs # Overview Source: https://docs.rotavision.com/api-reference/guardian/overview /api-reference/openapi.json get /api/v1/guardian What the Guardian is watching across the fleet, and the interventions it has fired (each on the chain, via the kill path). # Run Watches Source: https://docs.rotavision.com/api-reference/guardian/run-watches /api-reference/openapi.json post /api/v1/guardian/agents/{agent_id}/check Evaluate the agent's armed watches now. Any hard watch that trips fires the kill path (actor breaker:) — real-time intervention on demand, for a drill or an operator. (Watches also run on the 60s in-process sweep.) # Healthz Source: https://docs.rotavision.com/api-reference/healthz /api-reference/openapi.json get /healthz # API Reference Source: https://docs.rotavision.com/api-reference/introduction Orient to the Sakshi HTTP API: base URL, the /api/v1 version prefix, Bearer authentication, and the module tags the endpoints are grouped under. This is the reference for the Sakshi HTTP API. The endpoint pages under **Endpoints** are generated from the live OpenAPI spec, so they always match the version of Sakshi you are running. This page orients you before you go there. ## Base URL Sakshi is single-tenant and runs in your environment, so the base URL is your own deployment: ``` https://sakshi.your-company.internal ``` The public demo at `https://demo.rotavision.com` serves the same API, read-only, over synthetic data. Every path below is relative to your base URL. ## Versioning All endpoints live under a version prefix: ``` /api/v1 ``` New capabilities are added within `v1`. A breaking change would move to a new prefix, so a URL that works today keeps working. ## Authentication Every request carries a Bearer token in the `Authorization` header: ``` Authorization: Bearer ``` The token is either an **ingest API key** (register and witness) or an **OIDC JWT** from your IdP (governance and admin actions). Which one you need depends on the endpoint. See [Authentication](/authentication) for how to mint, use, and rotate them, and [Errors](/errors) for what `401` and `403` mean. ## Modules The endpoints are grouped by tag, one per capability, plus two for the calling principal and access control: Know Your Agent. Agents and models, ownership and provenance, attestations, verified agent identity, and the ingest keys and webhooks that feed the platform. The flight recorder. Capture a decision to the tamper-evident chain, list records, and recompute the chain with `chain/verify` and `chain/head`. Autonomy envelopes and enforcement. Evaluate an intended action, request a human handoff, drive the kill switch and circuit breakers, and work the review queues. Evidence. Regulator-mapped clause maps and readiness, signed evidence packs, incident register, evaluation runs, DPIA support, and audit bundles. Declared-first fairness. Declared and locality screens, pre-deploy runs, and the extraction, disposition, and MCP governance screens. Admin-only. The entitlement mapping from IdP roles and groups to a Sakshi role and org unit, kept as chain-attested config. The calling principal: the resolved entitlements for your token and your briefing. ## Using the reference Each endpoint page shows its method, path, parameters, request body, and response schema, with a live request panel. Point it at your deployment, paste a Bearer token, and call it. Ingest keys versus OIDC JWTs, and when each is required. For most integrations, use the SDK instead of raw HTTP: it wraps these endpoints, buffers capture, and models the enforcement outcomes. # Get Briefing Source: https://docs.rotavision.com/api-reference/me/get-briefing /api-reference/openapi.json get /api/v1/me/briefing A scoped, grounded digest of what changed since this user was last here and what needs them now — plus, when enabled, a sovereign-LLM narration over ONLY those facts. The digest is authoritative. # My Entitlements Source: https://docs.rotavision.com/api-reference/me/my-entitlements /api-reference/openapi.json get /api/v1/me/entitlements The caller's EFFECTIVE Sakshi entitlements after the access mapping is applied (ADR-021) — the console reads this at sign-in so nav matches what the API authorizes. Any authenticated user; reflects the resolved principal, not the raw IdP claims. # Add Provenance Source: https://docs.rotavision.com/api-reference/register/add-provenance /api-reference/openapi.json post /api/v1/agents/{agent_id}/provenance # Attach Identity Source: https://docs.rotavision.com/api-reference/register/attach-identity /api-reference/openapi.json post /api/v1/agents/{agent_id}/identity Attach an external identity (#68): verify an A2A Signed Agent Card (Ed25519 over the card bytes) or record a declared identity (Entra Agent ID / workload identity — a claim, not proof). The verdict is attested; the agent carries its verification status. # Create Agent Source: https://docs.rotavision.com/api-reference/register/create-agent /api-reference/openapi.json post /api/v1/agents # Create Attestation Source: https://docs.rotavision.com/api-reference/register/create-attestation /api-reference/openapi.json post /api/v1/attestations # Create Model Source: https://docs.rotavision.com/api-reference/register/create-model /api-reference/openapi.json post /api/v1/models # Create Webhook Source: https://docs.rotavision.com/api-reference/register/create-webhook /api-reference/openapi.json post /api/v1/webhooks Register a webhook endpoint. The signing secret appears in THIS response; receivers verify X-Sakshi-Signature with it. Registration is itself evidence (attested), like ingest keys. # Get Agent Source: https://docs.rotavision.com/api-reference/register/get-agent /api-reference/openapi.json get /api/v1/agents/{agent_id} # List Agents Source: https://docs.rotavision.com/api-reference/register/list-agents /api-reference/openapi.json get /api/v1/agents # List Api Keys Source: https://docs.rotavision.com/api-reference/register/list-api-keys /api-reference/openapi.json get /api/v1/api-keys # List Attestations Source: https://docs.rotavision.com/api-reference/register/list-attestations /api-reference/openapi.json get /api/v1/attestations # List Models Source: https://docs.rotavision.com/api-reference/register/list-models /api-reference/openapi.json get /api/v1/models # List Orphans Source: https://docs.rotavision.com/api-reference/register/list-orphans /api-reference/openapi.json get /api/v1/agents/orphans Agents with no accountable owner. Design doc 2.1: no orphans — these get an owner within a week or get suspended. # List Provenance Source: https://docs.rotavision.com/api-reference/register/list-provenance /api-reference/openapi.json get /api/v1/agents/{agent_id}/provenance # List Webhooks Source: https://docs.rotavision.com/api-reference/register/list-webhooks /api-reference/openapi.json get /api/v1/webhooks # Meta Source: https://docs.rotavision.com/api-reference/register/meta /api-reference/openapi.json get /api/v1/register/_meta # Mint Api Key Source: https://docs.rotavision.com/api-reference/register/mint-api-key /api-reference/openapi.json post /api/v1/api-keys Mint a managed ingest key. The raw value appears in THIS response and never again — only its SHA-256 is stored. Minting is itself evidence. # Revoke Api Key Source: https://docs.rotavision.com/api-reference/register/revoke-api-key /api-reference/openapi.json post /api/v1/api-keys/{key_id}/revoke Revocation takes effect on the next request — the auth layer reads the row, there is no cache to flush. Also evidence. # Revoke Webhook Source: https://docs.rotavision.com/api-reference/register/revoke-webhook /api-reference/openapi.json post /api/v1/webhooks/{webhook_id}/revoke # Suspend Agent Source: https://docs.rotavision.com/api-reference/register/suspend-agent /api-reference/openapi.json post /api/v1/agents/{agent_id}/suspend # Test Webhook Source: https://docs.rotavision.com/api-reference/register/test-webhook /api-reference/openapi.json post /api/v1/webhooks/{webhook_id}/test Send a signed test event to a webhook so the receiver can verify wiring + signature before a real governance event fires. # Transfer Ownership Source: https://docs.rotavision.com/api-reference/register/transfer-ownership /api-reference/openapi.json post /api/v1/agents/{agent_id}/transfer-ownership # Update Agent Source: https://docs.rotavision.com/api-reference/register/update-agent /api-reference/openapi.json patch /api/v1/agents/{agent_id} # Status Source: https://docs.rotavision.com/api-reference/status /api-reference/openapi.json get /status Public status: liveness + version only — never data, never chain state (those need a principal). # Audit Bundle Source: https://docs.rotavision.com/api-reference/vidhi/audit-bundle /api-reference/openapi.json get /api/v1/vidhi/audit-bundle Independent-audit bundle (DPDP Rule 13(2)): DPIA substrate + every regulator pack + readiness snapshot, in one signed artifact for the auditor. Auditor-role principals (read-only) can pull this without being able to change anything — the 'hand the auditor a login' story extended to a portable, verifiable export. # Build Pack Source: https://docs.rotavision.com/api-reference/vidhi/build-pack /api-reference/openapi.json get /api/v1/vidhi/packs/{map_name} Generate the evidence pack for a clause map, live: chain verification runs, telemetry computes, counts query — never a cached document. # Create Eval Run Source: https://docs.rotavision.com/api-reference/vidhi/create-eval-run /api-reference/openapi.json post /api/v1/vidhi/eval-runs Store a citable eval run. Metrics are supplied (scored offline by exact-match, the Indic judge, or humans — recorded in `scorer`); the platform computes the GATE VERDICT against Wilson confidence intervals and stores the exact artifact. Packs cite it by id as validation evidence. # Create Incident Source: https://docs.rotavision.com/api-reference/vidhi/create-incident /api-reference/openapi.json post /api/v1/vidhi/incidents Declaring an incident is a human act (reviewer and above); the report assembles itself from the flight recorder. # Dpia Support Source: https://docs.rotavision.com/api-reference/vidhi/dpia-support /api-reference/openapi.json get /api/v1/vidhi/dpia The algorithmic-processing sections of a DPDP Rule-13 DPIA, pre-filled from live evidence. # Export Pack Source: https://docs.rotavision.com/api-reference/vidhi/export-pack /api-reference/openapi.json get /api/v1/vidhi/packs/{map_name}/export The evidence bundle: a freshly generated pack, Ed25519-signed with this deployment's key — verifiable offline by anyone with the public key. # Get Clause Map Source: https://docs.rotavision.com/api-reference/vidhi/get-clause-map /api-reference/openapi.json get /api/v1/vidhi/clause-maps/{name} # Get Eval Run Source: https://docs.rotavision.com/api-reference/vidhi/get-eval-run /api-reference/openapi.json get /api/v1/vidhi/eval-runs/{run_id} # Get Readiness Source: https://docs.rotavision.com/api-reference/vidhi/get-readiness /api-reference/openapi.json get /api/v1/vidhi/readiness Regulatory Readiness Score (#52): this deployment scored live against each clause map. Adoption moves the score; assertions do not. # Incident Export Source: https://docs.rotavision.com/api-reference/vidhi/incident-export /api-reference/openapi.json get /api/v1/vidhi/incidents/{incident_id}/export Signed incident bundle — same Ed25519 envelope as evidence packs. # Incident Report Source: https://docs.rotavision.com/api-reference/vidhi/incident-report /api-reference/openapi.json get /api/v1/vidhi/incidents/{incident_id}/report # List Clause Maps Source: https://docs.rotavision.com/api-reference/vidhi/list-clause-maps /api-reference/openapi.json get /api/v1/vidhi/clause-maps # List Eval Runs Source: https://docs.rotavision.com/api-reference/vidhi/list-eval-runs /api-reference/openapi.json get /api/v1/vidhi/eval-runs # List Incidents Source: https://docs.rotavision.com/api-reference/vidhi/list-incidents /api-reference/openapi.json get /api/v1/vidhi/incidents # Meta Source: https://docs.rotavision.com/api-reference/vidhi/meta /api-reference/openapi.json get /api/v1/vidhi/_meta # Resolve Incident Source: https://docs.rotavision.com/api-reference/vidhi/resolve-incident /api-reference/openapi.json post /api/v1/vidhi/incidents/{incident_id}/resolve # Rti Decision Source: https://docs.rotavision.com/api-reference/vidhi/rti-decision /api-reference/openapi.json get /api/v1/vidhi/rti/decision RTI-shaped disclosure pack for one decision (#59): point-wise query/reply/source answers assembled from the flight recorder. # Create Predeploy Run Source: https://docs.rotavision.com/api-reference/vishwas/create-predeploy-run /api-reference/openapi.json post /api/v1/vishwas/predeploy-runs Pre-deploy bias testing: screen a batch of candidate decisions with declared attributes BEFORE the agent goes live. The stored result is the citable artifact; packs reference it by id. # Get Declared Screen Source: https://docs.rotavision.com/api-reference/vishwas/get-declared-screen /api-reference/openapi.json get /api/v1/vishwas/declared-screen Declared-attribute fairness screen (#30) computed live off the flight recorder — production-grounded monitoring, no inference. # Get Extraction Screen Source: https://docs.rotavision.com/api-reference/vishwas/get-extraction-screen /api-reference/openapi.json get /api/v1/vishwas/extraction-screen Document/KYC extraction governance (#80): data-minimization + storage-disposition findings computed live off the flight recorder. # Get Locality Screen Source: https://docs.rotavision.com/api-reference/vishwas/get-locality-screen /api-reference/openapi.json get /api/v1/vishwas/locality-screen Inference-free disparity screen: decision outcomes by locality against census SC/ST population shares (ETHICS.md §1a). Flags, never proves — the caveats travel inside the payload. # Get Mcp Screen Source: https://docs.rotavision.com/api-reference/vishwas/get-mcp-screen /api-reference/openapi.json get /api/v1/vishwas/mcp-screen MCP governance (#58): tool calls + manifests witnessed off the flight recorder, and the tool-poisoning findings (mcp_manifest_changed). # Get Predeploy Run Source: https://docs.rotavision.com/api-reference/vishwas/get-predeploy-run /api-reference/openapi.json get /api/v1/vishwas/predeploy-runs/{run_id} # List Predeploy Runs Source: https://docs.rotavision.com/api-reference/vishwas/list-predeploy-runs /api-reference/openapi.json get /api/v1/vishwas/predeploy-runs # Meta Source: https://docs.rotavision.com/api-reference/vishwas/meta /api-reference/openapi.json get /api/v1/vishwas/_meta # Verify Disposition Endpoint Source: https://docs.rotavision.com/api-reference/vishwas/verify-disposition-endpoint /api-reference/openapi.json post /api/v1/vishwas/verify-disposition Verify a storage disposition (#80 D2) — a signed vault receipt or a reference key against the configured vault — without ever seeing the raw value. # Capture Decision Source: https://docs.rotavision.com/api-reference/witness/capture-decision /api-reference/openapi.json post /api/v1/decisions # Chain Head Source: https://docs.rotavision.com/api-reference/witness/chain-head /api-reference/openapi.json get /api/v1/chain/head # Get Decision Source: https://docs.rotavision.com/api-reference/witness/get-decision /api-reference/openapi.json get /api/v1/decisions/{record_id} # List Decisions Source: https://docs.rotavision.com/api-reference/witness/list-decisions /api-reference/openapi.json get /api/v1/decisions # Meta Source: https://docs.rotavision.com/api-reference/witness/meta /api-reference/openapi.json get /api/v1/witness/_meta # Verify Chain Source: https://docs.rotavision.com/api-reference/witness/verify-chain /api-reference/openapi.json get /api/v1/chain/verify Recompute every hash in the range and check linkage. A regulator, an auditor, or the customer can run this without trusting us. # Authentication Source: https://docs.rotavision.com/authentication Authenticate to Sakshi with a Bearer token: an ingest API key for register and witness, or an OIDC JWT for governance and admin actions. Every request to Sakshi carries a Bearer token in the `Authorization` header. There are two kinds of token, and which one you need depends on what you are doing: * An **ingest API key** for the write path an agent uses at runtime: register itself and witness its decisions. * An **OIDC JWT** from your identity provider for governance and admin actions: activating an agent, publishing an autonomy envelope, minting keys, editing access mappings. Sakshi is single-tenant and runs in your environment, so the base URL in every example is your own deployment. The public demo at `https://demo.rotavision.com` is read-only and exposes a single `auditor` persona. **Evaluating Sakshi and don't have an instance yet?** [Request a sandbox](https://rotavision.com/sandbox) for a private, synthetic-data Sakshi you can sign into, mint an ingest key in, and point the SDK at. It is writable, unlike the read-only demo, so you can register an agent and witness real decisions. No install required. ## The Authorization header Send the token as a Bearer credential on every call: ``` Authorization: Bearer ``` Sakshi routes the token by shape: a value with dots is treated as a JWT and verified against your IdP; anything else is looked up as an ingest key by its SHA-256 hash. You do not choose a header or a scheme per token type. Send the one you hold. ## Ingest API keys Ingest keys are the credential your agents carry in production. They are **ingest-scoped**: they can register an agent and witness decisions, and nothing more. An ingest key cannot activate an agent, publish an envelope, or mint another key. Attempting a governance action with an ingest key returns `403`. ### Minting a key Keys are minted in the console under **Admin → API keys**. Minting is an admin action, so it requires an OIDC session, not another key. Sign in through SSO and open **Admin → API keys**. Give the key a name and the org unit it may write for. Scope is enforced at agent creation, capture, and evaluation, so a key can only ever act within its unit. The raw key is shown **exactly once**, at mint time. Sakshi stores only its SHA-256 hash, so it cannot show the value again. Copy it into your secrets manager now. The raw key is displayed once and never again. If you lose it, revoke it and mint a new one. Never commit a key to source control, paste it into logs, or ship it in client-side code. ### Using a key with the SDK Pass the key to the client. The base URL is your deployment. ```python Python theme={null} from sakshi import SakshiClient client = SakshiClient( base_url="https://sakshi.your-company.internal", api_key="sks_...", ) agent_id = client.register( "loan-decision-agent", owner_name="Priya Sharma", owner_email="priya@your-company.com", ) ``` ```bash cURL theme={null} curl https://sakshi.your-company.internal/api/v1/chain/verify \ -H "Authorization: Bearer sks_..." ``` Read the key from the environment rather than hard-coding it. In CI or a secrets manager, expose it as `SAKSHI_API_KEY` and pass `api_key=os.environ["SAKSHI_API_KEY"]`. ### Rotating and revoking Rotate on your own schedule, and immediately if a key may have leaked: Create a new key with the same org-unit scope. Update your secrets manager and restart the agents that read the key. `register` is idempotent by name, so restarts do not create duplicate agents. Revoke it in the console. Revocation takes effect on the next request, so verify the new key is live before you revoke. ## OIDC JWTs for governance Registering and witnessing is deliberately the only thing an ingest key can do. The decisions that shape governance, who owns an agent, what its autonomy envelope permits, who may approve a parked action, are gated behind a person authenticated by your identity provider. For those actions, obtain a JWT from your IdP through the console SSO flow and send it as the Bearer token. The token carries the caller's identity, roles, and team claims, which Sakshi resolves into a Sakshi role and org unit through the access mapping (see the `access` and `me` endpoints in the [API reference](/api-reference/introduction)). Governance and admin endpoints require a valid JWT; an ingest key on the same route returns `403`. Actions that need a JWT include: * Activating, suspending, or transferring ownership of an agent * Publishing or linting an autonomy envelope, setting a response policy, arming circuit breakers * Activating or releasing the kill switch, attesting a drill * Claiming and resolving review-queue items * Minting or revoking ingest keys and webhooks * Editing the access mapping under Admin ## The public demo The hosted demo at `https://demo.rotavision.com` is read-only and synthetic. It exposes a single `auditor` persona, which can read the registry, recompute the chain, and pull evidence, but cannot write. Use it to explore the API surface and the evidence model. Point the SDK at your own deployment when you are ready to write records. ## What to read next Install the SDK, register an agent, and witness a decision with a key. The exception model, fail-open capture versus fail-closed enforcement, and the HTTP status codes. Every client method, the witness session, and the exception types. Every endpoint, generated from the live OpenAPI spec. # Agent governance Source: https://docs.rotavision.com/concepts/agent-governance The mental model behind Sakshi: as models become agents, the unit of governance shifts from the model to the decision. The five capabilities, and the three ways to connect. Model governance asked one question: is this model fit for use? An agent asks a harder one, thousands of times a day: was this specific decision fit to be made, by this agent, with this autonomy, on this data? As models become agents, the unit of governance shifts from the model to the decision. Sakshi is the layer that governs at that unit. It sits beside the agents you already run, single-tenant, inside your own environment. It does not replace your models or your orchestration and it never sees a raw identifier: personal data is [tokenized at ingest](/concepts/pii-tokenization) before anything is stored. ## The five capabilities Each capability owns one part of the decision, and each has its own concept page. Know Your Agent. A live registry: every agent has an owner, an autonomy tier, a blast radius, and provenance. Nothing runs unregistered. The flight recorder. Every decision is hashed onto a tamper-evident chain that anyone can recompute. No trust in the vendor required. Autonomy envelopes. Each action routes to auto, review, or block by stakes and confidence, backed by circuit breakers and a drilled kill switch. Evidence packs. Regulator instruments encoded as data and scored against your live evidence, exported as a signed bundle you could hand an examiner. The fifth capability, Vishwas, screens for fairness declared-first and gates flags on statistical significance. See the [fairness screening guide](/guides/fairness-screening). ## How you connect There are three ways in, and every one lands on the same chain. Wrap your calls in a few lines of Python: `register`, `witness`, `enforce`. Add one line to an OpenAI, Anthropic, Gemini, Bedrock, LangChain, or LangGraph agent. Govern any agent that speaks the Model Context Protocol, with no SDK in the agent at all. Sakshi is built for regulated Indian enterprises first, with clause maps for RBI Model Risk Management, DPDP, SEBI, and IRDAI. Try a running instance at [demo.rotavision.com](https://demo.rotavision.com), or start with the [quickstart](/quickstart). # Autonomy envelopes Source: https://docs.rotavision.com/concepts/autonomy-envelopes Rules that route each action to auto, async review, sync review, or block based on stakes, confidence, and novelty. Envelopes are formally verified with Z3 before publish, versioned, and chain-attested. An autonomy tier is a declared posture. An autonomy envelope is what enforces it. The Bound capability is a set of rules that decide, for each intended action, whether the agent may act now, may act with review to follow, must wait for a human, or must not act at all. ## The four outcomes Every action an agent is about to take is routed by its envelope to one of four outcomes, based on signals like the stakes involved, the model's confidence, and how novel the situation is: * **`auto`** proceeds immediately. * **`async_review`** proceeds now, with a human reviewing after the fact. * **`sync_review`** parks the action until a human approves it. * **`block`** refuses the action outright. You ask for the decision before the agent acts. Enforcement fails **closed**: if Sakshi is unreachable it raises rather than letting an ungoverned action through. ```python theme={null} from sakshi import SakshiBlocked, SakshiSyncReviewRequired try: client.enforce(agent_id, "approve_loan", stakes=1_500_000, confidence=0.92) # allowed: proceed with the action except SakshiSyncReviewRequired: # parked for a human before it can run ... except SakshiBlocked: # the envelope (or the kill switch) refused it ... ``` ## Formally verified before it governs An envelope is a policy, and policies have bugs: a rule shadowed by an earlier one that can never fire, or an escalation that routes higher stakes to *less* oversight. Before an envelope is published, Sakshi checks it with the Z3 theorem prover for dead rules and for escalation monotonicity. This is a proof, not a test pass. Your autonomy policy is formally verified before it governs production. Every published envelope is versioned and chain-attested, so a change to how the agent is bounded is itself a governed, [recorded](/concepts/decision-chain) event. ## Circuit breakers Envelopes govern individual actions. Circuit breakers watch the aggregate and fire the [kill path](/concepts/kill-switch) when a pattern goes wrong: a rising human `override_rate` (the humans keep disagreeing with the agent), an `eval_volume` spike, or a `model_change` that drifts the model identity in effect away from the one under which the envelope was approved. Circuit breakers halt automatically but release human-only. A machine can stop an agent; restarting it is a human decision with an attestation. Publish an envelope, arm breakers, and lint it before it ships. Signals, the decision object, and the exception model. # Certification Source: https://docs.rotavision.com/concepts/certification A signed, RBI-mapped fit-for-production certificate for an agent, produced by running a battery over the evidence Sakshi already holds — red-team, fairness, evaluation gates, formally-verified autonomy policy, verified identity. Optionally required before an agent can be activated. No agent should reach production without a governance sign-off. Certification is that sign-off, made verifiable: a battery of checks over the evidence Sakshi already holds for a candidate agent, aggregated into a scored verdict and an Ed25519-signed certificate with an expiry — each check mapped to the regulatory obligation it satisfies. Certification is **evidence-first**: it reads evidence already on the chain, it never drives the agent. ## The battery A certification run assembles the checks other parts of Sakshi produce: * **Autonomy envelope** — the agent's policy is formally verified (no dead rules, no escalation that routes higher stakes to *less* oversight). RBI MRM bounded autonomy. * **Verified identity** — a cryptographically verified agent identity (KYA / SEBI Reg 16C). * **Independent validation** — evaluation gates cleared on the confidence-interval lower bound, not a point estimate. RBI MRM independent validation. * **Red-team** — an adversarial evaluation within the attack-success ceiling. RBI MRM red-teaming. * **Fairness** — a declared-first bias screen with no flagged cohort. DPDP / fair-lending. ## The verdict A hard check that fails **denies** the certificate; a missing hard check leaves the agent **incomplete** (you cannot certify what was never tested); all hard checks passing is **certified**. The certificate carries the per-check results, the clause mapping, an expiry, and a signature over the exact bytes — anyone can verify it without trusting Sakshi. ```bash theme={null} # run the battery over a candidate agent curl -X POST https://YOUR_HOST/api/v1/certify/runs \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"agent_id": "AGENT_ID"}' # the latest certificate for an agent, and its signed export GET /api/v1/certify/agents/{agent_id}/latest GET /api/v1/certify/runs/{id}/export ``` ## The gate Set `SAKSHI_REQUIRE_CERT` and an agent cannot be activated without a valid, unexpired certificate — certification becomes required infrastructure in your deployment pipeline. A denied or expired agent is refused activation, with the reason on record. ## Re-certification A certificate has a shelf life. Certifying an agent schedules its re-certification at the certificate's expiry, and events that invalidate its assumptions — a model change, or a [silent behavioural change](/concepts/silent-change) — flag it for re-certification immediately. This is the loop that closes the [governed-agent lifecycle](/concepts/guardian): certify before ship, watch in production, and a silent change sends the agent back through certification. # The decision chain Source: https://docs.rotavision.com/concepts/decision-chain Every decision is a record hashed together with the hash of the record before it. Recomputing re-derives every hash, so tampering is detectable and the chain says exactly where it happened. No trust in the vendor required. The Witness capability is a flight recorder for decisions. Every decision your agent makes becomes a record: the [registered agent](/concepts/know-your-agent), the model identity in effect, the [tokenized](/concepts/pii-tokenization) inputs, the steps taken, and the outcome. Failures are recorded too, because a failure is evidence. ## Why hash-chained A log you can edit proves nothing. Each record is hashed together with the hash of the record before it, so the records form a chain. Change one field in one record and its hash changes, which breaks the link to every record after it. Recomputing the chain re-derives every hash from the raw records, so any tampering is not just detectable, the chain says exactly where it happened. ``` record N-1 ── hash(N-1) ──┐ ▼ record N = { agent, model, inputs, steps, outcome, prev: hash(N-1) } │ hash(N) ──▶ record N+1 ... ``` ## Verify it yourself Anyone can recompute the chain. This is the point: you do not have to trust Rotavision, or even trust your own operators. `GET /api/v1/chain/verify` walks the records, recomputes, and returns whether the chain holds, how many records it checked, and the first sequence number where it broke, if any. ```bash cURL theme={null} curl -H "Authorization: Bearer sks_..." \ https://sakshi.your-company.internal/api/v1/chain/verify ``` ```json Response theme={null} { "valid": true, "checked": 1842, "first_invalid_seq": null } ``` Tamper with a single field in any record and `valid` becomes `false`, with the break located precisely. ## Signed evidence bundles When you export a decision or an [evidence pack](/concepts/evidence-packs), the bundle is Ed25519-signed over its exact bytes. A recipient checks the signature against your published key, so the artifact you hand an examiner is verifiable on its own, away from your systems, and cannot be altered without detection. Signing is over the exact bytes of the bundle. Re-serializing or reformatting the JSON breaks the signature by design. Ship the bytes you signed. Open a witness session and record steps as they happen. The verify endpoint and the decision record shape. # Evidence packs Source: https://docs.rotavision.com/concepts/evidence-packs Vidhi: regulator instruments encoded as data, scored against your live evidence to produce a readiness score and a signed, exportable pack you could hand an examiner. An examiner does not ask for your logs. They ask whether you meet a specific obligation, clause by clause. The Vidhi capability (vidhi, "law") closes that gap by encoding the instrument itself as data and scoring it against the evidence you are already producing. ## Instruments as data Each regulatory instrument is a clause map: the obligations broken out one by one, with each clause bound to the kind of live evidence that would satisfy it. Sakshi ships maps for RBI Model Risk Management, DPDP, SEBI Regulation 16C, and IRDAI. A clause is scored **ready** only when the backing evidence check passes against real records on the [chain](/concepts/decision-chain), never on a self-assertion. Because the instrument is data, adding a regulator is a new map, not a new release. ## Readiness, then a pack Two things come out of a clause map. First, a readiness score: what fraction of the obligations are met by live evidence right now, which doubles as a gap-ordered work list. Second, an evidence pack: the clauses, the evidence behind each, and a scoped [chain](/concepts/decision-chain) verification, bundled and Ed25519-signed over its exact bytes so it stands on its own once exported. ```bash Readiness theme={null} curl -H "Authorization: Bearer sks_..." \ https://sakshi.your-company.internal/api/v1/vidhi/readiness ``` ```bash Export a signed pack theme={null} curl -H "Authorization: Bearer sks_..." \ "https://sakshi.your-company.internal/api/v1/vidhi/packs/rbi-mrm-draft-2026/export" ``` The pack lives at `GET /api/v1/vidhi/packs/{map_name}`, its signed export at `.../export`, and readiness across all instruments at `GET /api/v1/vidhi/readiness`. ## Beyond the standing maps Vidhi also assembles instruments a regulator or a citizen asks for on demand: an RTI decision pack that answers pointed questions about one automated decision, each point citing its source, and a DPDP DPIA plus a signed audit bundle for an independent auditor. Every one is built from the same live evidence and verified at export time. A readiness score below 100 is not a failure to hide. A deliberately unmet clause is a known gap you can point to, which is what an honest control environment looks like. Map live records to RBI, DPDP, SEBI, or IRDAI and export a signed bundle. Packs, export, and readiness endpoints. # The Guardian Source: https://docs.rotavision.com/concepts/guardian Real-time fleet monitoring and intervention. The Guardian watches every agent through evidence-only watches and, when a hard watch trips, halts, escalates or steers it using the same drilled kill switch a human would — every intervention on the tamper-evident record. A flight recorder tells you why the plane crashed; the Guardian keeps it from crashing. It is a live monitor over the whole agent fleet that halts, escalates, or steers the moment an agent drifts, is poisoned, or goes off-policy. When a hard watch trips it fires the **existing kill path** — so the intervention reuses the audited, attested response machinery, not a new one, and every intervention lands on the chain with the evidence that justified it. RBI's draft model-risk directions name continuous monitoring and a kill switch as obligations; the Guardian produces both as evidence. ## Watches The Guardian evaluates each agent through a set of **watches**. The evidence-only watches derive entirely from witnessed evidence and need no GPU: * **Model-identity change** — an undeclared model transition (a silent model swap). * **[Silent behavioural change](/concepts/silent-change)** — the agent's behaviour diverged from baseline with no governance event to explain it. * **MCP tool poisoning** — a tool's manifest changed mid-session (the OWASP MCP class); a poisoned tool spreads across the fleet. * **Error / refusal spike**, **signal drift**, and volume anomalies — the agent no longer looks like what its envelope was written for. A tripped hard watch halts or routes-to-review the agent through the same drilled kill switch, with an actor of `guardian:`, attested. ```bash theme={null} # what the Guardian is watching, and the interventions it has fired GET /api/v1/guardian # evaluate an agent's watches now (a drill, or on demand) POST /api/v1/guardian/agents/{agent_id}/check ``` ## Model-serving watches Two further watches — **groundedness** (is the output supported by its sources?) and **live toxicity** — require serving a judge model on the live stream. Because they evaluate live production decisions, the judge must run inside the customer's environment (in-VPC or async-batch), never off-region. These watches are declared in the control surface but gated behind that serving decision; everything else the Guardian does today is evidence-first and needs no GPU. ## The lifecycle The Guardian is the middle of the governed-agent lifecycle: [certify](/concepts/certification) an agent before it ships, the Guardian watches it in production, and a [silent change](/concepts/silent-change) sends it back through certification. Certify. Watch. Re-certify. # The kill switch Source: https://docs.rotavision.com/concepts/kill-switch A per-agent halt or safe mode. safe_mode swaps evaluation to a conservative envelope and parks actions in review instead of blocking. Drilled on a schedule with signed attestations, and checked on every enforcement call. When an agent goes wrong, you need to stop it now, not after the next deploy. The kill switch is a per-agent control that takes an agent out of autonomous operation immediately. It is part of the Bound capability and works alongside [autonomy envelopes](/concepts/autonomy-envelopes) and circuit breakers. ## Two rungs: halt and safe mode The response is per-agent policy, frozen at the moment of activation: * **`halt`** stops the agent outright. Every enforcement decision refuses. * **`safe_mode`** does not hard-stop. It swaps the agent's evaluation to a conservative envelope and parks actions in review instead of blocking them, so the agent degrades to human-supervised operation rather than going dark. This suits an agent whose blast radius makes a full stop its own kind of incident. Circuit breakers (rising override rate, evaluation-volume spikes, model drift) fire this same path automatically. Breakers halt without a human; release is human-only, with an attestation. ## Checked on every action, fails closed The SDK's `enforce()` consults the kill switch before it evaluates anything. A background poller caches each agent's kill state, so a halt takes effect within one poll interval even between decisions, and the server re-checks inside the evaluation. Both checks fail closed: an unreachable platform, or a missing response field from an older deployment, reads as `halt`, never as permissive. ```python theme={null} from sakshi import SakshiBlocked try: client.enforce(agent_id, "approve_loan", stakes=1_500_000, confidence=0.92) except SakshiBlocked: # halted by the kill switch, or blocked by a rule: stop the agent ... ``` ## Drilled, not just declared A safety control you have never fired is a hope, not a control. Kill switches are drilled on a schedule, and each drill produces a signed attestation recording the propagation time and the scope it reached. That attestation is evidence you tested the switch, cited by the [evidence packs](/concepts/evidence-packs) that map to regulator obligations. Activate at `POST /api/v1/bound/kill`, check state at `GET /api/v1/bound/kill/status`, and release (human-only) at `.../kill/release`. Set the response policy, arm circuit breakers, and run a drill. The conservative envelope safe mode routes through. # Know Your Agent Source: https://docs.rotavision.com/concepts/know-your-agent A live registry of every agent: owner, autonomy tier, blast radius, and provenance. Sakshi verifies external signed agent identities rather than inventing its own, and nothing runs unregistered. You cannot govern what you have not inventoried. Know Your Agent (the Register capability) is a live registry of every agent and model in production. It is the anchor the rest of Sakshi hangs off: a decision on the [chain](/concepts/decision-chain) points back to a registered agent, and an [autonomy envelope](/concepts/autonomy-envelopes) binds a registered agent. ## What a registration holds Each agent carries the facts a risk team and an examiner both need: * **Owner.** A named person accountable for the agent, not a team alias. * **Autonomy tier.** `L0` is the lowest autonomy; higher tiers act more freely. The tier is a declared posture that the envelope then enforces. * **Blast radius.** Which systems the agent can touch and how much money it can move. This is the honest measure of what a failure could cost. * **Provenance.** The model, prompt, and tool versions in effect, so a silent swap is visible after the fact. Nothing runs unregistered. `register` is synchronous and idempotent by name, so calling it on every startup is safe and never duplicates an agent. ```python theme={null} from sakshi import SakshiClient client = SakshiClient(base_url="https://sakshi.your-company.internal", api_key="sks_...") agent_id = client.register( "loan-decision-agent", owner_name="Priya Sharma", owner_email="priya@your-company.com", autonomy_tier="L1", blast_radius={"systems": ["core-banking"], "max_amount": 2_000_000}, description="Approves or refers retail loan applications.", ) ``` ## Verified identity, not invented identity An agent may already carry an identity issued elsewhere: an A2A Signed Agent Card or a workload identity from your IdP. Sakshi verifies that identity rather than minting a competing one. It checks the Ed25519 signature over the card's canonical bytes: a genuine card is recorded as verified, and a tampered card is rejected. A declared identity with no proof is stored honestly marked unverified, because a claim is not a proof. Verified identity is the third-party-vendor story a regulator asks about directly. If a vendor's platform signed the agent's card, you can show you verified it, not that you took the vendor's word. ## Endpoints and next steps The registry is exposed at `POST/GET /api/v1/agents`, `POST /api/v1/agents/{id}/identity` for signed-identity verification, and `POST/GET /api/v1/agents/{id}/provenance`. The step-by-step guide, including blast radius and provenance. Every argument to `register` and the full client surface. # PII tokenization Source: https://docs.rotavision.com/concepts/pii-tokenization Identifiers are tokenized at ingest, before anything is stored, so raw personal data never enters the chain. Sakshi governs the act and holds the proof; it never becomes the store for raw sensitive data. Sakshi records every decision, and decisions touch Aadhaar numbers, PANs, card numbers, and account numbers. A governance layer that accumulated all of that would become the most sensitive database in the building. Sakshi is built so it cannot: raw identifiers are tokenized at ingest, before anything is stored. ## Tokenized at ingest When a record arrives, identifiers are replaced by tokens on the way in, ahead of persistence. Two things do the work: a deterministic layer that recognizes known identifier formats, and a named-entity recognition (NER) pass that catches identifiers in free text. Because tokenization happens at ingest, the raw value never reaches the [decision chain](/concepts/decision-chain). What lands on the chain is a token, and the chain is still fully verifiable over tokens. ```python theme={null} with client.witness( agent_id, model={"provider": "openai", "name": "gpt-5.2", "version": "2026-05"}, context={"applicant_aadhaar": "XXXX-XXXX-1234"}, client_ref="APP-2026-04471", ) as decision: decision.step("assess", risk_band="low", score=0.92) decision.action(outcome="approved", amount=1_500_000, mode="auto") # the Aadhaar value is tokenized before this record is stored ``` ## Governing storage without holding the value The raw value still has to live somewhere: a compliant store like a UIDAI Aadhaar Data Vault. Sakshi's job is to prove it went there, not to hold it. Given a reference key, Sakshi verifies the value landed in the vault by checking the key's metadata, which the vault exposes without granting decode rights. It never sees or detokenizes the value. `POST /api/v1/vishwas/verify-disposition` returns a **vault-verified** disposition when the check passes. A value backed only by a signed receipt is recorded as **attested**, and a value sitting outside the vault, or offshore, is flagged as a residency breach. An assertion would have hidden that; verification surfaces it. Sakshi governs the act and holds the proof. It never becomes the system of record for raw sensitive data. That boundary is a core safety property, not a configuration option. Govern what was extracted, by which agent, and where it was stored. Declared-first screens that run without ever de-anonymizing anyone. # Silent-change detection Source: https://docs.rotavision.com/concepts/silent-change Sakshi fingerprints each agent's behaviour from the witnessed decision stream and tells you the day it started behaving differently — and whether the change was declared or silent. Maps to the RBI unauthorised-change obligation. Agents change silently. A model gets swapped underneath one, a prompt is injected, data drifts — and nobody notices until it is a headline. Silent-change detection fingerprints each agent's behaviour from the decision chain and tells you the day it started behaving differently, with **no new system of record** and **no GPU** — just statistics over evidence you already have. ## The behavioural fingerprint Sakshi computes a fingerprint per agent from its witnessed decisions — the model serving it, its decision and routing mix, its tool use, its decline reasons — and measures how far a recent window has diverged from the window before it. A dimension is only flagged when the shift is both large **and** statistically significant, so a quiet agent never cries wolf (the same discipline as the Vishwas fairness screen and the evaluation confidence intervals). ```bash theme={null} # fleet summary — changed agents first GET /api/v1/behavior/fleet # per-agent: which dimensions diverged, baseline vs current GET /api/v1/behavior/agents/{agent_id}/change ``` ## Declared vs silent A flagged change is only alarming if it was **undeclared**. Sakshi correlates each flagged dimension with the governance events that would explain it — a model registered in the agent's provenance, an autonomy envelope published — and classifies the change: * **declared** — every flagged dimension is explained by a governance act you took (a registered model, a published policy). Expected; no alarm. * **silent** — a flagged dimension has no governance event to explain it. The agent changed and nobody declared why. A declared model upgrade and a silent swap look identical in the metrics; the difference is whether you told the registry — and Sakshi checks. ## The signed change report Persisting a report Ed25519-signs the verdict as a tamper-evident, RBI-mapped artifact (the unauthorised-change obligation). When the verdict is **silent**, the report also emits an `agent_behavior_changed` webhook and flags the agent for [re-certification](/concepts/certification) — so a silent change automatically sends the agent back through the fit-for-production gate. ```bash theme={null} # persist a signed change report (silent -> flags re-cert + emits a webhook) POST /api/v1/behavior/agents/{agent_id}/report GET /api/v1/behavior/reports/{id}/export # the signed artifact ``` # Errors Source: https://docs.rotavision.com/errors How Sakshi fails on purpose: fail-open capture, fail-closed enforcement, the SDK exception model, HTTP status codes, and idempotency. Sakshi governs production agents, so how it behaves when something goes wrong is a design decision, not an accident. Two rules run through everything below: * **Capture is fail-open.** Recording a decision must never take your agent down. If the platform is unreachable, the record is retried and then dropped with a warning, and your agent keeps running. * **Enforcement is fail-closed.** Asking permission to act is different. If Sakshi cannot return an enforcement decision, the SDK raises rather than let an ungoverned action through. Fail-open is the default for capture. Pass `fail_open=False` to `SakshiClient` for jobs where losing a record is worse than stopping, such as batch pipelines. Capture then delivers synchronously and raises on failure. ## The SDK exception model The SDK raises three exceptions, all from `enforce()`. Import them from the top level: ```python theme={null} from sakshi import ( SakshiBlocked, SakshiSyncReviewRequired, SakshiEnforcementUnavailable, ) ``` | Exception | Raised when | What it means | | ------------------------------ | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | `SakshiBlocked` | The envelope matched a **block** rule, or the **kill switch** has halted the agent | The action must not run. Stop it. | | `SakshiSyncReviewRequired` | The envelope routed the action to **sync review** | A human must decide before the action can run. Park it; the review item already exists server-side. | | `SakshiEnforcementUnavailable` | Sakshi could not be reached for a decision | Fail closed. Do not proceed as if allowed. | `enforce()` returns an `EnforcementDecision` whose `outcome` is one of `auto`, `async_review`, `sync_review`, or `block`. By default (`raise_on_hold=True`) a `block` raises `SakshiBlocked` and a `sync_review` raises `SakshiSyncReviewRequired`, so the allowed path is the one that returns. ### Handling enforcement The common shape is a `try`/`except` around the intended action: ```python theme={null} from sakshi import ( SakshiBlocked, SakshiSyncReviewRequired, SakshiEnforcementUnavailable, ) try: client.enforce( agent_id, "approve_loan", stakes=1_500_000, confidence=0.92, ) # auto or async_review: proceed with the action approve(application) except SakshiSyncReviewRequired: # a human must decide first; park the action, do not run it park_for_review(application) except SakshiBlocked: # an envelope rule or the kill switch stopped it decline_or_defer(application) except SakshiEnforcementUnavailable: # fail closed: no decision means no ungoverned action defer(application) ``` `async_review` allows the action to run now with review after the fact, so it returns normally. If you would rather branch on the outcome yourself instead of catching exceptions, pass `raise_on_hold=False` and read the decision: ```python theme={null} decision = client.enforce( agent_id, "approve_loan", stakes=1_500_000, confidence=0.92, raise_on_hold=False, ) if decision.allowed: # auto or async_review approve(application) else: # sync_review or block park_for_review(application) ``` `SakshiEnforcementUnavailable` is still raised even with `raise_on_hold=False`, because an unreachable platform is not an outcome you can branch on. It is the fail-closed guarantee. ### Handling capture Capture through a `witness` session does not raise in the default fail-open mode. The record is queued and delivered by a background worker; if delivery fails after retries, the record is dropped and a warning is logged to the `sakshi` logger. Your `with client.witness(...)` block always completes. An exception inside the witness block is recorded as the decision's outcome and re-raised, because failures are evidence too: ```python theme={null} with client.witness(agent_id, client_ref="APP-2026-04471") as decision: decision.step("assess", risk_band="low") decision.action(outcome="approved") # if this block raises, # the error is captured, then re-raised ``` With `fail_open=False`, capture delivers synchronously and raises the underlying transport error instead of dropping the record. Use it only where a lost record is worse than a stopped job. ## HTTP status codes If you call the API directly, or want to reason about what the SDK is reacting to, these are the statuses Sakshi returns. | Status | Meaning | How to react | | ------ | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | `401` | No token, or an invalid or revoked token | Check the `Authorization` header. Mint or refresh the credential. See [Authentication](/authentication). | | `403` | Valid token, insufficient scope: for example an ingest key attempting a governance action | Use an OIDC JWT for governance and admin actions. Ingest keys can only register and witness. | | `404` | No such resource, or not visible to this principal | Verify the id and that the token's org unit or team can see it. | | `409` | Conflict with current state | Re-read the resource and reconcile. Do not blindly retry. | | `422` | Request failed validation | Fix the payload. The body names the offending fields. | | `429` | Rate limit exceeded | Back off. The response carries `Retry-After` (seconds); wait that long before retrying. | | `5xx` | Server-side error, usually transient | Retry idempotent calls with backoff. Do not silently retry `enforce` or `handoff` (see below). | Validation errors (`422`) follow the standard schema for this API and list each invalid field, so you can map the failure back to a specific parameter. ## Retries and idempotency The SDK's **capture** layer retries transient failures with jittered exponential backoff up to `max_retries=3` (configurable on the client). A fleet of agents backing off in lockstep would hammer a recovering platform, so the backoff is jittered. After the last attempt it either drops the record (fail-open) or raises (fail\_open=False). Not every call is safe to retry, so not every call is retried: | Call | Idempotency | Retried by the SDK | | ----------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | `register` | Idempotent by **name**: safe to call on every startup | Synchronous; you may retry | | `witness` | Idempotent by **`client_ref`**: a redelivered record does not double-count | Yes, by the capture layer | | `enforce` | **Not** idempotent | **No.** A failure raises `SakshiEnforcementUnavailable` | | `request_human_handoff` | **Not** idempotent | **No.** Synchronous and raising; a lost handoff is a compliance failure | `enforce` and `handoff` are never retried automatically, because re-asking for a decision or re-requesting a human are not free to repeat. If you retry them yourself, guard the retry with your own idempotency key so you do not create duplicate evaluations or duplicate review items. Pass a stable `client_ref` to `witness` so a retried capture reconciles to the same record rather than writing a second one. ## Next steps How envelopes route an action to auto, review, or block, and how the kill switch halts an agent. The client, the witness session, and the full exception reference. Ingest keys versus OIDC JWTs, and why a `403` usually means the wrong token type. Every endpoint and its response schema, generated from the OpenAPI spec. # Evaluate Sakshi Source: https://docs.rotavision.com/evaluate A practical path for a BFSI team evaluating Sakshi: get a writable instance, govern your first agent, verify the chain yourself, and export a regulator-mapped evidence pack, in about half an hour. This is the shortest honest path from "curious" to "convinced" for a team evaluating Sakshi on a real use case. It governs one agent end to end, on your own instance, and ends with a signed evidence pack you can hand to a risk or audit function. Budget about thirty minutes. The whole point is that you do not have to take our word for anything. Every decision is recomputable, the evidence bundle is signed over its exact bytes, and Sakshi never sees a raw identifier. ## 1. Get a writable instance You need an instance you can write to. There are two ways in. A private, synthetic-data Sakshi, provisioned for you, no install. Writable, so you can register agents and witness decisions. The fastest way to start. Single-tenant, in your VPC or on-prem. One host, one install. Evidence never leaves your environment. The path your security team will want to see. The public demo at `https://demo.rotavision.com` is read-only, so use it to look around, not to run this walkthrough. Then mint an ingest key in the console under **Admin → API keys** and point the SDK at your instance. See [Authentication](/authentication). ## 2. Govern your first agent Wrap one real decision. Register the agent, then witness a decision as it happens. Identifiers you pass are tokenized at ingest, so raw PII never lands on the chain. ```python theme={null} from sakshi import SakshiClient client = SakshiClient("https://sakshi.your-company.internal", api_key="sks_...") agent_id = client.register( "loan-decision-agent", owner_name="Priya Sharma", owner_email="priya@your-company.com", autonomy_tier="L1", ) with client.witness(agent_id, client_ref="APP-2026-04471") as decision: decision.step("retrieve", source="credit-bureau") decision.step("assess", risk_band="low", score=0.92) decision.action(outcome="approved", amount=1_500_000, mode="auto") ``` Already run agents on a framework? You do not rewrite them. Add one line of middleware for [OpenAI](/integrations/openai), [Anthropic](/integrations/anthropic), [Gemini](/integrations/gemini), [Bedrock](/integrations/bedrock), [LangGraph](/integrations/langgraph), or [Google ADK](/integrations/adk), or govern tool calls over [MCP](/integrations/mcp). See [Register an agent](/guides/register-an-agent) and [Witness a decision](/guides/witness-a-decision). ## 3. Verify the chain yourself This is the step that matters. Recompute the chain and confirm it holds, without trusting Rotavision or even your own operators. ```bash cURL theme={null} curl -H "Authorization: Bearer sks_..." \ https://sakshi.your-company.internal/api/v1/chain/verify ``` ```json Response theme={null} { "valid": true, "checked": 1842, "first_invalid_seq": null } ``` Tamper with a single field in any record and `valid` flips to `false`, and the response tells you the sequence number where it broke. That is the trust model: evidence, not assertion. See [The decision chain](/concepts/decision-chain). ## 4. Bound the autonomy Publish an autonomy envelope so consequential actions route to auto, human review, or block by stakes and confidence, and drill the kill switch. Enforcement fails closed: if Sakshi is unreachable, an ungoverned action does not slip through. See [Bound autonomy](/guides/bound-autonomy). ```python theme={null} from sakshi import SakshiBlocked, SakshiSyncReviewRequired try: client.enforce(agent_id, "approve_loan", stakes=1_500_000, confidence=0.92) except SakshiSyncReviewRequired: ... # parked for a human before it can run except SakshiBlocked: ... # the envelope or the kill switch blocked it ``` ## 5. Produce the regulator evidence This is what a risk or audit function actually wants. Read your readiness across instruments, then export a signed evidence pack that maps your live records to the obligation, clause by clause. ```bash Readiness theme={null} curl -H "Authorization: Bearer sks_..." \ https://sakshi.your-company.internal/api/v1/vidhi/readiness ``` ```bash Signed pack (RBI MRM) theme={null} curl -H "Authorization: Bearer sks_..." \ "https://sakshi.your-company.internal/api/v1/vidhi/packs/rbi-mrm-draft-2026/export" ``` Packs cover RBI Model Risk Management, DPDP, SEBI, and IRDAI. Each is Ed25519-signed over its exact bytes, so it stands on its own once exported. See [Evidence packs](/guides/evidence-packs). For fairness, the declared-first screen flags a cohort only on a threshold breach that is also statistically significant, so it does not cry wolf. See [Fairness screening](/guides/fairness-screening). ## What a design-partner engagement looks like If the evaluation lands, the next step is a design partnership: we deploy alongside you, wire Sakshi to the agents you actually run, and shape the roadmap around what you hit first. It is deepest in BFSI, where the regulatory work is furthest along. For your risk and procurement teams, the [design partner brief](https://rotavision.com/assets/rotavision-design-partner-packet.pdf) is a single PDF covering the regulatory mapping, the security posture, and the engagement. Ways in, from a free sandbox to a production engagement in your VPC. Sakshi is single-tenant and runs in your environment. Customer data never leaves it, PII is tokenized before anything is stored or hashed, and every record is independently verifiable, even by an auditor who does not trust us. # Bound autonomy Source: https://docs.rotavision.com/guides/bound-autonomy Publish an autonomy envelope, enforce it before an agent acts, arm circuit breakers, and drill the kill switch. Witnessing records what happened. Bounding decides what is allowed to happen. An autonomy envelope is a set of versioned, attested rules that route each intended action to `auto`, `async_review`, `sync_review`, or `block` by its stakes, confidence, and novelty. This guide publishes an envelope, enforces it from the SDK, arms circuit breakers, and drills the kill switch. Publish a new envelope version for the agent. Rules are evaluated in order; the first match wins, and anything unmatched falls through to `default_outcome`. Publishing is a governance act, so it needs an OIDC JWT. Every envelope is formally linted with Z3 before it governs production. The lint catches dead rules and escalation non-monotonicity (a case where higher stakes would route to *less* oversight). With `SAKSHI_ENVELOPE_LINT_STRICT=true` a failing lint blocks the publish; by default the findings are attached and attested. ```bash cURL theme={null} curl -X POST \ https://sakshi.your-company.internal/api/v1/bound/agents/$AGENT_ID/envelope \ -H "Authorization: Bearer $SAKSHI_GOVERNANCE_JWT" \ -H "Content-Type: application/json" \ -d @envelope.json ``` ```json envelope.json theme={null} { "default_outcome": "sync_review", "deep_review_rate": 0.1, "note": "Retail loan approvals v3", "rules": [ { "name": "small-auto", "when": { "actions": ["approve_loan"], "max_stakes": 500000, "min_confidence": 0.85 }, "outcome": "auto" }, { "name": "mid-async", "when": { "actions": ["approve_loan"], "min_stakes": 500000, "max_stakes": 2000000 }, "outcome": "async_review" }, { "name": "jumbo-sync", "when": { "actions": ["approve_loan"], "min_stakes": 2000000 }, "outcome": "sync_review" } ] } ``` `deep_review_rate` is the fraction of `auto` decisions sampled for post-hoc deep review, so oversight does not go dark just because an action was allowed. Prior versions deactivate but are never deleted: the envelope in force at any past decision stays reconstructable. Route the intended action through `enforce` and let the outcome decide. Enforcement fails **closed**: if Sakshi is unreachable it raises rather than letting an ungoverned action through. By default a hold raises, so the happy path is simply "no exception means proceed". ```python theme={null} from sakshi import ( SakshiBlocked, SakshiSyncReviewRequired, SakshiEnforcementUnavailable, ) try: decision = client.enforce( agent_id, "approve_loan", stakes=450_000, confidence=0.9 ) # decision.allowed is True; decision.outcome is "auto" or "async_review" approve_loan() except SakshiSyncReviewRequired as exc: # a human must decide before this runs; the queue item already exists park_for_human(exc.decision.matched_rule) except SakshiBlocked as exc: # an envelope block rule or the kill switch stopped it stop(exc.decision) except SakshiEnforcementUnavailable: # fail-closed: do not act on an ungoverned decision stop() ``` Prefer to branch yourself? Pass `raise_on_hold=False` and read the decision directly. `async_review` allows the action with post-hoc review; `sync_review` and `block` do not. ```python theme={null} decision = client.enforce( agent_id, "approve_loan", stakes=3_000_000, raise_on_hold=False ) if decision.allowed: # "auto" or "async_review" approve_loan() else: # "sync_review" or "block" park_for_human(decision.outcome, decision.matched_rule) ``` Breakers watch the decision stream and fire the same halt path as the kill switch when a governed signal degrades. Available metrics are `override_rate`, `eval_volume`, `model_change`, `error_rate`, and `signal_drift`. A breaker releases human-only. ```bash cURL theme={null} curl -X POST \ https://sakshi.your-company.internal/api/v1/bound/agents/$AGENT_ID/breakers \ -H "Authorization: Bearer $SAKSHI_GOVERNANCE_JWT" \ -H "Content-Type: application/json" \ -d '{ "name": "override-spike", "metric": "override_rate", "params": { "threshold": 0.3, "min_resolved": 20 } }' ``` ```text What each breaker fires on theme={null} override_rate humans keep overriding the agent (disagreement is a signal) eval_volume a burst of evaluations far above the recent baseline model_change the model identity in scope silently changed error_rate the agent's own error/exception rate crossed a threshold signal_drift mean stakes drifted from baseline by z standard deviations ``` The kill switch outranks every envelope. Its response is set per agent and frozen on activation. `halt` blocks outright, so the next `enforce` raises `SakshiBlocked`. `safe_mode` does not hard-stop; instead the evaluation swaps in the conservative envelope and actions park in review, so the business degrades to human-decided rather than stopping. Drill it regularly. A `drill: true` activation exercises the whole path without hiding that it was a drill. ```bash Activate (drill) theme={null} curl -X POST \ https://sakshi.your-company.internal/api/v1/bound/kill \ -H "Authorization: Bearer $SAKSHI_GOVERNANCE_JWT" \ -H "Content-Type: application/json" \ -d '{ "target_type": "agent", "target_id": "'$AGENT_ID'", "drill": true, "reason": "Quarterly kill-switch drill", "response": "halt" }' ``` ```bash Release theme={null} curl -X POST \ https://sakshi.your-company.internal/api/v1/bound/kill/release \ -H "Authorization: Bearer $SAKSHI_GOVERNANCE_JWT" \ -H "Content-Type: application/json" \ -d '{ "target_type": "agent", "target_id": "'$AGENT_ID'", "note": "Drill complete" }' ``` The SDK polls kill status in the background, so a halt takes effect within one poll interval even between evaluations. Enforcement reads that cached state first, then re-checks server-side inside the evaluation. ## Next steps How rules route actions, and why envelopes are versioned and attested. Halt versus safe mode, drills, and the response ladder. Record the reasoning behind each governed action. The full enforcement API and its exception model. # Generate an evidence pack Source: https://docs.rotavision.com/guides/evidence-packs Map your live records to an RBI, DPDP, SEBI, or IRDAI instrument, check the readiness score, and export a signed evidence pack. An evidence pack is a regulator's instrument encoded as data: each clause is mapped to the evidence that satisfies it, and each mapping is scored against the live records in your deployment. The result is a readiness score and a signed bundle you can hand to an auditor. Adoption moves the score, assertions do not. This guide picks a clause map, reads its readiness, and exports a signed pack. An ingest key is enough to read every endpoint here. List the instruments this deployment maps. The `name` field is the `map_name` the pack endpoints take. ```bash cURL theme={null} curl -H "Authorization: Bearer $SAKSHI_KEY" \ https://sakshi.your-company.internal/api/v1/vidhi/clause-maps ``` ```json Response theme={null} [ { "name": "rbi-mrm-draft-2026", "regulator": "RBI", "clauses": 22 }, { "name": "dpdp-act-2023", "regulator": "MeitY", "clauses": 15 }, { "name": "sebi-ai-ml-2025", "regulator": "SEBI", "clauses": 11 }, { "name": "irdai-ai-anticipated-2026", "regulator": "IRDAI", "clauses": 6 } ] ``` | `map_name` | Instrument | | --------------------------- | ---------------------------------------------------- | | `rbi-mrm-draft-2026` | RBI draft Model Risk Management directions | | `dpdp-act-2023` | Digital Personal Data Protection Act and Draft Rules | | `sebi-ai-ml-2025` | SEBI AI/ML use in the securities market (Reg 16C) | | `irdai-ai-anticipated-2026` | IRDAI AI framework (anticipated) | Readiness scores every clause across every mapped instrument. A clause is `READY` only when each evidence type it cites passes a live check against this deployment's data. Gaps come back with the specific failing evidence and the fix that unblocks them. ```bash cURL theme={null} curl -H "Authorization: Bearer $SAKSHI_KEY" \ https://sakshi.your-company.internal/api/v1/vidhi/readiness ``` ```json Response theme={null} { "method": "clauses scored READY only when every cited evidence type passes a live check ...", "instruments": [ { "name": "rbi-mrm-draft-2026", "regulator": "RBI", "score": 0.59, "ready": [ { "clause": "56", "title": "Model identity ..." } ], "gaps": [ { "clause": "21", "failing_evidence": [ { "type": "agent_inventory", "detail": "1 orphan agent", "fix": "register + assign an owner" } ] } ] } ] } ``` Work the gaps in leverage order. One fix, like assigning an owner to an orphan agent, often unblocks several clauses at once. Generate the pack for a map. It is built live every time: chain verification runs, telemetry computes, and counts query the current records. It is never a cached document. `window_days` bounds the evidence window (default 30). ```bash theme={null} curl -H "Authorization: Bearer $SAKSHI_KEY" \ "https://sakshi.your-company.internal/api/v1/vidhi/packs/rbi-mrm-draft-2026?window_days=90" ``` The export endpoint returns a freshly generated pack, Ed25519-signed with this deployment's key over the exact bytes. Anyone holding the public key can verify it offline, with no live connection to Sakshi. ```bash cURL theme={null} curl -H "Authorization: Bearer $SAKSHI_KEY" \ "https://sakshi.your-company.internal/api/v1/vidhi/packs/rbi-mrm-draft-2026/export" \ -o rbi-mrm-pack.json ``` ```json Response (shape) theme={null} { "pack": { "pack": "rbi-mrm", "instrument": "...", "clauses": [ ... ] }, "signature": "base64-ed25519...", "public_key": "base64...", "signed_at": "2026-07-21T09:14:03Z" } ``` The signature covers the exact serialized bytes of the pack. Re-serialize or edit a field and verification fails, which is the point. ## Next steps How instruments are encoded as clause maps and scored on live evidence. The Vishwas declared-first screens that feed several clauses. The kill switch and envelope evidence that many clauses depend on. The full Vidhi endpoint surface. # Fairness screening Source: https://docs.rotavision.com/guides/fairness-screening Run Vishwas declared-first fairness screens: how declared attributes flow in, the significance gate that stops false alarms, and running a pre-deploy run. Vishwas screens decision outcomes for disparity without inferring anyone's identity. It is declared-first: it groups outcomes over attributes the applicant actually declared, never over a proxy guessed from name or locality. A flag requires both a threshold breach and statistical significance, so the screen does not cry wolf on sampling noise. This guide shows how declared attributes reach the screen, how to read the live result, and how to run a pre-deploy screen before an agent goes live. ## How declared-first screening works The screen covers two declared attributes, using the enum values below (lowercase). Gender follows the NALSA three legal categories; caste category is the one applicants already declare on priority-sector and scheme lending. | Attribute | Declared values | | ---------------- | ------------------------------- | | `gender` | `female`, `male`, `transgender` | | `caste_category` | `sc`, `st`, `obc`, `general` | Per attribute, the screen computes group approval rates, the gap in percentage points against the largest declared group, and a disparate-impact ratio against that group. A group is **flagged** only when both of these hold: 1. a **threshold breach**: the disparate-impact ratio is below `0.8` (the four-fifths convention) *or* the approval gap is at least `5pp`; and 2. **significance**: a two-proportion z-test gives `|z| >= 2.0`. A breach without significance is reported as "keep watching", not alarmed. Cohorts below the minimum of `20` decisions in the window are suppressed and surfaced, never silently dropped: silence and erasure are different things. The screen reads from the flight recorder, so declared attributes must be on the decision record. The wire contract is `context.declared`. Values outside the enums are recorded as `undisclosed`, they are never imputed. ```python theme={null} with client.witness( agent_id, client_ref="APP-2026-04471", context={ "declared": {"gender": "female", "caste_category": "obc"}, }, ) as decision: decision.step("assess", score=0.71) decision.action(decision="approve") ``` Declared values are ordinary decision context: they are tokenized and scrubbed like any other field, and the screen works over the group labels, never over an individual. See [PII tokenization](/concepts/pii-tokenization). The declared screen is computed live over the production decision stream. Pass `agent_id` to scope it to one agent. ```bash theme={null} curl -H "Authorization: Bearer $SAKSHI_KEY" \ "https://sakshi.your-company.internal/api/v1/vishwas/declared-screen?agent_id=$AGENT_ID" ``` The response reports each attribute's groups with their approval rates, `gap_vs_reference`, `di_ratio`, `z_score`, and a `flagged` boolean, plus the suppressed cohorts and the thresholds in force. ```json Response (excerpt) theme={null} { "method": "declared-attribute group screen (no inference; ...)", "flag": false, "attributes": { "caste_category": { "reference_group": "general", "groups": [ { "group": "general", "approval_rate": 0.74, "reference": true }, { "group": "sc", "approval_rate": 0.66, "gap_vs_reference": -0.08, "di_ratio": 0.892, "z_score": 1.4, "flagged": false, "note": "gap within sampling noise at this volume (|z| 1.4 < 2.0), keep watching, do not alarm" } ], "suppressed": [ { "group": "st", "decisions": 12, "reason": "below minimum cohort (20) at this window" } ], "flagged_groups": [], "flag": false } } } ``` A top-level `flag` of `null` means nothing was declared and the screen is underpowered, which is itself worth acting on. Screen a batch of candidate decisions with declared attributes before the agent is live. The stored result is a citable artifact that evidence packs reference by id. This is a governance act, so it needs an OIDC JWT. ```bash cURL theme={null} curl -X POST \ https://sakshi.your-company.internal/api/v1/vishwas/predeploy-runs \ -H "Authorization: Bearer $SAKSHI_GOVERNANCE_JWT" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "'$AGENT_ID'", "label": "loan-agent candidate v3 fairness run", "data_source": "holdout-2026-06 backtest extract", "rows": [ { "declared": { "gender": "female", "caste_category": "sc" }, "approved": true }, { "declared": { "gender": "male", "caste_category": "general" }, "approved": false } ] }' ``` ```python Python theme={null} import httpx rows = [ {"declared": {"gender": g, "caste_category": c}, "approved": bool(a)} for g, c, a in candidate_backtest ] resp = httpx.post( "https://sakshi.your-company.internal/api/v1/vishwas/predeploy-runs", headers={"Authorization": f"Bearer {governance_jwt}"}, json={ "agent_id": agent_id, "label": "loan-agent candidate v3 fairness run", "data_source": "holdout-2026-06 backtest extract", "rows": rows, }, ) run = resp.json() ``` The pre-deploy run scores the batch with the same engine as the live screen, so a flag means the same thing: a real, significant disparity, not noise. Read `flag` for the overall verdict, `attributes..flagged_groups` for the specific groups, and `suppressed` for cohorts that need a larger sample before the run can speak to them. The `caveats` block travels inside every result and should travel with any conclusion drawn from it. A clean screen is not proof of fairness, and a flagged screen is not proof of discrimination. Approval-rate parity ignores creditworthiness mix; a flag is grounds for controlled analysis, not a conclusion. The significance gate here uses a two-proportion z-test. Sakshi's model and agent eval-run store applies the same "do not overclaim on thin data" discipline a different way: it gates a pass on the Wilson 95% confidence interval's lower bound, not the point estimate, so a strong score on a small sample can still fail. ## Next steps Where fairness evidence feeds the regulator clause maps. Include a pre-deploy fairness run in a signed regulator bundle. How declared attributes reach the flight recorder. The full Vishwas endpoint surface. # Govern an MCP agent Source: https://docs.rotavision.com/guides/mcp-governance Two ways to bring an agent that speaks the Model Context Protocol under governance: run Sakshi's MCP server, or drop the interception proxy in front of the agent's real MCP server. Any agent that speaks the [Model Context Protocol](https://modelcontextprotocol.io) can be governed without touching its code. There are two approaches. Run Sakshi's MCP server and the agent gains governance tools it can call. Or drop the interception proxy between the agent and its real MCP server, and every tool call is witnessed and, optionally, enforced in the tool path. Both make tool-manifest poisoning visible on the chain, and neither ever sees a raw identifier: each is a client of the Sakshi API, so calls ride the same tokenization and hash-chain path as any other ingest. ## Option A: run Sakshi's MCP server `sakshi-mcp` exposes governance as three MCP tools. The agent opts in by calling them: it is the MCP analogue of the SDK, so it is evidence plus advisory enforcement. | Tool | When the agent calls it | | ------------------ | --------------------------------------------------------------------------------------- | | `enforce_action` | Before a consequential action, to get `auto` / `async_review` / `sync_review` / `block` | | `witness_decision` | After executing one, to record it and get the chain sequence number and hash | | `check_readiness` | Any time, for the live regulator-readiness score per instrument | ```bash theme={null} pip install sakshi-mcp ``` Point the host (Claude Desktop, Cursor, an agent framework) at the `sakshi-mcp` command. `SAKSHI_MCP_API_KEY` is a Sakshi ingest key. ```json MCP host config theme={null} { "mcpServers": { "sakshi-governance": { "command": "sakshi-mcp", "env": { "SAKSHI_API_URL": "https://sakshi.your-bank.internal", "SAKSHI_MCP_API_KEY": "sks_..." } } } } ``` To run it directly instead, stdio is the default and streamable-http is opt-in: ```bash theme={null} SAKSHI_API_URL=... SAKSHI_MCP_API_KEY=sks_... sakshi-mcp # stdio (default) SAKSHI_MCP_TRANSPORT=streamable-http sakshi-mcp # serve over HTTP ``` The agent calls `enforce_action` before any tool that moves money, changes records, or contacts a customer, and honours the result: if `allowed` is false, it stops and surfaces the reasons instead of acting. A kill switch or safe mode outranks the envelope. After executing an allowed action, it calls `witness_decision` to put the receipt on the chain. This path is opt-in: the agent chooses to consult the tools. When the agent must not be able to skip governance, use the interception proxy below, which enforces in the tool path. ## Option B: drop the interception proxy in front `sakshi-mcp-proxy` sits between the agent and its real MCP server. It relays every JSON-RPC message verbatim, so capability negotiation, resources, and prompts pass through untouched, and it taps only the messages it governs. ```text theme={null} agent <--stdio--> sakshi-mcp-proxy <--stdio--> real MCP server ``` Replace the agent's downstream server command with the proxy, followed by `--` and the real command. The agent imports nothing and changes nothing. ```bash theme={null} SAKSHI_API_URL=https://sakshi.your-bank.internal \ SAKSHI_MCP_API_KEY=sks_... \ sakshi-mcp-proxy --agent-id $AGENT_ID -- \ npx -y @modelcontextprotocol/server-filesystem /data ``` A remote downstream works too, over streamable-http: ```bash theme={null} sakshi-mcp-proxy --agent-id $AGENT_ID --http https://tools.example/mcp --header "Authorization: Bearer ..." ``` By default the proxy is transparent: it observes and witnesses, it does not block. Every `tools/call` lands an `mcp_tool_call` step with the arguments tokenized at ingest, and every `tools/list` result is hashed. Tapping is best-effort, so a witness failure never breaks or delays a relayed message. If a tool's description or schema changes mid-session, the manifest hash moves and an `mcp_manifest_changed` finding lands on the chain. That is tool poisoning made visible (the OWASP MCP Top 10 class), attributed to the specific tool that changed, rather than a silent success. Set `SAKSHI_MCP_ENFORCE=on` and each `tools/call` is routed through the agent's envelope before it reaches the tool. An `auto` outcome is forwarded; anything else is stopped and the agent gets a "parked for review" reply, never the tool. A tool poisoned mid-session is blocked outright. ```bash theme={null} SAKSHI_MCP_ENFORCE=on \ SAKSHI_MCP_FAILCLOSED_STAKES=200000 \ SAKSHI_MCP_EXEMPT_TOOLS=get_balance,search \ SAKSHI_MCP_STAKES_FIELDS=amount,amount_inr,value \ sakshi-mcp-proxy --agent-id $AGENT_ID -- ``` | Variable | Effect | | ------------------------------ | ----------------------------------------------------------------------------------------------------------- | | `SAKSHI_MCP_ENFORCE` | `on` enables tool-path blocking (default: observe only) | | `SAKSHI_MCP_FAILCLOSED_STAKES` | If Sakshi is unreachable, block calls at or above this stakes; below, forward. Unset means always fail-open | | `SAKSHI_MCP_EXEMPT_TOOLS` | Read-only tools to forward without evaluation | | `SAKSHI_MCP_STAKES_FIELDS` | Argument keys the proxy reads stakes from | | `SAKSHI_MCP_BLOCK_STYLE` | `result` (structured tool error, default) or `error` (JSON-RPC error) | Fail-static is deliberate and configurable. With `SAKSHI_MCP_ENFORCE=on` but no `SAKSHI_MCP_FAILCLOSED_STAKES`, an unreachable Sakshi fails open and forwards. Set the threshold so high-stakes calls block rather than slip through when governance is down. Already using the Python SDK? `from sakshi.middleware import watch_mcp` wraps an MCP client session so its tool discovery and calls are witnessed on the active decision, including the same tool-poisoning detection, with no separate process. ## Next steps The server and proxy in full: every tool, flag, and transport. The `watch_mcp` middleware for SDK-instrumented agents. Where witnessed tool calls and poisoning findings are recorded. The envelope the proxy enforces against in the tool path. # Register an agent Source: https://docs.rotavision.com/guides/register-an-agent Put an agent under governance: register it, set its owner and autonomy tier, activate it, and optionally verify its identity. Know Your Agent is the first module for a reason. Before an agent decides anything, it needs an entry in the inventory: an owner who is accountable, a declared autonomy level, and a verifiable identity. Nothing runs unregistered. This guide registers an agent, activates it, and attaches a cryptographic identity. It assumes you have the SDK installed (`pip install sakshi-sdk`) and a Sakshi deployment you can reach. See the [Quickstart](/quickstart) if you have neither. An ingest API key is enough to register an agent. Capture is fail-open by default, which is what you want in production: governance must never take the host agent down. ```python theme={null} from sakshi import SakshiClient client = SakshiClient( base_url="https://sakshi.your-company.internal", api_key="sks_...", ) ``` `register` is synchronous and idempotent by name, so it is safe to call on every startup: the second call returns the existing agent rather than creating a duplicate. Give it an accountable owner and, where you can, a blast radius describing what the agent can touch. ```python theme={null} agent_id = client.register( "loan-decision-agent", owner_name="Priya Sharma", owner_email="priya@your-company.com", autonomy_tier="L1", org_unit="retail-credit", blast_radius={"moves_money": True, "customer_facing": True}, description="Approves or refers retail loan applications.", ) ``` `autonomy_tier` is the declared autonomy level (`L0` through `L4`) and is a registry attribute only. The limits that are actually enforced live in the agent's [autonomy envelope](/guides/bound-autonomy). `blast_radius` is a set of boolean facts the envelope engine can match rules against. `owner_email` matters. An agent with no accountable owner is an orphan, and Sakshi surfaces orphans (`GET /api/v1/agents/orphans`) so they get an owner or get suspended. A freshly registered agent lands in `draft`. Activation flips its status to `active`, and it is a governance act, so it needs an OIDC JWT, not an ingest key. This separation is deliberate: the credential that lets an agent write evidence must not also be able to promote an agent into production. ```bash cURL theme={null} curl -X PATCH \ https://sakshi.your-company.internal/api/v1/agents/$AGENT_ID \ -H "Authorization: Bearer $SAKSHI_GOVERNANCE_JWT" \ -H "Content-Type: application/json" \ -d '{"status": "active"}' ``` ```json Response theme={null} { "id": "agt_7f3a...", "name": "loan-decision-agent", "status": "active", "autonomy_tier": "L1", "owner_email": "priya@your-company.com" } ``` Activation, ownership transfer, and envelope publication all require a governance JWT. An ingest key will get a `403`. See [Authentication](/authentication) for how to mint each credential. If the agent presents an [A2A Signed Agent Card](/concepts/know-your-agent), Sakshi verifies the Ed25519 signature over the card's canonical bytes and records the agent as identity-verified. A tampered card fails verification. You can instead record a declared Entra or workload identity, which is marked unverified honestly: a claim is not a proof. ```bash Verify a signed card theme={null} curl -X POST \ https://sakshi.your-company.internal/api/v1/agents/$AGENT_ID/identity \ -H "Authorization: Bearer $SAKSHI_KEY" \ -H "Content-Type: application/json" \ -d '{"signed_card": { "name": "loan-decision-agent", "signature": "..." }}' ``` ```bash Record a declared identity theme={null} curl -X POST \ https://sakshi.your-company.internal/api/v1/agents/$AGENT_ID/identity \ -H "Authorization: Bearer $SAKSHI_KEY" \ -H "Content-Type: application/json" \ -d '{"declared_source": "entra", "declared_ref": "agent://acme/loan-decision"}' ``` The verdict is attested, and the agent then carries an `identity_verified` flag with its `identity_source` and `identity_ref`. Sakshi verifies an external identity rather than inventing one. ## Next steps Record what the agent decides on the tamper-evident chain. Publish an envelope so the agent's actions route to auto, review, or block. The registry model: owners, tiers, provenance, and verifiable identity. Every client method, in full. # Witness a decision Source: https://docs.rotavision.com/guides/witness-a-decision Record a decision on the tamper-evident chain: open a session, capture the reasoning and outcome, then recompute the chain to verify it. A witnessed decision is the unit of governance. Each record captures the model that decided, the steps it took, the tools it called, and the action it produced, and each record is hashed onto a chain with the one before it. Anyone can recompute the chain and prove nothing was altered after the fact. This guide witnesses one decision and then verifies it independently. It assumes a [registered agent](/guides/register-an-agent) and an initialized client. `witness` returns a session used as a context manager. Pass the model identity, any context, and a client reference you can trace back to your own system. Identifiers in `context` are tokenized at ingest, so raw PII never reaches the chain. ```python theme={null} with client.witness( agent_id, model={"provider": "openai", "name": "gpt-5.2", "version": "2026-05"}, context={"applicant_aadhaar": "XXXX-XXXX-1234"}, client_ref="APP-2026-04471", ) as decision: ... ``` The `applicant_aadhaar` value above is tokenized before storage or hashing. Sakshi governs the decision, it never becomes the system of record for the identifier. See [PII tokenization](/concepts/pii-tokenization). Inside the block, record the steps in order. Each method appends to the same decision. Capture is fail-open: a recording problem is logged and dropped, it never breaks the host agent. ```python theme={null} with client.witness(agent_id, client_ref="APP-2026-04471") as decision: decision.step("retrieve", source="credit-bureau") decision.tool("bureau_lookup", output={"score": 780, "band": "prime"}) decision.step("assess", risk_band="low", score=0.92) decision.model_identity(name="gpt-5.2", version="2026-05") decision.human("review", who="reviewer:meera", verdict="concur") decision.action(outcome="approved", amount=1_500_000, mode="auto") ``` | Method | Records | | ------------------------------------- | ---------------------------------------- | | `decision.step(name, **data)` | A reasoning step | | `decision.tool(name, output, **data)` | A tool call and its result | | `decision.model_identity(**data)` | The model that decided (RBI MRM para 56) | | `decision.human(kind, who, **data)` | A human touchpoint | | `decision.action(**data)` | The final action and outcome | An exception raised inside the block is captured as the outcome and re-raised. Failures are evidence too. The record is assembled and written to the chain when the `with` block exits, not on each call. In the default fail-open mode the write is buffered on a background worker; pass `fail_open=False` to `SakshiClient` for synchronous, raising capture where losing evidence is worse than stopping. If you drive your LLM through a [middleware](/integrations/overview), each model call auto-attaches an `llm_call` step and fills in the model identity from the provider response. You get the same chain without hand-instrumenting every call. Recompute the chain and confirm no record was tampered with. This needs no trust in Rotavision: anyone with read access can run it. ```bash cURL theme={null} curl -H "Authorization: Bearer $SAKSHI_KEY" \ https://sakshi.your-company.internal/api/v1/chain/verify ``` ```json Response theme={null} { "valid": true, "checked": 2038, "first_invalid_seq": null } ``` Alter a single field in any record and `valid` becomes `false`, with `first_invalid_seq` pointing at exactly where the recomputation diverged. ## Next steps How records are hashed together and why recomputation is the proof. Decide whether an action may run before it does, not just record it after. Wrap an OpenAI, Anthropic, Gemini, Bedrock, or LangGraph agent. The witness session and every method it exposes. # Google ADK Source: https://docs.rotavision.com/integrations/adk Govern agents built with Google's Agent Development Kit: one plugin witnesses every model and tool call in the agent tree, and can enforce autonomy in the tool path, with no hard dependency on google-adk. Google's Agent Development Kit runs agents through a `Runner`, and the Runner invokes registered plugins around every agent, model, and tool step. Sakshi ships such a plugin. Register `SakshiAdkPlugin` once on the Runner and every model call and tool call in the whole agent tree is witnessed on the active decision. Turn on `enforce` and each tool call is routed through the agent's autonomy envelope before it runs. The plugin does not import `google-adk` at module load, so it degrades cleanly and carries no version lock. ## Install ```bash theme={null} pip install sakshi-sdk google-adk ``` ## Register the plugin Pass `SakshiAdkPlugin` to the Runner's `plugins` list, and open a witness session around the run. Every model and tool call inside lands on that decision. ```python Before theme={null} from google.adk.runners import Runner runner = Runner( agent=root_agent, app_name="loans", session_service=session_service, ) async for event in runner.run_async(user_id="u1", session_id="s1", new_message=message): ... ``` ```python After theme={null} from google.adk.runners import Runner from sakshi import SakshiClient from sakshi.middleware import SakshiAdkPlugin client = SakshiClient("https://sakshi.your-company.internal", api_key="sks_...") runner = Runner( agent=root_agent, app_name="loans", session_service=session_service, plugins=[SakshiAdkPlugin(client, "loan-decision-agent")], ) with client.witness("loan-decision-agent", client_ref="APP-2026-04471") as decision: async for event in runner.run_async(user_id="u1", session_id="s1", new_message=message): ... decision.action(outcome="approved", mode="auto") ``` ## What gets witnessed The plugin records onto the active witness session as the agent runs: * **Model calls** become `llm_call` steps with latency, token usage, the finish reason, and the served model identity (RBI draft MRM para 56). ADK agents usually run on Gemini, but the plugin reads the model generically, so LiteLLM-backed models are recorded too. * **Tool calls** become `tool_call` steps with the tool name, the argument keys (never the values, which are tokenized at ingest anyway), the latency, and the result status. A tool that raises is recorded as an error, since failures are evidence too. * **Agents** are bracketed with `agent_start` and `agent_end` steps, so a multi-agent tree reads as nested control flow on the chain. ## Enforce in the tool path With `enforce=True`, the plugin routes each tool call through the agent's autonomy envelope in `before_tool_callback`, before the tool runs. An `auto` outcome lets the tool proceed. Anything else (review or block) stops the tool and returns a structured "parked for review" result to the agent instead of executing it. This is the same tool-path boundary as the MCP proxy, applied inside ADK. ```python theme={null} plugin = SakshiAdkPlugin( client, "loan-decision-agent", enforce=True, exempt_tools=("check_credit", "lookup_customer"), # read-only, never gated stakes_fields=("amount", "stakes", "value"), # read stakes from tool args confidence_fields=("confidence", "score"), ) ``` The stakes and confidence signals are read from the tool's own arguments by the field names you configure, and passed to the envelope. A read-only tool listed in `exempt_tools` is never gated. Enforcement is **fail-closed**: if Sakshi is unreachable, a governed tool is blocked rather than run ungoverned. Witnessing is **fail-open**: a recording problem never breaks the agent, and with no active witness session the plugin is a no-op. `enforce=True` requires the client and the agent id. ## Combine with MCP tools If your ADK agent reaches tools over the Model Context Protocol, you can also govern that layer with the [MCP integration](/integrations/mcp), which detects tool-manifest poisoning. The two compose: the ADK plugin governs the agent's own tool calls, and the MCP surface governs the MCP tool boundary. The client, the witness session, and enforcement. # Anthropic Source: https://docs.rotavision.com/integrations/anthropic Witness Anthropic Messages API calls with one line, with no dependency on the Anthropic SDK. `watch_anthropic` wraps any client that exposes `messages.create`, the Anthropic SDK shape. It is duck-typed, so the Sakshi SDK does not depend on the `anthropic` package: it wraps the client you already hold. ## Install ```bash theme={null} pip install sakshi-sdk anthropic ``` ## Wrap the client ```python Before theme={null} from anthropic import Anthropic client = Anthropic() reply = client.messages.create( model="claude-opus-4-6", max_tokens=1024, messages=[{"role": "user", "content": "Assess this applicant."}], ) ``` ```python After theme={null} from anthropic import Anthropic from sakshi.middleware import watch_anthropic client = watch_anthropic(Anthropic()) with sakshi.witness(agent_id, client_ref="APP-2026-04471") as decision: reply = client.messages.create( model="claude-opus-4-6", max_tokens=1024, messages=[{"role": "user", "content": "Assess this applicant."}], ) decision.action(outcome="approved", mode="auto") ``` See [the shared shape](/integrations/overview#the-shared-shape) for how `sakshi` and `agent_id` are created. ## What gets recorded Each message inside a witness session lands an `llm_call` step, and the served model identity auto-fills (para-56): * The provider, recorded as `anthropic`. * `model_requested` versus `model_served`, so a silent model swap is visible. * The response id and the stop reason. * Input and output token usage. * The call latency. * With content capture on, the last message you sent and the first text block of the response, each truncated to `content_limit`. ## Capture options ```python theme={null} watch_anthropic(client, capture_content=True, content_limit=1000) ``` * `capture_content` defaults to `True`. Content capture is safe by design: the platform tokenizes PII at ingest, before storage or hashing. Set it to `False` for a minimal-capture deployment where only metadata, tokens, and identity are recorded. * `content_limit` caps how much of each message and response is stored. The default is 1000 characters. A call made outside a witness session passes through untouched and is not recorded. Recording is fail-open: it never breaks or delays the message call. The client, the witness session, and enforcement. # AutoGen Source: https://docs.rotavision.com/integrations/autogen Govern AutoGen agents at the tool boundary and the model client. There is no dedicated one-line middleware, because AutoGen does not expose a single tool-and-model hook, so Sakshi is honest about the two paths that do work. AutoGen comes in two lineages, Microsoft's `autogen-agentchat` (the 0.4 rewrite) and the `ag2` / `pyautogen` fork. Both build their model client internally from a config, and neither exposes a single hook that sees both tool calls and model calls the way [ADK](/integrations/adk), [CrewAI](/integrations/crewai), and [Strands](/integrations/strands) do. So Sakshi does not ship a `watch_autogen`, and does not pretend to. It governs AutoGen at the two boundaries that are stable across both lineages: the tool and the model client. ## Govern at the tool boundary This is the path that works across every AutoGen version, and it is where the consequential decisions are. Open a witness session around the run, and inside each tool that matters, witness the step and call `enforce` before acting. ```python theme={null} from sakshi import SakshiClient, SakshiBlocked, SakshiSyncReviewRequired client = SakshiClient("https://sakshi.your-company.internal", api_key="sks_...") def approve_loan(application_id: str, amount: int) -> str: """A tool your AutoGen agent can call.""" from sakshi import current_session session = current_session() if session is not None: session.step("tool_call", via="autogen", tool="approve_loan", arg_keys=["application_id", "amount"]) try: client.enforce("loan-agent", "approve_loan", stakes=amount) except (SakshiBlocked, SakshiSyncReviewRequired): return "Parked for review: this action needs a human." ... # only runs when the envelope returns auto with client.witness("loan-agent", client_ref="APP-2026-04471") as decision: # run your AutoGen agent / team here; the tool records and enforces as it fires decision.action(outcome="approved") ``` Register `approve_loan` as a tool with your AutoGen agent as you normally would. The witness session is active for the duration of the run, so the tool's `step` lands on the same decision, and `enforce` fails closed. ## Witness the model calls If you construct the underlying provider client yourself, wrap it with the matching Sakshi middleware before AutoGen uses it, and every model call inside the witness session is recorded, exactly as for a direct [OpenAI](/integrations/openai) or [Anthropic](/integrations/anthropic) call. ```python theme={null} from openai import OpenAI from sakshi.middleware import watch_openai_compatible llm = watch_openai_compatible(OpenAI()) # pass `llm` wherever your AutoGen setup accepts an explicit client ``` When AutoGen builds the client internally from a config dict, there is no object to wrap; use the tool-boundary path above, which is where governance matters most. This is the same honesty as the [LangChain](/integrations/langchain) page: Sakshi will not ship a middleware that implies a clean hook the framework does not offer. If AutoGen adds a stable tool-and-model interception point, a dedicated integration follows. The client, the witness session, and enforcement. # AWS Bedrock Source: https://docs.rotavision.com/integrations/bedrock Witness AWS Bedrock Converse calls with one line. Residency-compliant in AWS Mumbai for Indian regulated entities. `watch_bedrock` wraps a boto3 `bedrock-runtime` client's `converse` method. It is duck-typed, so the Sakshi SDK does not depend on boto3: it wraps the client you already build. Bedrock in the AWS Mumbai region is the residency-compliant hosted path for Indian regulated entities, so the `modelId` captured here is para-56 evidence on the deployment most partners will actually run. ## Install ```bash theme={null} pip install sakshi-sdk boto3 ``` ## Wrap the client ```python Before theme={null} import boto3 client = boto3.client("bedrock-runtime", region_name="ap-south-1") reply = client.converse( modelId="anthropic.claude-opus-4-6-v1:0", messages=[{"role": "user", "content": [{"text": "Assess this applicant."}]}], ) ``` ```python After theme={null} import boto3 from sakshi.middleware import watch_bedrock client = watch_bedrock( boto3.client("bedrock-runtime", region_name="ap-south-1") ) with sakshi.witness(agent_id, client_ref="APP-2026-04471") as decision: reply = client.converse( modelId="anthropic.claude-opus-4-6-v1:0", messages=[{"role": "user", "content": [{"text": "Assess this applicant."}]}], ) decision.action(outcome="approved", mode="auto") ``` See [the shared shape](/integrations/overview#the-shared-shape) for how `sakshi` and `agent_id` are created. ## What gets recorded Each Converse call inside a witness session lands an `llm_call` step, and the served model identity auto-fills (para-56): * The provider, recorded as `aws-bedrock`. * The requested `modelId`. * The stop reason. * Input and output token usage. * The call latency. * With content capture on, the last message you sent and the first text block of the response, each truncated to `content_limit`. ## Capture options ```python theme={null} watch_bedrock(client, capture_content=True, content_limit=1000) ``` * `capture_content` defaults to `True`. Content capture is safe by design: the platform tokenizes PII at ingest, before storage or hashing. Set it to `False` for a minimal-capture deployment where only metadata, tokens, and identity are recorded. * `content_limit` caps how much of each message and response is stored. The default is 1000 characters. A call made outside a witness session passes through untouched and is not recorded. Recording is fail-open: it never breaks or delays the Converse call. The client, the witness session, and enforcement. # CrewAI Source: https://docs.rotavision.com/integrations/crewai Witness a CrewAI crew: register one event listener and every model call and tool call in the crew lands on the chain, with no hard dependency on crewai. CrewAI emits events across a crew's lifecycle (kickoff, agent, task, LLM call, tool usage) on a global event bus. Sakshi ships a listener for it. Instantiate `SakshiCrewListener` once before `crew.kickoff()` and every model call and tool call inside a witness session is recorded on the chain. The listener does not import `crewai` at module load, so it degrades cleanly and carries no version lock. ## Install ```bash theme={null} pip install sakshi-sdk crewai ``` ## Register the listener Create the listener once (it registers on CrewAI's event bus), then run your crew inside a witness session. ```python Before theme={null} from crewai import Crew crew = Crew(agents=[...], tasks=[...]) result = crew.kickoff(inputs={"application_id": "APP-2026-04471"}) ``` ```python After theme={null} from crewai import Crew from sakshi import SakshiClient from sakshi.middleware import SakshiCrewListener client = SakshiClient("https://sakshi.your-company.internal", api_key="sks_...") SakshiCrewListener() # registers on the CrewAI event bus (once) crew = Crew(agents=[...], tasks=[...]) with client.witness("loan-crew", client_ref="APP-2026-04471") as decision: result = crew.kickoff(inputs={"application_id": "APP-2026-04471"}) decision.action(outcome="approved") ``` ## What gets witnessed As the crew runs, the listener records onto the active witness session: * **Model calls** become `llm_call` steps with the served model, token usage, and finish reason. * **Tool calls** become `tool_call` steps with the tool name, the argument keys (never the values, which are tokenized at ingest anyway), the latency, and the role of the agent that called it. A tool that errors is recorded as such. * **Crew, agent, and task** boundaries are bracketed, so a multi-agent crew reads as structured control flow on the chain. Because inputs are tokenized at ingest, a PAN or Aadhaar that flows through a task is tokenized before it is stored, exactly as with every other integration. ## Enforcement in CrewAI CrewAI's event bus is observe-only, so this middleware witnesses; it does not block a tool the way the [ADK](/integrations/adk) or [MCP](/integrations/mcp) tool-path enforcement does. To gate a consequential action inside CrewAI, call `enforce` at the top of the tool's own function and act on the outcome. ```python theme={null} from crewai.tools import tool from sakshi import SakshiBlocked, SakshiSyncReviewRequired @tool("approve_loan") def approve_loan(application_id: str, amount: int) -> str: try: client.enforce("loan-crew", "approve_loan", stakes=amount) except (SakshiBlocked, SakshiSyncReviewRequired): return "Parked for review: this action needs a human." ... # only runs when the envelope returns auto ``` Recording is fail-open: a problem writing a step never breaks the crew, and with no active witness session the listener is a no-op. CrewAI dispatches event handlers asynchronously and flushes them before finishing kickoff, so every model and tool call is recorded; the final crew close-bracket is best-effort. Call `crewai_event_bus.flush()` after `kickoff` if you want it guaranteed before the witness session closes. The client, the witness session, and enforcement. # Google Gemini Source: https://docs.rotavision.com/integrations/gemini Witness Google Gemini generate_content calls with one line, with no dependency on the google-genai SDK. `watch_gemini` wraps any client that exposes `models.generate_content`, the google-genai SDK shape. It is duck-typed, so the Sakshi SDK does not depend on the `google-genai` package: it wraps the client you already hold. ## Install ```bash theme={null} pip install sakshi-sdk google-genai ``` ## Wrap the client ```python Before theme={null} from google import genai client = genai.Client() reply = client.models.generate_content( model="gemini-2.5-pro", contents="Assess this applicant.", ) ``` ```python After theme={null} from google import genai from sakshi.middleware import watch_gemini client = watch_gemini(genai.Client()) with sakshi.witness(agent_id, client_ref="APP-2026-04471") as decision: reply = client.models.generate_content( model="gemini-2.5-pro", contents="Assess this applicant.", ) decision.action(outcome="approved", mode="auto") ``` See [the shared shape](/integrations/overview#the-shared-shape) for how `sakshi` and `agent_id` are created. ## What gets recorded Each generation inside a witness session lands an `llm_call` step, and the served model identity auto-fills (para-56): * The provider, recorded as `google-gemini`. * `model_requested` versus `model_served`, taken from the response `model_version`, so a silent model swap is visible. * Prompt and candidate token usage. * The call latency. * With content capture on, the `contents` you sent and the response text, each truncated to `content_limit`. ## Capture options ```python theme={null} watch_gemini(client, capture_content=True, content_limit=1000) ``` * `capture_content` defaults to `True`. Content capture is safe by design: the platform tokenizes PII at ingest, before storage or hashing. Set it to `False` for a minimal-capture deployment where only metadata, tokens, and identity are recorded. * `content_limit` caps how much of the input and response is stored. The default is 1000 characters. A call made outside a witness session passes through untouched and is not recorded. Recording is fail-open: it never breaks or delays the generation. The client, the witness session, and enforcement. # LangChain Source: https://docs.rotavision.com/integrations/langchain Witness LangChain apps through the provider middleware underneath, and the LangGraph path for graph-structured agents. There is no `watch_langchain`, and Sakshi does not invent one. LangChain does not run models itself: it builds on the same provider SDKs Sakshi already wraps, and modern LangChain agents compile to a graph. So a LangChain app is witnessed through the surfaces that already exist, with no LangChain dependency in the SDK. There are two accurate paths, and you can use both together. ## Path A: wrap the provider client (recommended for model evidence) A LangChain chat model calls a provider SDK client underneath. Build that client yourself, wrap it with the matching Sakshi middleware, and hand the wrapped client to the chat model. Every completion then lands an `llm_call` step with the served model identity (para-56), tokens, and outcome. ```python OpenAI-backed theme={null} from openai import OpenAI from langchain_openai import ChatOpenAI from sakshi.middleware import watch_openai_compatible client = watch_openai_compatible(OpenAI()) llm = ChatOpenAI(model="gpt-5.2", client=client.chat.completions) with sakshi.witness(agent_id, client_ref="APP-2026-04471") as decision: reply = llm.invoke("Assess this applicant.") decision.action(outcome="approved", mode="auto") ``` ```python Anthropic-backed theme={null} from anthropic import Anthropic from langchain_anthropic import ChatAnthropic from sakshi.middleware import watch_anthropic client = watch_anthropic(Anthropic()) llm = ChatAnthropic(model="claude-opus-4-6", client=client.messages) with sakshi.witness(agent_id, client_ref="APP-2026-04471") as decision: reply = llm.invoke("Assess this applicant.") decision.action(outcome="approved", mode="auto") ``` The middleware wraps the client and exposes the same `chat.completions` or `messages` surface LangChain calls, so what gets recorded is exactly what the [OpenAI](/integrations/openai) and [Anthropic](/integrations/anthropic) pages describe. Consult your LangChain version for the precise constructor argument that accepts a preconstructed client. ## Path B: witness the graph (recommended for agents) A LangChain agent built with LangGraph, or with a helper like `create_react_agent`, compiles to a graph that exposes `invoke` and `stream`. Wrap it with `watch_langgraph`, and decorate individual nodes with `witness_node`. This path is provider-agnostic: it does not need to know which model sits underneath. ```python theme={null} from sakshi.middleware import watch_langgraph agent = watch_langgraph(compiled_agent) with sakshi.witness(agent_id, client_ref="APP-2026-04471") as decision: result = agent.invoke({"messages": [("user", "Assess this applicant.")]}) decision.action(outcome="approved", mode="auto") ``` Combine both paths for the fullest evidence: Path B records the graph and node control flow, and Path A records the model calls inside each node. If your LangChain app is a graph, start with Path B. See [LangGraph](/integrations/langgraph) for `watch_langgraph` and `witness_node`. The client, the witness session, and enforcement. # LangGraph Source: https://docs.rotavision.com/integrations/langgraph Witness graph-structured agents: bracket a compiled LangGraph and record individual nodes, with no dependency on LangGraph. LangGraph agents are graphs of nodes. Sakshi witnesses them at two levels, and neither imports `langgraph`. `watch_langgraph` brackets a whole run. `witness_node` records a single node. Combine them with a provider middleware, and the model calls inside each node are recorded as `llm_call` steps in order, nested under the graph and node steps. ## Install ```bash theme={null} pip install sakshi-sdk langgraph ``` ## Bracket a compiled graph `watch_langgraph` wraps a compiled graph's `invoke` and `stream` so each run is bracketed by `graph_start` and `graph_end` steps, with latency and, for streams, the chunk count. ```python Before theme={null} graph = builder.compile() result = graph.invoke({"application": application}) ``` ```python After theme={null} from sakshi.middleware import watch_langgraph graph = watch_langgraph(builder.compile()) with sakshi.witness(agent_id, client_ref="APP-2026-04471") as decision: result = graph.invoke({"application": application}) decision.action(outcome="approved", mode="auto") ``` The optional `name` labels the graph in the evidence. If you do not pass it, the graph's own name or its type name is used. ```python theme={null} graph = watch_langgraph(builder.compile(), name="loan-decision-graph") ``` ## Witness individual nodes `witness_node` wraps a node callable so each execution lands a `graph_node` step: the node name, the latency, which state keys the node updated, and the error if it raised. Failures are evidence too, so the exception is re-raised after it is recorded. The state values themselves are not captured, only the keys. ```python theme={null} from sakshi.middleware import witness_node def assess(state): ... return {"risk_band": "low", "score": 0.92} # Bare, wrapping an existing callable: builder.add_node("assess", witness_node(assess)) # Or as a decorator, with an optional name override: @witness_node(name="assess") def assess(state): ... ``` Both the sync and async node signatures are supported, including the `state` and `state, config` forms. ## Combine with a provider middleware The graph and node steps describe the control flow. To also record the model calls, wrap the provider client that your nodes use with the matching middleware, for example [OpenAI](/integrations/openai) or [Anthropic](/integrations/anthropic). Because every step attaches to the same active witness session, the `llm_call` steps land in order under the node that made them. Recording is fail-open across all three surfaces: a problem writing a graph, node, or model step never breaks the run. With no active witness session, calls pass through untouched. The client, the witness session, and enforcement. # MCP Source: https://docs.rotavision.com/integrations/mcp Witness an agent's Model Context Protocol tool calls, and make tool-manifest poisoning chain-visible, with one line. `watch_mcp` wraps an MCP client session so an agent's tool discovery and tool calls are witnessed on the active decision. It is duck-typed, so the Sakshi SDK does not depend on the MCP SDK. The differentiated beat: if a server rewrites a tool's description or schema mid-session, the change lands on the chain as a finding, so tool poisoning is evidence rather than a silent success. This page covers the client-side middleware. To expose Sakshi's enforce and witness capabilities as MCP tools, or to front a server with an intercepting proxy that enforces in the tool path, see [MCP governance](/sdk/mcp) and the [MCP governance guide](/guides/mcp-governance). ## Install ```bash theme={null} pip install sakshi-sdk ``` ## Wrap the session MCP sessions are async, so the wrapper's `list_tools` and `call_tool` are async and everything else delegates through. ```python Before theme={null} async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() result = await session.call_tool("transfer_funds", {"account": "…", "amount": 5000}) ``` ```python After theme={null} from sakshi.middleware import watch_mcp async with ClientSession(read, write) as inner: await inner.initialize() session = watch_mcp(inner, server="bank-tools") with sakshi.witness(agent_id, client_ref="APP-2026-04471") as decision: tools = await session.list_tools() result = await session.call_tool( "transfer_funds", {"account": "…", "amount": 5000} ) decision.action(outcome="approved", mode="auto") ``` The optional `server` labels which MCP server the session talks to, for the evidence. See [the shared shape](/integrations/overview#the-shared-shape) for how `sakshi` and `agent_id` are created. ## What gets recorded * On the first `list_tools`, an `mcp_tools` step with the tool names and a SHA-256 digest of the tool manifest, which is each tool's name, description, and input schema. * On each `call_tool`, an `mcp_tool_call` step with the tool name, the argument keys, the latency, the result status, and the error if the call raised. Argument values are not captured here, and are tokenized at ingest in any case. * If a later `list_tools` returns a different manifest digest, an `mcp_manifest_changed` finding, because the description or schema is the tool poisoning vector (the 2025 incident class, OWASP MCP Top 10). Recording is fail-open: it never breaks the host tool call, and a call raised by the server is re-raised after it is recorded. With no active witness session, calls pass through untouched. The Sakshi MCP server and the interception proxy for zero-code governance. The server and proxy in practice, including tool-path enforcement. # OpenAI and OpenAI-compatible Source: https://docs.rotavision.com/integrations/openai Witness OpenAI and any OpenAI-compatible runtime, including self-hosted Ollama, vLLM, TGI, and LM Studio, with one line. `watch_openai_compatible` wraps any client that exposes `chat.completions.create`. That is the official OpenAI client, and it is also the sovereignty path: point a client at a base URL and the same wrapper witnesses Ollama, vLLM, TGI, or LM Studio running inside your own environment. One integration covers every runtime that speaks the OpenAI API. ## Install ```bash theme={null} pip install sakshi-sdk openai ``` ## Wrap the client The change is one line: wrap the client you already build. Everything downstream keeps calling it the same way. ```python Before theme={null} from openai import OpenAI client = OpenAI() reply = client.chat.completions.create( model="gpt-5.2", messages=[{"role": "user", "content": "Assess this applicant."}], ) ``` ```python After theme={null} from openai import OpenAI from sakshi.middleware import watch_openai_compatible client = watch_openai_compatible(OpenAI()) with sakshi.witness(agent_id, client_ref="APP-2026-04471") as decision: reply = client.chat.completions.create( model="gpt-5.2", messages=[{"role": "user", "content": "Assess this applicant."}], ) decision.action(outcome="approved", mode="auto") ``` See [the shared shape](/integrations/overview#the-shared-shape) for how `sakshi` and `agent_id` are created. ## Self-hosted and sovereign runtimes The provider is derived from the client's base URL. A call to `openai.com` records as `openai`. A call to `localhost`, a private address, or a bare service hostname records as `self-hosted (host)`, so the evidence reflects that the model never left your network. ```python theme={null} from openai import OpenAI from sakshi.middleware import watch_openai_compatible # An Ollama server on the same host, or a vLLM service in the cluster. local = watch_openai_compatible( OpenAI(base_url="http://ollama:11434/v1", api_key="ollama") ) with sakshi.witness(agent_id) as decision: reply = local.chat.completions.create( model="qwen2.5:1.5b", messages=[{"role": "user", "content": "Assess this applicant."}], ) ``` ## What gets recorded Each completion inside a witness session lands an `llm_call` step, and the served model identity auto-fills (para-56): * The provider, derived from the base URL as above. * `model_requested` versus `model_served`, so a silent model swap is visible. * `system_fingerprint` when the provider returns one. * Prompt and completion token usage. * The finish reason and the call latency. * With content capture on, the last message you sent and the response text, each truncated to `content_limit`. ## Capture options ```python theme={null} watch_openai_compatible(client, capture_content=True, content_limit=1000) ``` * `capture_content` defaults to `True`. Content capture is safe by design: the platform tokenizes PII at ingest, before storage or hashing. Set it to `False` for a minimal-capture deployment where only metadata, tokens, and identity are recorded. * `content_limit` caps how much of each message and response is stored. The default is 1000 characters. A call made outside a witness session passes through untouched and is not recorded. Recording is fail-open: it never breaks or delays the completion. The client, the witness session, and enforcement. # Integrations Overview Source: https://docs.rotavision.com/integrations/overview Wrap your provider client with a Sakshi middleware, add one line, and every model call your agent makes lands on the same tamper-evident chain. Sakshi witnesses the AI agents you already run. You do not rewrite the agent. You wrap the client it calls, and every model call is recorded on the chain, with the served model identity, tokenized inputs, and outcome. Sakshi never sees a raw identifier: PII is tokenized at ingest, before anything is stored or hashed.
OpenAI Anthropic Google Gemini AWS Bedrock LangChain LangGraph Google ADK CrewAI AWS Strands AutoGen MCP

Product names and marks are trademarks of their respective owners, shown to indicate compatibility, not endorsement.

## The integration doctrine There are three ways in, and every one lands on the same chain. A few lines. You open a witness session and record the steps of a decision yourself. Full control over what becomes evidence. One line. Wrap the provider client once and every model call inside a witness session is recorded automatically, with no rewrite of the agent. Govern any agent that speaks the Model Context Protocol, including agents you cannot instrument, through a client session wrapper or a proxy. The middlewares are the shortest path. They are duck-typed, so the Sakshi SDK carries no dependency on any provider SDK: a middleware wraps any object with the right shape, attaches to the active witness session, and auto-fills the served model identity from the provider's response (the RBI draft para-56 evidence, per decision). ## Supported providers The OpenAI client, plus Ollama, vLLM, TGI, and LM Studio through a base URL. The sovereign, self-hosted path. The Anthropic Messages API shape. The google-genai SDK shape. The bedrock-runtime Converse API. Residency-compliant in AWS Mumbai. The same provider middlewares underneath, plus the LangGraph path for graph apps. Bracket a compiled graph and witness individual nodes. Register one plugin on the Runner to witness the whole agent tree, and optionally enforce in the tool path. Register one event listener to witness a crew's model and tool calls. One hook provider to witness tool and model calls, and cancel a tool through the envelope. Govern at the tool boundary and the model client. The honest paths that work. | Runtime | Function | Wraps | | ------------------------------------ | --------------------------------- | --------------------------------------------------------- | | OpenAI, Ollama, vLLM, TGI, LM Studio | `watch_openai_compatible` | `chat.completions.create` | | Anthropic | `watch_anthropic` | `messages.create` | | Google Gemini | `watch_gemini` | `models.generate_content` | | AWS Bedrock | `watch_bedrock` | `converse` | | LangGraph | `watch_langgraph`, `witness_node` | a compiled graph, a node callable | | Google ADK | `SakshiAdkPlugin` | a Runner plugin (witness the tree, enforce the tool path) | | CrewAI | `SakshiCrewListener` | an event listener (witness the crew's model + tool calls) | | AWS Strands | `SakshiStrandsHooks` | a hook provider (witness + enforce the tool path) | | AutoGen | (tool-boundary pattern) | witness + enforce inside your tools | | MCP | `watch_mcp` | an MCP client session | Every function lives in `sakshi.middleware`: ```python theme={null} from sakshi.middleware import ( watch_openai_compatible, watch_anthropic, watch_gemini, watch_bedrock, watch_langgraph, witness_node, watch_mcp, SakshiAdkPlugin, SakshiCrewListener, SakshiStrandsHooks, ) ``` ## The shared shape Every integration follows the same four steps: point the SDK at your deployment, register the agent, wrap the provider client once, then make calls inside a witness session. ```python theme={null} from sakshi import SakshiClient from sakshi.middleware import watch_openai_compatible from openai import OpenAI # 1. Point the SDK at your Sakshi deployment. sakshi = SakshiClient( base_url="https://sakshi.your-company.internal", api_key="sks_...", ) # 2. Register the agent. Idempotent by name, so safe on every startup. agent_id = sakshi.register( "loan-decision-agent", owner_name="Priya Sharma", owner_email="priya@your-company.com", autonomy_tier="L1", ) # 3. Wrap the provider client once. This is the one line. llm = watch_openai_compatible(OpenAI()) # 4. Calls made inside a witness session are recorded automatically. with sakshi.witness(agent_id, client_ref="APP-2026-04471") as decision: reply = llm.chat.completions.create( model="gpt-5.2", messages=[{"role": "user", "content": "Assess this applicant."}], ) decision.action(outcome="approved", amount=1_500_000, mode="auto") ``` You do not pass the model to `witness` when a middleware is in play. The middleware fills the served model identity from the provider's response, so the record reflects what actually answered, not what you intended to call. Evidence belongs to decisions. A model call made outside a witness session passes through untouched and is not recorded. Recording is also fail-open: a problem writing evidence never breaks or delays the host agent's call. ## Where to go next The client, the witness session, enforcement, and the exception model. The MCP server and the interception proxy for zero-code governance. How steps become a tamper-evident record anyone can recompute. Register, witness, and verify the chain in a few minutes. # AWS Strands Source: https://docs.rotavision.com/integrations/strands Govern agents built with the AWS Strands Agents SDK: one hook provider witnesses every tool and model call, and can cancel a tool through the autonomy envelope, with no hard dependency on strands. A Strands `Agent` runs with a list of hook providers, and the SDK invokes their callbacks around every tool call and model call. Sakshi ships one. `SakshiStrandsHooks` is a `HookProvider`: pass it via `Agent(hooks=[...])` and every tool and model call is witnessed on the active decision. Turn on `enforce` and each tool call is routed through the agent's autonomy envelope before it runs. The provider does not import `strands` at module load, so it degrades cleanly and carries no version lock. ## Install ```bash theme={null} pip install sakshi-sdk strands-agents ``` ## Register the hooks Pass `SakshiStrandsHooks` in the agent's `hooks` list, and run inside a witness session. ```python Before theme={null} from strands import Agent agent = Agent(model=model, tools=[check_credit, approve_loan]) agent("Assess this applicant.") ``` ```python After theme={null} from strands import Agent from sakshi import SakshiClient from sakshi.middleware import SakshiStrandsHooks client = SakshiClient("https://sakshi.your-company.internal", api_key="sks_...") agent = Agent( model=model, tools=[check_credit, approve_loan], hooks=[SakshiStrandsHooks(client, "loan-decision-agent", enforce=True)], ) with client.witness("loan-decision-agent", client_ref="APP-2026-04471") as decision: agent("Assess this applicant.") decision.action(outcome="approved") ``` ## What gets witnessed As the agent runs, the provider records onto the active witness session: * **Model calls** become `llm_call` steps with the served model and the finish reason. * **Tool calls** become `tool_call` steps with the tool name, the argument keys (never the values, which are tokenized at ingest anyway), and the status. A tool that raises or is cancelled is recorded as such. * **The agent invocation** is bracketed with `agent_start` and `agent_end`. ## Enforce in the tool path With `enforce=True`, the provider routes each tool call through the agent's autonomy envelope in `BeforeToolCallEvent`, before the tool runs. An `auto` outcome lets the tool proceed; anything else cancels the tool (Strands' `cancel_tool`) so it never executes, and a `tool_blocked` step records why. ```python theme={null} hooks = SakshiStrandsHooks( client, "loan-decision-agent", enforce=True, exempt_tools=("check_credit",), # read-only, never gated stakes_fields=("amount", "stakes"), # read stakes from tool input confidence_fields=("confidence", "score"), ) ``` The stakes and confidence signals are read from the tool's own input by the field names you configure. A read-only tool listed in `exempt_tools` is never gated. Enforcement is **fail-closed**: if Sakshi is unreachable, a governed tool is cancelled rather than run ungoverned. Witnessing is **fail-open**: a recording problem never breaks the agent, and with no active witness session the provider is a no-op. `enforce=True` requires the client and the agent id. The client, the witness session, and enforcement. # Introduction Source: https://docs.rotavision.com/introduction Sakshi is the governance layer for AI agents in Indian enterprises: register every agent, witness every decision on a tamper-evident chain, bound autonomy, and produce regulator-mapped evidence. Sakshi (साक्षी, "witness") is a governance layer that sits beside the AI agents you already run. It does not replace your models or your orchestration. It makes them accountable: every agent is inventoried, every decision is recorded on a chain anyone can recompute, autonomy is bounded by policy, and the result is evidence you can hand to an auditor or a regulator. It is built for regulated Indian enterprises first, with clause maps for RBI Model Risk Management, DPDP, SEBI, and IRDAI, and it runs single-tenant inside your own environment. ## The mental model As models become agents, the unit of governance shifts from the model to the decision. Sakshi is organized around that idea: Know Your Agent. A live inventory of every agent and model with an owner, an autonomy tier, provenance, and a verifiable identity. Nothing runs unregistered. The flight recorder. Every decision is hashed onto a tamper-evident chain with the model identity, tokenized inputs, and outcome, recomputable by anyone. Autonomy envelopes. Each action routes to auto, human review, or block by stakes and confidence, with oversight telemetry and a drilled kill switch. Evidence and fairness. Regulator instruments encoded as data and scored against live evidence, plus declared-first fairness screens gated on statistical significance. ## How you connect There are three ways in, and every one lands on the same chain. Sakshi never sees a raw identifier. Wrap your calls in a few lines with the Python SDK. Add one line to an OpenAI, Anthropic, Gemini, Bedrock, LangChain, LangGraph, Google ADK, CrewAI, or AWS Strands agent. Govern any agent that speaks the Model Context Protocol. ## What to read next Install the SDK, register an agent, witness a decision, and recompute the chain in a few minutes. The ideas underneath Sakshi: the chain, autonomy envelopes, evidence packs, and PII tokenization. Every endpoint, generated from the live OpenAPI spec. A private, synthetic-data instance to poke at, no install required. Sakshi is single-tenant and runs in your environment, so the base URL in these docs is your own deployment. Examples use the public demo (`https://demo.rotavision.com`) where a running instance helps. # Quickstart Source: https://docs.rotavision.com/quickstart Install the SDK, register an agent, witness a decision, and recompute the chain. This walks through the shortest path to an accountable agent: register it, witness one decision, and verify the record independently. It takes a few minutes. ## Prerequisites * Python 3.9 or newer * A **writable** Sakshi instance you can reach: your own deployment, or a [sandbox](https://rotavision.com/sandbox), a private synthetic-data instance with no install. The public demo at `https://demo.rotavision.com` is read-only, so it cannot register or witness. * An ingest API key. In a sandbox or your own deployment, mint one in the console under **Admin → API keys**. See [Authentication](/authentication). ## 1. Install the SDK ```bash theme={null} pip install sakshi-sdk ``` The distribution is `sakshi-sdk`; the import is `sakshi`. ## 2. Initialize the client ```python theme={null} from sakshi import SakshiClient client = SakshiClient( base_url="https://sakshi.your-company.internal", api_key="sks_...", ) ``` Capture is **fail-open** by default: if the platform is unreachable, your agent keeps running and records are dropped with a warning. Governance must never take production down. Pass `fail_open=False` for batch jobs where losing evidence is worse than stopping. ## 3. Register the agent An agent must not run unregistered. `register` is idempotent by name, so it is safe to call on every startup. ```python theme={null} agent_id = client.register( "loan-decision-agent", owner_name="Priya Sharma", owner_email="priya@your-company.com", autonomy_tier="L1", description="Approves or refers retail loan applications.", ) ``` ## 4. Witness a decision Open a witness session as a context manager. Record the steps as they happen; the decision record is written to the chain when the block exits. Identifiers you pass are tokenized at ingest, so raw PII never lands on the chain. ```python theme={null} with client.witness( agent_id, model={"provider": "openai", "name": "gpt-5.2", "version": "2026-05"}, context={"applicant_aadhaar": "XXXX-XXXX-1234"}, client_ref="APP-2026-04471", ) as decision: decision.step("retrieve", source="credit-bureau") decision.step("assess", risk_band="low", score=0.92) decision.action(outcome="approved", amount=1_500_000, mode="auto") ``` ## 5. Bound the autonomy (optional) Before an agent acts, route the intended action through its envelope. Enforcement fails **closed**: if Sakshi is unreachable it raises rather than letting an ungoverned action through. ```python theme={null} from sakshi import SakshiBlocked, SakshiSyncReviewRequired try: client.enforce(agent_id, "approve_loan", stakes=1_500_000, confidence=0.92) # allowed: proceed with the action except SakshiSyncReviewRequired: # parked for a human before it can run ... except SakshiBlocked: # the envelope (or the kill switch) blocked it ... ``` ## 6. Verify the chain Anyone can recompute the chain. No trust in Rotavision required. ```bash cURL theme={null} curl -H "Authorization: Bearer sks_..." \ https://sakshi.your-company.internal/api/v1/chain/verify ``` ```json Response theme={null} { "valid": true, "checked": 1842, "first_invalid_seq": null } ``` Tamper with a single field in any record and `valid` becomes `false`, and the chain says exactly where it broke. ## Next steps Wrap an existing OpenAI, Anthropic, LangGraph, Google ADK, CrewAI, or AWS Strands agent with a middleware instead of hand-instrumenting. Publish an autonomy envelope, arm circuit breakers, and drill the kill switch. Map your live records to RBI, DPDP, SEBI, or IRDAI obligations and export a signed bundle. Every client method, the witness session, and the exception model. # MCP Governance Source: https://docs.rotavision.com/sdk/mcp Govern any agent that speaks the Model Context Protocol: Sakshi as an MCP server, or a transparent proxy that witnesses and enforces every tool call. Not every agent is a Python program you can add the SDK to. Many speak the [Model Context Protocol](https://modelcontextprotocol.io) instead: an MCP host (Claude Desktop, Cursor, an agent framework) launches tool servers and calls their tools. The `sakshi-mcp` package governs those agents two ways, neither of which requires touching the agent's code. ```bash theme={null} pip install sakshi-mcp ``` Both surfaces are clients of the Sakshi HTTP API, so every call rides the same tokenization and hash-chain path as any other ingest. Sakshi never sees a raw identifier here either. Adds three governance tools to any MCP host. Opt-in: the agent chooses to call them. Evidence plus advisory enforcement. Sits transparently between the agent and its real MCP server. Witnesses every tool call, and can enforce in the tool path. The agent cannot opt out. Configure the endpoint for both with two environment variables: `SAKSHI_API_URL` (defaults to `http://localhost:8000`) and `SAKSHI_MCP_API_KEY`, a Sakshi ingest key (falls back to `SAKSHI_API_KEY`). ## Sakshi as an MCP server `sakshi-mcp` exposes Sakshi governance as MCP tools. Any MCP-capable host gains them without integrating the SDK. Because the agent chooses when to call them, this is opt-in: evidence plus advisory enforcement. | Tool | When the agent calls it | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `enforce_action` | Before a consequential action (move money, change records, contact a customer). Returns `auto`, `async_review`, `sync_review`, or `block`. A kill switch or safe mode outranks the envelope. Honour `allowed`: if false, stop. | | `witness_decision` | After executing a consequential tool. Records it on the tamper-evident chain and returns the sequence number and record hash (the receipt). | | `check_readiness` | Any time. The live regulator-readiness score per instrument (RBI MRM, DPDP, SEBI, IRDAI), read-only. | Register it in an MCP host by pointing a server entry at the `sakshi-mcp` command: ```json theme={null} { "mcpServers": { "sakshi-governance": { "command": "sakshi-mcp", "env": { "SAKSHI_API_URL": "https://sakshi.your-bank.internal", "SAKSHI_MCP_API_KEY": "sks_..." } } } } ``` The transport is stdio by default (how MCP hosts launch a local server). Set `SAKSHI_MCP_TRANSPORT=streamable-http` to serve it over HTTP instead: ```bash stdio (default) theme={null} SAKSHI_API_URL=... SAKSHI_MCP_API_KEY=sks_... sakshi-mcp ``` ```bash streamable-http theme={null} SAKSHI_MCP_TRANSPORT=streamable-http sakshi-mcp ``` The server enforces before an action and witnesses after it, but only when the agent calls the tools. For agents you do not control, or where opting out is not acceptable, use the proxy below. ## The interception proxy `sakshi-mcp-proxy` sits between an agent and its real MCP server and relays every JSON-RPC message verbatim: ``` agent <--stdio--> sakshi-mcp-proxy <--stdio--> real MCP server ``` Capability negotiation, resources, prompts, sampling, and notifications all pass through untouched, so the proxy is transport-faithful and future-proof. It only taps the messages it governs: `tools/call` (witnessed with a bounded argument and result preview, arguments tokenized at ingest) and `tools/list` (the tool manifest is hashed). Point the agent's MCP host at the proxy command instead of the real server: ```bash theme={null} sakshi-mcp-proxy --agent-id -- npx -y @modelcontextprotocol/server-filesystem /data ``` Everything after `--` is the real downstream server command. ### Transparent witnessing By default the proxy is transparent: it observes and witnesses, it does not block. Every tool call the agent makes lands on the chain, attributed to `--agent-id`, with argument values tokenized so raw identifiers never reach the record. Witnessing is best-effort: a capture failure never breaks or delays a relayed message, and anything the proxy cannot parse is forwarded verbatim. ### Tool-poisoning detection The proxy hashes the tool manifest per tool. A tool whose description or schema changes mid-session is the tool-poisoning vector: a malicious server can rewrite a tool's description after the agent first trusted it, to inject instructions (the 2025 incident class; OWASP MCP Top 10). When that happens, the proxy lands an `mcp_manifest_changed` finding on the chain, attributed to the specific tool. The poisoning becomes evidence, not a silent success. ### Enforcement in the tool path Set `SAKSHI_MCP_ENFORCE=on` to route each `tools/call` through the agent's autonomy envelope before it reaches the tool. An auto-approved call is forwarded and witnessed; anything else is stopped and the agent gets a "parked for review" reply, never the tool. Under enforcement, a call to a tool poisoned mid-session is blocked outright, ahead of exemptions and the envelope. Enable tool-path blocking. Off is transparent observe-and-witness. Block calls to a tool poisoned mid-session. Defaults to on whenever enforcement is on. Tools to skip evaluation for (for example read-only tools). They are still forwarded and witnessed. Fail-static threshold. If Sakshi is unreachable, block calls whose stakes are at or above this value; below it, forward. Unset means always fail-open on an unreachable platform. How a blocked call is returned to the agent: a structured tool result flagged `isError` with block details, or a JSON-RPC error. Argument keys to read the stakes signal from (defaults include `amount`, `amount_inr`, `value`, `stakes`, `sum`, `total`). Argument keys to read the confidence signal from (defaults include `confidence`, `conf`, `score`). ### Remote downstream The downstream MCP server can be remote over streamable-http instead of a local subprocess. The agent still speaks stdio to the proxy: ``` agent <--stdio--> sakshi-mcp-proxy <--streamable-http--> remote MCP server ``` ```bash theme={null} sakshi-mcp-proxy --agent-id --http https://tools.example/mcp --header "Authorization: Bearer ..." ``` `--header` is repeatable. The governance decision is identical on both transports; the streamable-http path relays parsed JSON-RPC messages, so un-parseable agent input is dropped rather than forwarded verbatim. Enforcement fails static, not simply open or closed. On an unreachable platform the outcome depends on `SAKSHI_MCP_FAILCLOSED_STAKES`: high-stakes calls block, lower-stakes calls forward. Set the threshold deliberately for your risk posture. ## Which to use You control the agent or host and want governance tools it can call, or you want a readiness check available to the agent. Advisory: the agent must honour the outcome. You need every tool call witnessed whether the agent cooperates or not, or you want hard enforcement in the tool path and tool-poisoning detection. ## Related A walkthrough of proxying an agent, witnessing its tools, and enabling enforcement. Instrument an in-process MCP client session from Python with the SDK middleware instead. The policy the proxy evaluates each tool call against. The full client reference behind every governance decision. # SDK Overview Source: https://docs.rotavision.com/sdk/overview The Sakshi Python SDK: register agents, witness decisions on the tamper-evident chain, and bound autonomy, in a few lines or with a one-line middleware. The Sakshi SDK is how a Python agent talks to the governance layer. It carries no provider dependencies and never sends a raw identifier: everything you pass is tokenized at ingest, before it is stored or hashed. Sakshi runs single-tenant in your environment, so the `base_url` in these examples is your own deployment. ```bash theme={null} pip install sakshi-sdk ``` The distribution is `sakshi-sdk`; the import is `sakshi`. Install the latest version from PyPI. Python 3.9 or newer. ## Hello, witness Six lines: create the client, register the agent (idempotent by name), and witness one decision. The record is written to the chain when the `with` block exits. ```python theme={null} from sakshi import SakshiClient client = SakshiClient("https://sakshi.your-company.internal", api_key="sks_...") agent_id = client.register("loan-agent", owner_name="Priya Sharma", owner_email="priya@your-company.com") with client.witness(agent_id) as decision: decision.action(outcome="approved", amount=1_500_000) ``` ## Two ways to integrate Both land on the same chain. Pick per agent, or mix them in one process. Open a `witness` session and record the steps as they happen, and route consequential actions through `enforce` before they run. Full control over what evidence a decision carries. This is the reference API on the [Python SDK](/sdk/python) page. Wrap the client you already use (OpenAI-compatible, Anthropic, Gemini, Bedrock, LangGraph) with a single call. Every model call is recorded on the active witness session, with the served model identity auto-filled. See [Integrations](/integrations/overview). ## Capture is fail-open, enforcement is fail-closed The two operations have opposite failure modes on purpose. Decision capture buffers on a background worker. If the platform is unreachable, your agent keeps running and records are dropped with a warning. Governance infrastructure must never take production down. Pass `fail_open=False` for synchronous capture that raises, for batch jobs where losing evidence is worse than stopping. Routing an action through its autonomy envelope fails closed. If Sakshi cannot be reached, `enforce` raises rather than letting an ungoverned action through. An ungoverned action is worse than a delayed one. Enforcement is never auto-retried and capture is buffered, so the two paths behave differently under load and outage. The [Python SDK](/sdk/python) page documents the exact delivery semantics. ## Where to go next Every client method, the witness session, the enforcement outcomes, and the exception model. Add governance to an existing OpenAI, Anthropic, Gemini, Bedrock, or LangGraph agent with one line. Govern any agent that speaks the Model Context Protocol, as an MCP server or a transparent proxy. Point `base_url` at `https://demo.rotavision.com` to run these examples against a live, synthetic-data instance. # Python SDK Source: https://docs.rotavision.com/sdk/python Full reference for the Sakshi Python SDK: SakshiClient, the witness session, enforcement outcomes, and the exception model. The full public surface of the `sakshi` package. Everything here is importable directly: ```python theme={null} from sakshi import ( SakshiClient, WitnessSession, EnforcementDecision, SakshiBlocked, SakshiSyncReviewRequired, SakshiEnforcementUnavailable, ) ``` ## Installation ```bash theme={null} pip install sakshi-sdk ``` The distribution is `sakshi-sdk`; the import is `sakshi`. Install the latest version from PyPI. Python 3.9 or newer. The only runtime dependency is `httpx`. ## Client ```python theme={null} from sakshi import SakshiClient client = SakshiClient( base_url="https://sakshi.your-company.internal", api_key="sks_...", ) ``` Your Sakshi deployment. Sakshi is single-tenant, so this is your own instance (or `https://demo.rotavision.com` for the public demo). An ingest key or governance token, sent as a bearer token. Mint one in the console under Admin, or see [Authentication](/authentication). Decision capture mode. When `True` (default), captured records go onto a background queue and are delivered by a worker thread; if the platform is unreachable they are dropped with a warning so the host agent is never blocked. Set `False` for synchronous capture that raises on delivery failure, for batch jobs where losing evidence is worse than stopping. Note that `enforce` always fails closed regardless of this setting. Supply your own configured `httpx.Client` (custom transport, proxy, mTLS, timeouts). When supplied, Sakshi does not own it and will not close it. When omitted, Sakshi builds one against `base_url` with a 10s timeout. Attempts per captured record before it is dropped (fail-open) or raised (fail-closed), with jittered exponential backoff so a fleet of agents does not hammer a recovering platform in lockstep. Bound on the in-memory capture queue. When it is full, the newest record is dropped with a warning rather than blocking the agent. Seconds between background polls of each agent's kill-switch status. A halt stops the agent within one interval, even between evaluations. The poller is created lazily the first time you call `enforce` for an agent. Capture is fail-open by default. Records can be silently dropped when the platform is unreachable. For evidence you cannot afford to lose, use `fail_open=False`, and call [`flush`](#flush-and-close) before the process exits. ## register Register an agent and get its id back. Always synchronous: an agent must not run unregistered. Idempotent by name, so it is safe to call on every startup. ```python theme={null} agent_id = client.register( "loan-decision-agent", owner_name="Priya Sharma", owner_email="priya@your-company.com", autonomy_tier="L1", description="Approves or refers retail loan applications.", org_unit="retail-credit", ) ``` Human-readable agent name. The idempotency key: a second call with the same name returns the existing agent instead of creating a duplicate. Accountable owner's name. Accountable owner's email. The agent's autonomy tier (for example `L0`, `L1`). Structured description of what the agent can affect. Feeds the response-ladder default when a kill switch or safe mode is armed. Organizational unit the agent belongs to. Scopes review queues and org-unit enforcement on captured records. The agent id. Use it for every subsequent `witness` and `enforce` call. ## witness Open a witness session as a context manager. Record the steps as they happen; the decision record is written to the chain when the block exits. An exception raised inside the block is captured as the outcome and re-raised, because failures are evidence too. ```python theme={null} with client.witness( agent_id, model={"provider": "openai", "name": "gpt-5.2", "version": "2026-05"}, context={"applicant_aadhaar": "XXXX-XXXX-1234"}, client_ref="APP-2026-04471", ) as decision: decision.step("retrieve", source="credit-bureau") decision.tool("risk_model", output={"band": "low", "score": 0.92}) decision.human("review", who="analyst-114", note="spot check") decision.action(outcome="approved", amount=1_500_000, mode="auto") ``` The id returned by [`register`](#register). Decision context. Identifiers you place here are tokenized at ingest, so raw PII never lands on the chain. Model identity for the decision (provider, name, version). Middlewares fill this in automatically from the provider's response (RBI draft MRM para 56); a value you set is not overwritten. Policy inputs known at the start of the decision. Refine mid-decision with `decision.policy(...)`. Your correlation reference (for example an application id). Used for idempotent retries and for search in the console. A context manager. The record is submitted on `__exit__`. ### WitnessSession methods Call these on the object bound in the `with` block to build the decision chain. Append a generic step to the decision chain. `name` is a short verb (`retrieve`, `assess`); `**data` is arbitrary evidence for that step. Record a tool invocation: the tool `name`, its `output`, and any extra `**data`. Recorded as a `tool` step. Record a human touchpoint: the `kind` of involvement, `who` was involved, and any extra `**data`. Record that the customer was told they are interacting with AI (RBI draft MRM 59(ii)). Lands an `ai_disclosure` touchpoint. The touchpoint is the evidence: coverage is measurable, assertion is not. Record a document-extraction act. `extracted` and `authorized` are PII category names (`aadhaar`, `pan`, `name`, `address`), never values. When `authorized` is given, over-extraction (categories pulled beyond the authorized purpose) is computed for you. `storage` is the disposition of the raw value the agent produced, for example `{"vault_ref": "V-8821", "residency": "IN", "retained_raw": False}`. Sakshi evidences where it went; it never holds it. Record a read of a stored, reference-keyed value. `reference_key` is the vault token, never the value. When `authorized_purposes` is given, an off-purpose read is flagged (DPDP purpose limitation on access). Sakshi witnesses that the read happened and whether it was on-purpose; it never detokenizes the value. Merge facts into policy state that are only known mid-decision, such as which routing outcome the policy engine actually chose. Set the decision's final action (outcome, amount, mode). If the block raises and no action was set, the outcome is recorded as `error` with the exception. Fill or refine the decision's model identity. Middlewares call this with the provider-reported model and version; a key already set is not overwritten. ## enforce Route an intended action through the agent's autonomy envelope before it runs. Enforcement fails closed: an unreachable platform raises rather than letting an ungoverned action through. The kill switch is consulted from the local poller cache first, then re-checked server-side inside the evaluation. ```python theme={null} from sakshi import ( SakshiBlocked, SakshiSyncReviewRequired, SakshiEnforcementUnavailable, ) try: decision = client.enforce( agent_id, "approve_loan", stakes=1_500_000, confidence=0.92, ) # decision.allowed is True here: proceed with the action except SakshiSyncReviewRequired as e: # parked for a human before it can run; e.decision has the details ... except SakshiBlocked as e: # the envelope, a block rule, or the kill switch stopped it ... except SakshiEnforcementUnavailable: # platform unreachable; enforcement fails closed, so do not act ... ``` Short verb for the intended action, for example `approve_loan` or `transfer_funds`. Magnitude of the action (for example an amount in INR). Signals are non-negative and unbounded; they are not confined to 0..1. The agent's confidence, 0..1. How novel the situation is, 0..1. Your correlation reference for the evaluation. When `True` (default), a `block` outcome raises `SakshiBlocked` and a `sync_review` outcome raises `SakshiSyncReviewRequired`. Set `False` to get the `EnforcementDecision` back for every outcome and branch on it yourself. ### Outcomes `enforce` returns an `EnforcementDecision` (a frozen dataclass): One of `auto`, `async_review`, `sync_review`, `block`. Whether the agent may act now. `True` for `auto` and `async_review` (the latter allows the action with post-hoc review); `False` for `sync_review` and `block`. The envelope rule that decided the outcome, if any. The version of the autonomy envelope that was evaluated. Server-side id for this evaluation, for correlation and review lookup. With `raise_on_hold=False`, branch on the decision directly: ```python theme={null} decision = client.enforce(agent_id, "approve_loan", stakes=1_500_000, raise_on_hold=False) if decision.allowed: ... # auto or async_review: act now elif decision.outcome == "sync_review": ... # park for a human before acting else: # block ... # do not act ``` ### Exceptions The platform refused the action: a kill switch is active, or a block-outcome rule matched. This must stop the host agent. Carries an optional `.decision`. The action needs a human decision before it executes. The review queue item already exists server-side; the host agent must park the action. Carries `.decision`. The platform could not be reached for a decision. Unlike capture (fail-open), enforcement fails closed, so this is raised rather than defaulting to allow. `enforce` is never retried automatically. A single evaluation either returns a decision or fails closed. Contrast with capture, which is buffered and retried. ## request\_human\_handoff The customer asked for a human (RBI draft MRM 59(iii)). Synchronous and raising: a lost handoff request is a compliance failure, so this never buffers or drops. ```python theme={null} result = client.request_human_handoff( agent_id, client_ref="APP-2026-04471", reason="customer requested a human agent", ) ``` The server response for the parked handoff (a `handoff` review-queue item). ## flush and close Block until every queued capture record has been delivered or dropped. Call it before a batch job exits, or before a short-lived process ends, to be sure buffered evidence has left the queue. Stop the kill-switch pollers, flush the capture queue, join the worker, and close the HTTP client if Sakshi owns it. When `fail_open=True`, `close` is registered with `atexit`, so it runs on normal interpreter shutdown; call it explicitly for deterministic cleanup. ```python theme={null} client.flush() # drain buffered records client.close() # stop pollers and release resources ``` ## Middlewares If you would rather not hand-instrument every call, the SDK ships duck-typed middlewares that wrap the client you already use and record each model call on the active witness session, with the served model identity auto-filled. They carry no provider dependencies. ```python theme={null} from sakshi.middleware import watch_openai_compatible llm = watch_openai_compatible(openai_client) # also covers Ollama, vLLM, local with client.witness(agent_id) as decision: llm.chat.completions.create(model="gpt-5.2", messages=[...]) # recorded ``` See [Integrations](/integrations/overview) for every middleware (`watch_openai_compatible`, `watch_anthropic`, `watch_gemini`, `watch_bedrock`, `watch_langgraph`, `witness_node`, `watch_mcp`) and worked examples. To govern agents over the Model Context Protocol, see [MCP governance](/sdk/mcp). ## Related Register, witness, and verify the chain end to end. How `enforce` routes actions to auto, review, or block. One-line capture for OpenAI, Anthropic, Gemini, Bedrock, and LangGraph. The endpoints the SDK calls, generated from the live spec. # Self-hosting Source: https://docs.rotavision.com/self-hosting Run Sakshi single-tenant in your own environment: one host, one install, evidence that never leaves your VPC, and a chain anyone can recompute. What a bank's security team wants to see. Sakshi is single-tenant by design. It installs on one host in your VPC or on-prem, holds your evidence in your environment, and never phones home. PII is tokenized before anything is stored or hashed, and every record is independently verifiable, even by an auditor who does not trust Rotavision. This page is the deployment and security posture for the team signing off on that. One host is one install. Container and volume names are pinned to a single tenant on purpose, so a deployment's identity and its secrets are unambiguous. To evaluate without installing anything, [request a sandbox](https://rotavision.com/sandbox) instead. ## Prerequisites * A VM: 4 vCPU / 8 GB RAM / 40 GB disk (Ubuntu 22.04+, or any Docker host). Add a little headroom for the optional on-host PII model. * Docker Engine with the compose plugin, plus `openssl` and `python3`. * For a public deployment: a DNS A record pointing at the VM and ports 80/443 reachable, so the built-in reverse proxy (Caddy) can provision TLS automatically. ## Install Sakshi ships as a single-command installer. It runs preflight checks, generates its own secrets, wires the identity provider, writes the compose and TLS config, builds, starts, and waits for health. ```bash Your domain (TLS) theme={null} ./deploy/install.sh --domain sakshi.your-company.internal ``` ```bash Local evaluation (http) theme={null} ./deploy/install.sh --local ``` The installer generates the deployment's secrets into a `.env` (mode `600`) and **refuses to overwrite an existing one** without an explicit `--force`. A live deployment's secrets are its identity, so rotating them by accident is made impossible. ## Verify in ten minutes The Overview page loads with an empty registry on a fresh install. Under **Admin → API keys** (an admin action). The installer also writes a break-glass bootstrap key into `.env`. ```python theme={null} from sakshi import SakshiClient client = SakshiClient("https://sakshi.your-company.internal", api_key="sks_...") client.register("install-smoke-agent", owner_name="You", owner_email="you@your-company.com") ``` `GET /api/v1/chain/verify` returns `valid: true` once decisions exist. That is the acceptance test: the evidence checks out against itself. After any install or routing change, run the deployment smoke against the real domain. It exercises the proxied stack (every routing contract, then a full SSO login in a browser) that an in-process test suite cannot see. ## Security posture Point the platform at your own OIDC identity provider by setting the issuer and client credentials. Roles map to `admin`, `governance-officer`, `reviewer`, and `auditor`; a teams claim scopes review queues. The shipped demo realm is demo-grade and is meant to be replaced before production. Identifiers are tokenized at ingest, before anything is stored or hashed, so raw PII never lands on the chain. An optional on-host NER model (open weights you mount yourself) strengthens detection without any egress. Local-model middlewares (OpenAI-compatible / Ollama) keep sensitive routes fully in-VPC, with the model weights digest witnessed on each call. Nothing leaves the host. A Prometheus endpoint at `/metrics` exposes evidence-derived gauges (agents by status, decision and chain head, queue depths, active halts, open incidents). It is off unless a dedicated scrape token is set, and requires exactly that token, separate from ingest keys. Every count derives from evidence tables; nothing self-reports. Public installs enable a per-credential token-bucket limiter that answers `429` with `Retry-After`. Callers are keyed by a hashed credential, so one noisy agent cannot starve the rest, and health endpoints are exempt for monitors. The deployment is its secrets, its identity config, and the Postgres data volume. Snapshot the VM or `pg_dump` on a schedule. The hash chain makes tampering evident; backups make the data durable. Upgrades pull the new tree without touching secrets or config, then rebuild and restart; schema migrations run automatically on start. ## Interactive API reference on your instance The deployment serves its own Swagger UI at `/api/docs` (and a reading view at `/api/redoc`) plus a public `/status` endpoint (liveness and version only; chain state always requires a principal). The reference exposes endpoint shapes, never data, and can be switched off entirely for a fully egress-blocked environment. The thirty-minute path: govern your first agent, verify the chain, and export a signed evidence pack.