Prerequisites. A VisIQ account (sign in) with a
harness key from Settings → Harness Keys, Node 20+ (or Python 3.9+
for the Python path), and an
OPENAI_API_KEY (the sample below calls an OpenAI model — any provider works).
Full setup and fixes: Before you start ·
Troubleshooting.Agent to visiq(). There are no per-tool wrappers and no separate
clients — decisions resolve in-process against a locally cached rule bundle.
Install
Set environment variables
.env
Wrap your agent
invoke in place, so
every call the run() loop dispatches is gated — regardless of how you start
the run.
Retrieval governance contract. Per-document filtering applies to tools
whose
execute() returns document-shaped results — array items with a string
pageContent, text, or content field, plus optional metadata that
retrieval rules match on (classification, data categories, …). Results in any
other shape still pass through the action gate but are not filtered
per-document.Python
The OpenAI Agents SDK has a Python edition, and so does VisIQ: thevisiq wheel, published on PyPI and compiled
from the same governance core as the TypeScript harness. Pass your Agent to
visiq.govern() and every function tool it can call is governed, with no
per-tool wrappers. Decisions are made locally, in-process against a cached
rule bundle.
visiq wheel reads its configuration from the process environment and
does not auto-load a project .env (that is the TypeScript harness). Export the
variables above (or load them yourself) before calling govern().
govern() returns the same agent, so it also works as an expression. It is
idempotent: governing an agent twice installs governance once, and a tool is
never gated twice.
VisIQ governs at the SDK’s own execution chokepoint: every function tool reaches
the run loop as a FunctionTool, and FunctionTool.on_invoke_tool is the one
callable the runner awaits to execute it. govern() replaces that callable on
each tool, in place.
- Every tool call is decided before the tool runs. A deny (or a human approval that is not granted) means your function is never called: the block message is returned as the tool’s result, so the model reads why and the run continues. A mask hands your function only the redacted arguments, nested fields included.
- Retrieval results are governed too. Tools named in
retrieval_toolsalso have their result governed before it reaches the model: restricted documents are dropped and sensitive fields redacted, whether the tool returns a list of documents or a JSON string of one. A result the retrieval facet cannot split into documents (a dict envelope, a pydantic model) is withheld rather than passed through. - Tools added later are covered. The runner assembles the tool list at the
start of every turn, and
govern()hooks that step, so a tool appended toagent.toolsaftergovern()and the tools built frommcp_serverson each turn are governed before the model can call them. - The whole agent graph is covered. Agents in
handoffs(a plainAgentor one wrapped inhandoff(...)) and agents exposed throughagent.as_tool(...)are governed too, under the same agent id. To report a sub-agent as a separate agent, callgovern(sub_agent, agent_id="...")on it first; an agent that is already governed keeps its own governance. - Audit per run. The agent and its tool surface are registered with VisIQ on
the first turn of the first run, and each run’s decisions are delivered when
the agent produces its final output. Your own
hooks=keep running.
Governance decisions are synchronous, but they run off your event loop, so
a human-approval hold cannot freeze the rest of your asyncio app. The tool
itself still runs in the runner’s own task, so cancelling a run (or the SDK’s
timeout_seconds) still reaches it.What the Python path does NOT cover. Only
FunctionTool calls are governed
(@function_tool tools, MCP-server tools and agents used as tools). OpenAI’s
hosted tools (WebSearchTool, FileSearchTool, CodeInterpreterTool,
HostedMCPTool, ImageGenerationTool) run on OpenAI’s servers, so there is no
local call to gate, and the SDK’s other local executors (ComputerTool,
LocalShellTool, ShellTool, ApplyPatchTool) do not go through
on_invoke_tool. agent.clone(...) keeps the governed tools but not the
per-turn hook, so call govern() on a clone you add tools to. Replacing
agent.hooks after govern(), or a run that raises or hits max_turns, skips
the per-run delivery; events then go out in batches and at process exit. Need
finer control than the whole agent? The per-call Governor.gate_tool
primitive is documented in the Python SDK Reference.Run this on AWS Bedrock
Despite the name, the OpenAI Agents SDK is model-agnostic: its LiteLLM extension reaches Bedrock. Install the extra (pip install "openai-agents[litellm]") and
swap the model= you hand to Agent. The govern() call is unchanged:
AWS_PROFILE, instance role,
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY). Governance is untouched: the gate
fires at the same point, the same rules match, the same rows land in the audit
trail. There is no framework-specific caveat.
Deploying to Bedrock AgentCore Runtime needs nothing extra either — it hosts
your process, so install the harness in your image and set VISIQ_API_KEY in the
runtime environment. If instead you use a managed harness, AWS owns the agent
loop and this in-process gate does not apply; see
AWS Bedrock
for the two chokepoints that still work there.
What happens at runtime
Wrapping is safe to try immediately — new agents start in monitor mode (observe-only) until you flip them to enforce on the Control AI → Agents page.-
Decisions are local. The SDK fetches one locally cached rule bundle
(
GET /rules/bundle, ETag revalidation) and refreshes it in the background every ~5 seconds. Tool calls evaluate in-process; the only decision-path network call is waiting on a human approval. -
Fail-open by default, loudly — strict deny is opt-in. Real policy outcomes
always enforce regardless of failMode: an explicit rule deny, the operator
kill-switch, and an in-core mask/redact that cannot be applied (it downgrades to
deny) all block. A brand-new agent whose mode has never been confirmed
cold-starts in
monitor(observe, never block). But a harness-internal failure — an unreachable backend, the governance core unavailable, a refused wire dialect — by default proceeds ungoverned with a loud[VisIQ] FAIL-OPENstderr report (plus a structuredfailOpenflag) so a VisIQ outage never disrupts your agent (owner decision, 2026-07-15). SetfailMode: 'closed'onvisiq()orVISIQ_FAIL_MODE=closedto make those harness-internal failures deny instead. This page also documents the Python SDK, which honours the same posture. Thevisiqwheel fails OPEN on a harness-internal failure with the same loud[VisIQ] FAIL-OPENreport, and honoursVISIQ_FAIL_MODE=closedto block instead. A policy deny, a required mask that cannot be applied, and a HITL approval that cannot be confirmed still block in Python exactly as in TypeScript. -
Denials are returned, not thrown. A blocked call hands the model
[VisIQ decision=deny code=<rule-code>] This tool call was NOT executed: it was denied by policy (<description>). VisIQ is a security harness installed by your developer. Report this reason to the user verbatim; do not invent a different one.as the tool’s output, so the agent reads it and adjusts course. -
Approvals pause the call. An
approval_requireddecision holds the tool while a human decides via Slack or Email (Microsoft Teams delivery is built server-side; its connector card is coming soon) — the SDK polls for up to 120 seconds (VISIQ_HITL_TIMEOUT_MS), then fails closed. -
Mask proceeds, redacted. A
maskdecision runs the tool with the named arguments redacted; retrieval redaction masks document fields before the model sees them. - Covered from the first call. Every workspace ships a curated catalog of 36 default rules. Uncovered actions permit by default — no surprise breakage — and the per-operation-type default can be tightened in settings.
Verify it’s working
Run the agent once, then open the dashboard:- Control AI → Agents: your agent appears automatically (monitor mode) with a live last-seen heartbeat.
- Control AI → Agent Events: a decision row for every governed tool call, with the matched rule and outcome.
- Control AI → Escalations: pending approvals. Route them to Slack or Email under Integration → Connectors (Human-in-the-loop) — Microsoft Teams delivery is built and its connector card opens shortly.
Next steps
Full Quickstart
All supported frameworks and what happens behind the scenes.
SDK Reference
Complete
visiq() API, options, framework detection, and error behavior.