ParapetAgentMiddleware¶
parapetai_agent.langgraph.ParapetAgentMiddleware — a real
langchain.agents.middleware.AgentMiddleware for
langchain.agents.create_agent(..., middleware=[...]). See the
LangGraph / LangChain guide for narrative
usage and design rationale; this page is the exhaustive parameter
reference.
from langchain.agents import create_agent
from parapetai_agent.langgraph import build_middleware
agent = create_agent(
model,
tools=[lookup_order, hr_lookup],
middleware=[build_middleware(policy_dir="./policies")],
)
There is no GovernedAgent/GovernedRunner-style subclass here — unlike
MAF's Agent/ADK's Runner, create_agent is a functional construction
API, not a subclassable class, so the middleware object itself is the
entire integration surface.
build_middleware()¶
def build_middleware(
policy_dir: str | Path | None = None,
entities_path: str | Path | None = None,
agent_id: str | None = None,
tenant: str = "default",
control_plane_url: str | None = None,
agent_secret: str | None = None,
pep_key_path: str | Path | None = None,
persist_policy_dir: str | Path | None = None,
local_log_dir: str | Path | None = None,
persist_pep_key: bool = True,
otel_log_mode: Literal["streaming", "buffered"] = "buffered",
console: bool = True,
vendor_scoped_resources: bool = False,
) -> ParapetAgentMiddleware:
Same kwarg surface, same semantics for every parameter, as
build_middleware()
(MAF)/build_plugin() (ADK) — policy
resolution, control-plane pull, Ed25519 PEP identity, OTel auto-wiring,
idempotent per-identity caching, and vendor_scoped_resources (see
Vendor/CRUD metadata). None of it is LangGraph-specific
(governance_runtime.py/control_plane.py/pep_identity.py are already
framework-agnostic); see those pages for the full explanation of each
parameter.
One difference from both: no alter_transforms= parameter. ALTER
decisions are not yet supported by this adapter — accepting the kwarg and
silently doing nothing with it would be worse than not accepting it. See
the Known gaps section of the
guide.
Idempotent the same way: calling build_middleware() twice with the same
(policy_dir, entities_path, agent_id, tenant, control_plane_url) returns
the same ParapetAgentMiddleware instance rather than starting a second
background poller. reset_middleware_registry() (not re-exported under
that bare name from the package root — see below) clears the cache and
joins every poller thread; test-only, mirrors
parapetai_agent.maf.reset_middleware_registry() exactly.
ParapetAgentMiddleware¶
class ParapetAgentMiddleware(AgentMiddleware):
def __init__(
self, engine: PolicyEngine, caller: Caller, *, vendor_scoped_resources: bool = False
) -> None: ...
Constructed directly only if you're building your own PolicyEngine/
Caller rather than going through build_middleware() — the common case
is build_middleware(). Implements:
| Method | Cedar stage | Runs |
|---|---|---|
wrap_model_call / awrap_model_call |
model_call (pre), then post |
Before and after the underlying model call |
wrap_tool_call / awrap_tool_call |
tool_call |
Before the tool body executes |
before_agent / abefore_agent, after_agent / aafter_agent |
none — no Cedar decision here | Once at the start/end of one whole agent.invoke()/ainvoke() run. No enforcement role; exist purely to bracket the cumulative cost/token tracking trace scope, since no OTel SpanContext exists here yet to derive one from the way MAF/ADK do. |
Each of wrap_model_call/wrap_tool_call raises GovernanceDenied
(never a silently-substituted response) before calling the framework's
own handler(request) on deny — see How a deny surfaces.
Naming: not re-exported from parapetai_agent under the bare names¶
build_middleware/reset_middleware_registry already belong to
parapetai_agent.maf's top-level re-export — this module's versions are a
genuinely different object (a different registry, a different
middleware type), not the same function imported twice, so they aren't
aliased over MAF's at the package root. Reach them as:
from parapetai_agent.langgraph import build_middleware, reset_middleware_registry
# or, from the package root:
from parapetai_agent import reset_langgraph_middleware_registry
ParapetAgentMiddleware, agent_identity, configure_otel,
configure_rotating_audit_log, current_identity, flush_otel,
identity_from_bearer_token, and track_tool_denials are re-exported
under their bare names from parapetai_agent (shared, framework-agnostic
names — same object either way).
See also¶
- LangGraph / LangChain guide — narrative usage, design rationale, known gaps
governed_identity— per-call end-user identityDecision, Exceptions- Environment variables
- Vendor/CRUD metadata, Cumulative cost & token tracking