Skip to main content
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.
Add action governance, retrieval governance, and a full audit trail to a LangChain agent by passing your AgentExecutor to visiq(). The same call detects and wraps a LangGraph CompiledGraph. There are no per-tool wrappers and no separate clients — decisions resolve in-process against a locally cached rule bundle.

Install

Pin LangChain to ^0.3. LangChain 1.x removed the langchain/agents entry point (AgentExecutor / createOpenAIToolsAgent) used below in favor of createAgent and the graph API — an unpinned install resolves to 1.x and the import in the next step throws. VisIQ governs all three per-tool: the 0.3 AgentExecutor path, a LangGraph compiled graph, and 1.x’s createAgent — the last is a ReactAgent wrapper whose graph sits at .graph, and the SDK reads through it, so the tools inside are governed individually rather than the agent being treated as one opaque tool. This quickstart uses 0.3. Pin zod@^3 too: zod 4 serializes tool parameters in a shape the evaluator rejects with 400 invalid_function_parameters.

Set environment variables

.env

Wrap your agent

Enforcement wraps each tool’s invoke/call/_call dispatch methods directly — LangChain callbacks cannot block a tool call — so it holds for any agent constructor, and executor.stream() is governed identically to invoke().
Per-document RAG governance. createRetrieverTool captures its retriever in a closure and returns one joined string, so the harness falls back to reduced-fidelity governance for that tool: pattern and value-shape masking still apply to the string, but retrieval rules keyed on per-document metadata (classification, source, …) cannot fire — the SDK prints a one-time console warning when this happens. For full per-document governance, expose the retriever on the tool as a reachable .retriever property, or use a tool that returns a Document[]: any retriever the harness can reach (including a nested .retriever) is instrumented so each document is evaluated with its own metadata.

Python

LangChain’s Python SDK is governed in one call too. Pass the agent to govern() from the visiq wheel, compiled from the same governance core as the TypeScript harness, and every tool the agent can call is governed, with no per-tool wrappers.
The visiq wheel reads its configuration from the process environment and does not auto-load a project .env (that is the TypeScript harness). Export VISIQ_API_KEY (and optionally VISIQ_AGENT_ID, plus VISIQ_ENDPOINT for onprem), or load them yourself, before calling govern().
govern() returns the same agent, so it also works as an expression, and it is idempotent. LangChain 1.x create_agent returns a LangGraph compiled graph, so the same call governs a LangGraph agent or a hand-built graph; see the LangGraph quickstart. VisIQ installs itself at LangGraph’s ToolNode tool-call wrapper, the same point LangChain plugs AgentMiddleware.wrap_tool_call into, and it runs outermost: it sees each call exactly as the model chose it, before your own middleware. Every tool call is decided before the tool runs. A deny means your function is never called: the block message is returned as the tool’s ToolMessage, so the model reads why and the run continues. A mask hands your function, and any middleware inside it, only the redacted arguments. Tools named in retrieval_tools also have their result governed before it reaches the model: restricted documents are dropped and sensitive fields redacted, per document, whether the tool returns dicts, a JSON list, or LangChain Document objects (their metadata is kept for the decision). A result the retrieval facet cannot split into documents (a dict envelope, a pydantic model) is withheld rather than passed through, and a content_and_artifact tool loses its artifact, which holds the raw documents.
On ainvoke / astream the governance decision runs off your event loop, so a human-approval hold cannot freeze the rest of your asyncio app, while the tool itself still runs in your own task. 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.
What the Python adapter does not cover. Tools must run in a ToolNode node, as they do in every create_agent agent. A hand-built graph that executes tools in its own node function is not seen, and govern() raises rather than returning a graph it cannot govern. The legacy AgentExecutor (now in langchain-classic) is not a graph and is not covered: govern it per call with Governor.gate_tool from the Python SDK Reference.

Run this on AWS Bedrock

LangChain reaches Bedrock through AWS’s own integration package, so the governance wiring does not change. pip install langchain-aws, then swap the model in the Python agent above:
Credentials come from the standard AWS chain (AWS_PROFILE, instance role, AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY). Governance is untouched: the gate still decides before the tool body runs, retrieval results are still filtered, the same rules match, the same rows land in the audit trail. LangChain on Bedrock is governed at the same ToolNode tool-call wrapper this adapter installs at. 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-OPEN stderr report (plus a structured failOpen flag) so a VisIQ outage never disrupts your agent (owner decision, 2026-07-15). Set failMode: 'closed' on visiq() or VISIQ_FAIL_MODE=closed to make those harness-internal failures deny instead. This page also documents the Python SDK, which honours the same posture. The visiq wheel fails OPEN on a harness-internal failure with the same loud [VisIQ] FAIL-OPEN report, and honours VISIQ_FAIL_MODE=closed to 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_required decision 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 mask decision 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.