> ## Documentation Index
> Fetch the complete documentation index at: https://docs.visiqlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK Reference

> API reference for the visiq Python package — the Governor harness, the low-level local decision gates (gate_action / gate_retrieval / decide), config resolution, and the ToolBlocked exception.

The [`visiq`](https://pypi.org/project/visiq/) package is the Python peer of
`@visiq/harness`. One compiled Rust core makes the same local, in-process policy
decisions, and on top of it the package ships the same end-to-end harness the
TypeScript SDK does — bundle fetch, agent registration, human-in-the-loop, and
audit telemetry.

```bash theme={null}
pip install visiq
```

Requires **Python 3.9+** (the wheel is `abi3`). Set the same environment as the
TypeScript SDK: `VISIQ_API_KEY` (`vq_prod_…` / `vq_test_…`), `VISIQ_ENDPOINT`
(`https://api.visiqlabs.com`), and optionally `VISIQ_AGENT_ID`.

<Note>
  **Two layers, one wheel.** Use `Governor` for a governed agent end to end (it
  fetches your bundle and enforces every outcome). Use the low-level
  `gate_action` / `gate_retrieval` / `decide` functions when you already hold a
  rule bundle and just want a local decision with no network.
</Note>

<Note>
  **Pre-1.0 (0.x).** Breaking changes may ship in any minor release until v1.0.
  Pin an exact version. See [SDK versioning](/versioning) for the posture.
</Note>

## Public API

Everything exported from the package top level:

| Symbol           | Kind      | Purpose                                                             |
| ---------------- | --------- | ------------------------------------------------------------------- |
| `Governor`       | class     | End-to-end harness — governs tool calls and retrieval for one agent |
| `ToolBlocked`    | exception | Raised by `Governor.gate_tool` on a deny or unapproved HITL         |
| `gate_action`    | function  | Low-level: decide one action/tool call against a bundle             |
| `gate_retrieval` | function  | Low-level: decide one retrieval against a bundle                    |
| `decide`         | function  | Low-level: evaluate a raw event against a bundle                    |
| `resolve_config` | function  | Build a `HarnessConfig` from the environment                        |
| `HarnessConfig`  | class     | Resolved transport configuration                                    |

***

## `Governor`

One `Governor` per agent process. It warms the rule bundle, decides every tool
call and retrieval locally against the compiled core, blocks on human approval
when a rule requires it, applies retrieval redaction, and streams an audit event
per decision.

```python theme={null}
from visiq import Governor, ToolBlocked

gov = Governor(agent_id="support-bot").start(tools=[
    {"name": "issue_refund", "description": "Issue a refund to a customer"},
    {"name": "search_knowledge", "description": "Search the company knowledge base"},
])
```

### `Governor(agent_id=None, config=None)`

Construct a governor. `agent_id` follows the same precedence as the TypeScript
SDK: explicit argument → `VISIQ_AGENT_ID` → `"agent"`. Pass a `HarnessConfig`
for `config` to bypass environment resolution.

### `start(tools=None) -> Governor`

Warm the bundle and run the one-time registration handshakes (environment +
tool surface). Safe to call once at startup; returns `self` so you can chain it
onto the constructor. `tools` is a list of `{"name", "description"}` dicts
describing your tool surface.

### `gate_tool(tool_name, args, call) -> Any`

Govern one tool call: decide locally, enforce the outcome, and only then invoke
`call(effective_args)`.

* `call` **must accept exactly one positional argument** — the effective
  argument mapping. On a `mask` verdict the gate hands `call` the **redacted**
  arguments, so the tool never sees the originals.
* A `permit` runs the tool with the original args.
* An `approval_required` registers the decision and **blocks** on the HITL poll
  until a human resolves it (or `hitl_timeout_ms` elapses → blocked).
* A `deny`, an unapproved/expired HITL, or an unredactable `mask` raises
  `ToolBlocked` — **the tool body never runs** (G001).

```python theme={null}
def issue_refund(customer_id: str, amount: float) -> str:
    return gov.gate_tool(
        "issue_refund",
        {"customer_id": customer_id, "amount": amount},
        lambda eff: f"Refunded ${eff['amount']} to {eff['customer_id']}",
    )
```

### `gate_documents(docs, query=None) -> list`

Filter retrieved documents through the retrieval facet: `deny` drops the
document, `redact` masks its fields, `allow`/`escalate` keep it. Each document
is `{page_content | text | content, metadata}`-shaped. Fails closed — a bundle
that has not loaded returns `[]`, and any per-document evaluation error drops
that one document rather than crashing the batch.

```python theme={null}
docs = gov.gate_documents(retrieved, query="What was Q3 revenue?")
```

### `gate_text(text, metadata=None) -> str`

Filter a single **string** tool result through the retrieval facet. Returns the
text unchanged on `allow`, a redacted copy on `redact`, or a short blocked
placeholder on `deny` / when governance is unavailable.

### `flush() -> None`

Flush buffered audit telemetry to the backend. Registered with `atexit`, so it
also runs at process exit — call it explicitly for long-lived processes.

### `agent_id` (property)

The resolved agent id this governor reports under.

<Warning>
  **Offline is fail-closed.** With no `VISIQ_ENDPOINT` + `VISIQ_API_KEY` (or a
  backend that never returns a bundle), `Governor` has no rules to evaluate, so
  `gate_tool` raises `ToolBlocked` and `gate_documents` returns `[]`. This differs
  from the TypeScript harness's monitor-until-confirmed cold start — set the
  endpoint and key so a bundle can load.
</Warning>

***

## Low-level decision gates

Pure functions over a rule bundle you already hold. No network, no I/O — the
decision path is entirely local and fails closed on malformed input (G001).
Every gate returns the same `UnifiedDecision` dict.

### `gate_action(bundle, *, tool_name, args=None, agent_id="agent", target_resource=None, normalized=None) -> dict`

Decide one tool/action call.

```python theme={null}
import visiq

decision = visiq.gate_action(
    bundle,
    tool_name="issue_refund",
    args={"customer_id": "cust_42", "amount": 500},
    agent_id="support-bot",
)
if decision["action"]["decision"] == "deny":
    ...  # block; on "mask", apply decision["action"]["argRedactionRules"] first
```

### `gate_retrieval(bundle, *, resource_type="document", resource_metadata=None, agent_id="agent", query=None) -> dict`

Decide one retrieval. Check `decision["retrieval"]["action"]` — drop on
`deny`/`escalate`, redact on `redact` via `retrieval.redactionRules`, keep on
`allow` — before the content reaches the model.

### `decide(event, bundle) -> dict`

Evaluate a raw event dict directly. `gate_action` and `gate_retrieval` are thin
wrappers over this.

### The decision dict

```json theme={null}
{
  "decision": "deny",
  "allowed": true,
  "reason": "No matching rule and no no-coverage config — fail-closed (G001)",
  "ruleId": null,
  "ruleCode": null,
  "enforced": false,
  "agentMode": "monitor",
  "action": { "decision": "deny", "allowed": false },
  "retrieval": { "action": null }
}
```

* `action.decision` ∈ `permit` · `deny` · `approval_required` · `mask`.
* `retrieval.action` ∈ `allow` · `deny` · `redact` · `escalate`.
* `enforced` is `false` when the agent is in monitor/off mode — the would-be
  verdict is still reported on `action.decision`, but top-level `allowed` stays
  `true` because nothing is actually blocked. In an `enforce` bundle, `allowed`
  reflects the real outcome.

<Note>
  **Where does `bundle` come from?** The full harness (`Governor`) fetches it for
  you from `GET /rules/bundle`. If you drive the low-level gates yourself, fetch
  that bundle over the [rules API](/rules/action/api-reference) and pass the JSON
  object straight in.
</Note>

***

## `resolve_config(agent_id=None) -> HarnessConfig`

Resolve transport configuration from the environment, matching the TypeScript
harness precedence:

* **endpoint** — `VISIQ_ENDPOINT`, then the `VISIQ_BASE_URL` alias.
* **api\_key** — `VISIQ_API_KEY`, then the `VISIQ_ALLOW_API_KEY` alias.
* **agent\_id** — explicit argument → `VISIQ_AGENT_ID` → `"agent"`.

`HarnessConfig(endpoint, api_key, agent_id, timeout_ms=10000, hitl_timeout_ms=120000)`
carries those values; `endpoint`/`api_key` may be `None`, in which case every
network call no-ops and the decision path is local-only (fail-closed on
enforce).

***

## `ToolBlocked`

```python theme={null}
class ToolBlocked(Exception): ...
```

Raised by `Governor.gate_tool` when a call is denied or a HITL approval is not
granted. The tool body is never executed (G001). Let it surface to your
framework as the tool's failure, or catch it to return a model-readable message:

```python theme={null}
try:
    result = issue_refund("cust_42", 500)
except ToolBlocked as blocked:
    result = f"[blocked] {blocked}"
```

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/quickstart#python">
    The Python section of the quickstart — governed agent in a few lines.
  </Card>

  <Card title="TypeScript Reference" icon="book" href="/reference">
    The `visiq()` function, options, and framework detection.
  </Card>
</CardGroup>
