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 Agentsand 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 theWATCHLIGHT_AGENT_SLUGenvironment variable, or by anagent_mapentry 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), andagent_slugbecomes 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 byWATCHLIGHT_AGENT_MAP) maps an agent'sname(an orchestrator'sid) 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_scopesmaps 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_mapnames 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"(orTrue, 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 fromwatchlight_core.INTENT_CATEGORIES, orcustom:<name>; addintent_justificationfor a category such asfinancial_operationthat 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_startedand terminal events, sub-agent runs link to their parent's, and each tool call emits atool_call_observedevent with the decision and the value-free argument shape. - Egress. With a wl-proxy URL configured (
connect_proxy_urlorWATCHLIGHT_PROXY_URL), each root run installs thewl-sidecarwith 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
A2AAgentnode, aBidiAgent, any object that is not astrands.Agentor 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_mapnames 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.
Links
- Documentation: https://docs.watchlight.ai/de
- Website: https://watchlight.ai
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)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| watchlight_strands-0.1.1.tar.gz | 42.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.