Prerequisites. A VisIQ account (sign in) with a
harness key from Settings → Harness Keys, Python 3.9+, and a model
provider key for Strands Agents (the sample calls a hosted model — any provider
works). Full setup and fixes: Before you start ·
Troubleshooting.
Strands is a framework, not a Bedrock feature — this page is not AWS-specific.
Strands Agents is an independent Apache-2.0 agent framework
that AWS wrote and open-sourced. It is model- and cloud-agnostic and runs anywhere Python
does — your laptop, Lambda, ECS, Kubernetes, on-prem — and it is a peer of LangGraph,
CrewAI and Pydantic AI. Everything below governs a Strands agent running against
any model provider; a Strands agent talking to Anthropic or a local Ollama is governed
by exactly the same hook.Strands separately happens to be the engine AWS runs inside Bedrock AgentCore’s managed
harness. That is a coincidence of AWS choosing its own framework, not a dependency —
and it does not make this page’s governance available there, because the managed harness
runs Strands in AWS’s own process where no hook of yours can be registered. See
the AgentCore governance map for which Bedrock posture is
which.
visiq.govern().
VisIQ registers itself as a Strands hook provider, the framework’s own
extension point, and governs every tool call at the BeforeToolCallEvent and
AfterToolCallEvent events the Strands tool executor fires around each call.
There are no per-tool wrappers: every tool the agent resolves is governed,
including tools added to its registry after govern() and MCP tools. 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
[openai] extra is only for the sample’s model. Any Strands model provider
works; see Run this on AWS Bedrock for the default one.
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
@tool decorator (or
wrap an existing function with tool(fn)): a plain function is rejected as an
“unrecognized tool specification” and the agent ends up with no tools at all.
govern() returns the same agent, so it also works as an expression. It is
idempotent: governing an agent twice installs governance once.
Every tool call is decided before the tool runs. A deny means your
function is never called: VisIQ sets the event’s cancel_tool, so Strands hands
the block message to the model as the tool’s result and the run keeps going,
instead of an exception aborting the whole invocation. A mask hands your
function only the redacted arguments, nested ones included. Tools named in
retrieval_tools also have their result governed before it reaches the
model, with restricted documents dropped and sensitive fields redacted. A result
the retrieval facet cannot split into documents (a dict envelope, an image or a
document attachment) is withheld rather than passed through. The direct
agent.tool.<name>(...) call path runs the same executor, so it is governed too.
Prefer to declare it at construction? The same governance is a hook provider:
Governance decisions are local and synchronous, but they run off your event
loop, so a human-approval hold cannot freeze the agent’s other concurrent
tool calls. VisIQ only rewrites the tool-call event; Strands still runs the tool
exactly where it would have without governance.
Run this on AWS Bedrock
Bedrock is Strands Agents’s default model provider, so there is no adapter package. Pass aBedrockModel; the govern() call is unchanged:
AWS_PROFILE, instance role,
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY). Governance is untouched: the hook
fires at the same point, the same rules match, the same rows land in the audit
trail. Verified end to end against real Bedrock inference, with an ungoverned
control arm that had to leak for the run to count.
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 hook 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
govern() does not cover. The structured-output tool Strands adds for
structured_output_model= is the agent’s answer, not an action, so it is not
governed and a deny-everything policy still lets the run return. VisIQ decides at
the last hook position (HookOrder.SDK_LAST), so it sees arguments after your own
hooks rewrite them, but an ExecuteToolStage middleware you register runs after
the decision and can still change a tool’s input or result. A direct
agent.tool.<name>(...) call is governed, but it has no invocation end, so its
audit events are delivered with the next flush rather than immediately. Code that
calls a tool function directly, outside the agent, is not an agent tool call and is
not governed. Need finer control than the whole agent? 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.