Skip to main content

watchlight-strands

Watchlight authorization for Strands Agents: every tool call, every sub-agent, every Swarm and Graph node, fail-closed. Install it once, on the root.

pip install watchlight-strands

Independent third-party plugin. This is an independent integration built by Watchlight AI. It is not affiliated with, endorsed by, or sponsored by Amazon Web Services. Strands Agents and related names are used here only to describe compatibility.

What it does

watchlight-strands puts a Watchlight authorization decision in front of every tool call a Strands agent makes, so each call is allowed or denied before it runs. You add one plugin, to the root agent or the root Swarm or Graph. The plugin then attaches itself to every agent and orchestrator the root reaches and governs them too: agents used as tools, agents a tool function calls, and Swarm and Graph nodes, including nested ones. Each child runs as a governed sub-agent under its parent's run, with a narrower scope.

The plugin uses only Strands' public hook API. It registers its callbacks on each agent's public hook registry (agent.hooks.add_callback). It does not patch, wrap or replace any Strands function, and your tools, prompts and agent code stay as they are. The policy decisions run on Watchlight's engine, in-process for local development or on the governed control plane in production.

Setup: one line, at the root

from strands import Agent
from watchlight_strands import WatchlightStrandsPlugin

researcher = Agent(name="researcher", tools=[web_search])
writer = Agent(
    name="writer",
    tools=[researcher.as_tool()],
    plugins=[WatchlightStrandsPlugin(agent_slug="agent-writer")],   # the only line you add
)
writer("Write a short brief on agent governance.")

The same plugin attaches as a hook provider (Agent(hooks=[...])) and to orchestrators (Swarm(nodes, plugins=[...]), GraphBuilder().set_plugins([...])). Install it on the root only.

Identity

Who an agent runs as comes from Watchlight and from configuration, never from your code.

  • The root runs as the registered agent named by agent_slug= or the WATCHLIGHT_AGENT_SLUG environment variable, or by an agent_map entry for its name. When watchlight-core gains credential-bound roots, the root will run as the agent bound to the process's verified credential (for example the AWS role mapping on Amazon Bedrock AgentCore), and agent_slug becomes an optional override.
  • A child never needs a registered agent of its own. It runs as a governed sub-agent of its parent, with a scope Watchlight narrows to a strict subset of the parent's: by default it asks for its own tool names. Until watchlight-core supports labelled sub-agents, an unmapped child runs under its parent's registered agent.
  • agent_map (or a JSON file named by WATCHLIGHT_AGENT_MAP) maps an agent's name (an orchestrator's id) to a registered agent. A mapped child runs as that agent, with its own policies, and is still spawned and narrowed under its parent. subagent_scopes maps a child's name to the scope it asks for.
  • An agent that cannot be identified is refused. A root with no slug and no map entry does not run. A child invoked on its own, outside any governed run, does not run unless agent_map names it.

What it enforces

  • Run start. Each invocation (agent(...), invoke_async, stream_async) opens a governed run before the first model turn: a root run, or a sub-agent run when a governed run is active in the calling context. If the run cannot be opened (no identity, an unregistered agent, a refused spawn, an unreachable backend, a missing pre-flight manifest, egress that cannot be governed), the invocation raises and the agent does not run: no model turn and no tool call.
  • Tool calls. Every tool call is authorized on the invocation's own run before it runs. That covers Python function tools (sync, async and streaming), MCP tools served through Strands' MCPClient, and agents used as tools. A denial, an unreachable backend, a timeout (authorize_timeout_seconds, 30 seconds by default) or an unexpected answer cancels the call through Strands' cancel_tool, and the tool body never runs. Concurrent tool calls are each authorized separately. Only argument names, count and value types are sent, never argument values.
  • Reaching children. When the root is built, the plugin attaches to every agent and orchestrator it can already see: agents used as tools, agents and orchestrators a tool function refers to (by closure or module name), and Swarm and Graph nodes, recursively. Before a tool call or a node runs, it attaches again to what that call will run, so agents and nodes added later are covered. A child it cannot attach to is refused before it runs.
  • Run-ending verdicts. A quarantine, terminate, sever or revoke verdict raises, which ends the invocation.
  • Human approval. An approval-required decision becomes a Strands interrupt named watchlight-approval, and the tool has not run. Resume with an interrupt response of "approved" (or True, or {"approved": True}) after an operator has approved the request in Watchlight; the plugin retries the authorization on the same run, and the tool runs only if Watchlight now allows it. Watchlight binds an approval to the intent it was requested under, so a run that uses approvals declares a typed intent: WatchlightStrandsPlugin(intent_objective="pay the invoice", intent_category="financial_operation") (a category from watchlight_core.INTENT_CATEGORIES, or custom:<name>; add intent_justification for a category such as financial_operation that Watchlight refuses without one). Every authorization of the run carries it, and the retry carries the approval inside it. Without a declared intent, an approved resume is refused with a message that says so, because Watchlight could not redeem the approval. The approval request id never leaves your process.
  • Run end. The run closes when the invocation ends: completed, failed or cancelled. Sub-agent runs still open under it end with it. An invocation that stops for an interrupt keeps its run for the resume.
  • Lineage. Each run emits its execution_started and terminal events, sub-agent runs link to their parent's, and each tool call emits a tool_call_observed event with the decision and the value-free argument shape.
  • Egress. With a wl-proxy URL configured (connect_proxy_url or WATCHLIGHT_PROXY_URL), each root run installs the wl-sidecar with that run's proxy token, so model calls (Amazon Bedrock included) and tool HTTP calls go through wl-proxy. If no proxy token was issued or the sidecar fails to start, the run is refused.

What is refused

