Skip to main content
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:
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:
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:
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:
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. 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:
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

Bound autonomy

How envelopes route an action to auto, review, or block, and how the kill switch halts an agent.

Python SDK

The client, the witness session, and the full exception reference.

Authentication

Ingest keys versus OIDC JWTs, and why a 403 usually means the wrong token type.

API reference

Every endpoint and its response schema, generated from the OpenAPI spec.