Prerequisites. A VisIQ account (sign in) with a
harness key from Settings → Harness Keys, Python 3.9+, and a model
provider key for LangGraph (the sample calls a hosted model — any provider
works). Full setup and fixes: Before you start ·
Troubleshooting.
visiq.govern(). VisIQ installs itself at the graph’s
ToolNode tool-call wrapper (wrap_tool_call / awrap_tool_call), LangGraph’s
own interception point for executing a model’s tool call. There are no per-tool
wrappers: every tool in every ToolNode of the graph is governed, including a
ToolNode inside a subgraph. The visiq
wheel, compiled from the same governance core as the TypeScript harness, makes
every decision locally, in-process against a cached rule bundle.
Install
Set environment variables
.env
The
visiq wheel reads its configuration from the process environment and
does not auto-load a project .env. Export the variables (or load them
yourself) before calling govern().
Govern your agent
govern() returns the same graph, so it also works as an expression. It is
idempotent: governing a graph twice installs governance once. The same call
governs a LangChain 1.x create_agent(...) agent, which is a LangGraph graph
underneath, and a graph you build yourself:
ToolMessage, so the model reads why and the graph keeps running. A mask
hands your function only the redacted arguments. Arguments LangGraph injects
(InjectedState, ToolRuntime) are added after the decision and are never sent
to policy. Tools named in retrieval_tools also have their result governed
before it reaches the model, with restricted documents dropped and sensitive
fields redacted, per document, whether the tool returns dicts, a JSON list, or
LangChain Document objects. A result the retrieval facet cannot split into
documents (a dict envelope, a pydantic model) is withheld rather than passed
through.
On
ainvoke / astream the governance decision runs off your event loop,
so a human-approval hold cannot freeze the rest of your asyncio app. The tool
itself still runs in your own task, so cancelling a run also cancels the tool
it was running. The agent and its tool surface are registered with VisIQ on the
first run, and each run’s decisions are delivered to the dashboard when that
run ends.Run this on AWS Bedrock
LangGraph drives LangChain model objects, so Bedrock needs one extra package,pip install langchain-aws, and one changed argument. 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. LangGraph on Bedrock was verified end to end against real Bedrock
inference, with no LangGraph-specific caveat.
15 of 15 Bedrock scenario checks passed.
Deploying to Bedrock AgentCore Runtime needs nothing extra either — it hosts
your process, so install the visiq wheel 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 governance does not apply; see
AWS Bedrock
for the two chokepoints that still work there.
What happens at runtime
New agents start in monitor mode (observe-only) until you flip them to enforce on the Control Agents → Agents page.- Decisions are local. The SDK fetches one cached rule bundle
(
GET /rules/bundle, ETag revalidation) and refreshes it in the background. 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: 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 harness-internal failure does NOT block: if the backend is unreachable or
the governance core is unavailable, the harness proceeds UNGOVERNED with a
loud
[VisIQ] FAIL-OPEN (no-bundle)report on stderr, so a VisIQ outage never disrupts your agent (owner decision, 2026-07-15). SetVISIQ_FAIL_MODE=closedto block those harness-internal failures instead. - Deny blocks the call. The tool is never executed, and the model receives the block message as the tool result.
- 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.
What it does not cover. Tools must run in a
ToolNode node, as they do in
create_react_agent, create_agent and the graph above. A graph that executes
tools in its own node function (or calls ToolNode from inside one) is not seen,
and govern() raises rather than returning a graph it cannot govern. A
hand-built ToolNode(wrap_tool_call=...) given only a synchronous wrapper runs
that wrapper, and the governance decision with it, on the event loop under
ainvoke, because that is LangGraph’s own fallback. Need finer control than the
whole graph? The per-call Governor.gate_tool primitive is documented in the
Python SDK Reference.Verify it’s working
Run the agent once, then open the dashboard:- Control Agents → Agents — your agent appears automatically (monitor mode) with a live last-seen heartbeat.
- Control Agents → Runtime Enforcement — a decision row for every governed tool call, with the matched rule and outcome.
Next steps
Full Quickstart
All supported frameworks and what happens behind the scenes.
Python SDK Reference
The complete
Governor API — gate_tool, gate_documents, fail modes.