These never run:

  • A child the plugin cannot attach to: an agent type it does not govern (a remote A2AAgent node, a BidiAgent, any object that is not a strands.Agent or a Swarm/Graph orchestrator), or a registry that will not take the callbacks. The tool call or node is cancelled.
  • An agent with background_tasks=..., root or child: a background tool call runs after its governed run has ended.
  • A tool function that refers to an agent or orchestrator the plugin cannot govern: the call is cancelled, because the plugin cannot tell whether the body would run it.
  • An agent that cannot be identified (see Identity).
  • Direct tool calls (agent.tool.my_tool(...)) made outside an invocation, an unknown tool, and a tool selection another hook rewrote inconsistently.
  • A second Watchlight plugin on the same agent, and one plugin installed on two roots.

What it does not enforce

  • Agents a tool function creates itself. An agent constructed inside a tool body exists only there, and Strands has no public hook through which the plugin could reach it before it runs. It is not governed in-process. With wl-proxy configured, its network calls still go through wl-proxy under the root run. Build sub-agents outside the tool function (or pass them as tools) so the plugin can reach them.
  • Lost context. An agent invoked from a thread your own code starts (Python threads do not inherit context) has no parent run, so it is refused unless agent_map names it.
  • Model turns are not authorized one by one. A model turn is refused when it belongs to no governed run or its run is quarantined or severed; egress governance comes from wl-proxy.
  • Hooks after the gate. The gate runs after every other hook. A hook you register after the plugin at an infinite order runs after it; if it changes the tool, the plugin withholds the result and logs an error.
  • Intent declaration is not wired yet, and a quarantine does not interrupt an in-flight model call; it stops the run at the next model turn or tool call.

Developer Edition

The zero-infrastructure Developer Edition runs the compiled engine in-process (requires the watchlight-engine package):

pip install watchlight-strands "watchlight-engine>=0.2.4,<0.3"
from strands import Agent, tool
from watchlight_core import InProcessClient
from watchlight_strands import WatchlightStrandsPlugin

# Cedar policies: these two tools may run, nothing else.
POLICIES = [
    {"name": "search",
     "code": 'permit(principal, action == Action::"execute", resource == Resource::"web_search");'},
    {"name": "delegate-to-researcher",
     "code": 'permit(principal, action == Action::"execute", resource == Resource::"researcher");'},
]

@tool
def web_search(query: str) -> str:
    """Search the web."""
    ...

researcher = Agent(name="researcher", tools=[web_search])
writer = Agent(
    name="writer",
    tools=[researcher.as_tool()],
    plugins=[WatchlightStrandsPlugin(agent_slug="agent-writer", governance=InProcessClient(POLICIES))],
)
writer("Write a short brief on agent governance.")

Each tool call is authorized as action execute on Resource::"<tool name>". An agent used as a tool is authorized under its tool name (here researcher) and then runs as a governed sub-agent under the writer's run, limited to its own tools.

Configuration

Argument Meaning
agent_slug The root's registered agent (env WATCHLIGHT_AGENT_SLUG). Optional when agent_map names the root.
agent_map Agent name to registered agent, a mapping or a JSON file path (env WATCHLIGHT_AGENT_MAP).
subagent_scopes Agent name to the scope it asks for as a sub-agent: allowed_tools, allowed_resources, allowed_intents, max_depth, time_budget_seconds. A named time_budget_seconds is granted exactly or the spawn is refused.
authorize_timeout_seconds Upper bound on each tool authorization (default 30).
connect_proxy_url wl-proxy URL for governed egress (env WATCHLIGHT_PROXY_URL).
application_id The AI application the runs belong to (env WATCHLIGHT_APPLICATION_ID). An Enterprise feature: the in-process engine refuses it.
governance A governance backend to use as is, such as InProcessClient.

In a Swarm, agents hand over with Strands' built-in handoff_to_agent tool; like any tool, your policy must allow it.

Two backends, same code

Backend Runs
Developer Edition InProcessClient The compiled engine, in-process: no server, no network
Enterprise ApdpClient The governed control plane: signed lineage, drift detection, fleet-wide governance

Moving from local to production means dropping governance= and pointing the plugin at the control plane:

WatchlightStrandsPlugin("https://apdp.your-company.example", agent_slug="agent-writer")
# The credential is loaded from the environment or your credential provider
# (for example WATCHLIGHT_CREDENTIAL_FILE); never put a key in source.

Compatibility

strands-agents 1.55 or later, below 2. The test suite asserts the Strands hook surface the plugin relies on, so a Strands release that moves it fails the tests instead of silently weakening governance.

License

Apache-2.0

Metadata

Release files for watchlight-strands 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for watchlight-strands 0.1.1
File Size Uploaded Provenance
watchlight_strands-0.1.1.tar.gz 42.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for watchlight-strands 0.1.1
File Interpreter ABI Platform
watchlight_strands-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 68.8 kB

Release files / watchlight_strands-0.1.1.tar.gz

Download URL watchlight_strands-0.1.1.tar.gz
Size 42.7 kB
Tags Source
SHA-256 checksum
How to use checksums
18e8cd95fbe6b7ddae1f362a37e4d5edb76afeb438acb7fa60df126a7fd44bd5
BLAKE2b-256 checksum
How to use checksums
b43a6db20db264ec313ab3af158564229ba08ee80c551b476aee6a2dc35bcc01
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

Release files / watchlight_strands-0.1.1-py3-none-any.whl

Download URL watchlight_strands-0.1.1-py3-none-any.whl
Size 26.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
804168648169ab6eb57a88e6a89556af83509c4191f36d0c0650f38771d63a1d
BLAKE2b-256 checksum
How to use checksums
afff0d569737e2f2a1917f143a1ebabc228886e79f5936d651e68e92c57f8799
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

Security for watchlight-strands 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for watchlight_strands-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page