Observed since 20 Oct, 23:35 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
agenttrafficcop
Python SDK for AgentTrafficCop — a runtime circuit breaker and schema shield for multi-agent systems.
pip install "agenttrafficcop[langgraph]"
import asyncio
from agenttrafficcop import Guard, LoopDetected
async def main():
async with Guard(
url="http://localhost:8000",
api_key="your-api-key",
) as guard:
try:
result = await guard.check(
session_id="run-42",
sender="coder",
recipient="reviewer",
payload={"diff": "fix typo", "file": "app.py"},
)
print("approved at step", result.step)
except LoopDetected as e:
print("loop caught:", e.error_code)
print(" session:", e.session_id)
print(" step:", e.diagnostics.get("step"))
asyncio.run(main())
Guard(url, api_key, *, timeout=10.0)Async client. Use as a context manager so the underlying HTTP client is closed cleanly.
| Method | Purpose |
|---|---|
await guard.check(session_id, sender, recipient, payload, intent="handoff", metadata=None) | Route one message. Returns RouteResult on success. |
await guard.health() | Call GET /health. No auth. |
await guard.reset_session(session_id) | Delete a session. Returns bool. |
await guard.close() | Close the HTTP client. Called by __aexit__. |
RouteResult| Attribute | Type | Meaning |
|---|---|---|
approved | bool | Always True on success. |
session_id | str | Session this message belongs to. |
step | int | 1-indexed step number. |
payload_hash | str | SHA-256 hex digest (64 chars). |
message | str | Human-readable confirmation. |
raw | dict | Original response body. |
All inherit from AgentTrafficCopError:
| Exception | When |
|---|---|
LoopDetected | Repeated or cyclic payload. |
StepLimitExceeded | Session hit the step cap. |
SchemaRejected | Server rejected the message schema. |
AuthError | Missing or invalid API key (401). |
ConnectionError | Could not reach the server. |
AgentTrafficCopError | Base class — catch this for everything. |
Every exception exposes .error_code, .session_id, and .diagnostics.
try:
await guard.check(...)
except AgentTrafficCopError as e:
log.error("governance: %s session=%s", e.error_code, e.session_id)
# ... decide what to do
If you're using LangGraph, the SDK ships an optional adapter that wraps any node so its handoff passes through AgentTrafficCop before the node runs.
pip install -e "./sdk[langgraph]"
from agenttrafficcop import Guard
from agenttrafficcop.langgraph import guarded_node
guard = Guard(url="http://localhost:8000", api_key="your-key")
graph.add_node(
"reviewer",
guarded_node(
reviewer_fn,
guard=guard,
sender="coder",
recipient="reviewer",
# Both of these can be static values OR callables that take
# the LangGraph state and return the value.
session_id=lambda state: state["run_id"],
payload=lambda state: {"draft": state["draft"]},
),
)
That's it. Every time LangGraph invokes the reviewer node,
AgentTrafficCop sees a handoff. If the breaker trips, the node raises
before doing any work.
payload=?The wrapper falls back to hashing the entire state dict (minus a small
denylist of known-noise keys like messages, history, trace). This
works for small graphs, but for real graphs you should supply an
explicit payload= extractor — otherwise a growing message history
could cause false-positive trips.
guarded_node supports sync and async node functions. Sync nodes are
called directly; async nodes are awaited. The guard call itself is
always async, so if you use a sync node you must call it from a sync
context (no running event loop). For anything async-first, make the
node async def and everything works.
By default, a breaker trip raises the SDK exception (e.g.
LoopDetected) and propagates out of the node. If you'd rather handle
trips via state — for example, to route to a human-review node — pass:
guarded_node(..., raise_on_trip=False)
The node becomes a no-op on a trip, and the graph continues.
See examples/langgraph_demo.py
for a runnable two-node graph that visibly trips the breaker.
httpx 0.27.xMIT
Download counts and popularity will appear here as the collector receives them.
| Package / target | Requirement | Relationship |
|---|---|---|
httpxEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | httpx<0.28.0,>=0.27.0 | runtime |
langgraphEnvironment{
"extras": "",
"marker": "extra == \"langgraph\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | langgraph<2.0.0,>=1.2.0; extra == "langgraph" | runtime |
pytestEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | pytest>=8.0; extra == "dev" | runtime |
pytest-asyncioEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.11",
"evaluation": "NOT_EVALUATED"
} | pytest-asyncio>=0.23; extra == "dev" | runtime |
bdist_wheel · 12.61 KB · Python py3
sdist · 14.27 KB · Python source
Observed since 20 Oct, 23:35 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:06c3cdaa80dd44c0dd9484dceebc0a7a4fdbfc8fb5a669d021bda17f5614843d