Skip to content

GovernedRunner

parapetai_agent.adk.GovernedRunnergoogle.adk.runners.Runner with Cedar governance wired in automatically. A drop-in replacement for Runner(...), same rationale as GovernedAgent: the alternative — build_plugin() + plugins=[plugin] — is genuinely the entire integration (two lines), but it's opt-in per Runner(...) call site, and forgetting the plugins= kwarg means zero enforcement, silently. See the ADK guide for narrative usage; this page is the exhaustive parameter reference.

runner = GovernedRunner(
    agent=root_agent,
    app_name="demo",
    session_service=InMemorySessionService(),
)

Everything past agent/app_name/session_service is optional — the minimal call enforces real (if generic) Cedar policy from the moment it's constructed, using the SDK's own bundled default policy set.

Constructor

def __init__(
    self,
    *,
    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,
    alter_transforms: Mapping[str, Callable[[Any], Any]] | None = None,
    trust_session_user_id: bool = False,
    vendor_scoped_resources: bool = False,
    **kwargs: Any,
) -> None:

Every parameter is keyword-only — confirmed against google-adk's own Runner.__init__ signature, which has no positional parameters to forward, so this class doesn't invent any either.

policy_dir through alter_transforms, and vendor_scoped_resources, are the exact same governance surface as GovernedAgent — identical meaning, identical defaults, identical control-plane/policy-resolution behavior (see Vendor/CRUD metadata for what vendor_scoped_resources does). See that page for the full explanation of each. **kwargs are forwarded to Runner.__init__agent=, app_name=, session_service=, or app=, unchanged from plain Runner(...).

trust_session_user_id — the one ADK-specific parameter

Parameter Default Meaning
trust_session_user_id False Whether ADK's Session.user_id — a plain, unverified string every run_async() call must supply, but never authenticated by ADK itself — flows into Cedar's identity_claims.

MAF has no equivalent, since MAF's AgentSession carries no user_id at all. Defaulting this to True would be unsafe: it would make identity-gated Cedar policies silently stricter for ADK than for MAF, since ADK requires a user_id unconditionally where MAF's identity is fully optional — an unverified string would start satisfying identity checks that should require real, asserted identity (see governed_identity). Set this True only when your own deployment sets user_id from a source you already trust. Not part of the identity-registry cache key — it affects construction, not identity.

app= vs. plugins=

Confirmed against google-adk's own Runner._resolve_app(): passing both app= and plugins= raises ValueErrorplugins= itself is deprecated in favor of App(plugins=[...]). GovernedRunner branches on which construction style you used:

  • If app= is present, the governance plugin is appended to app.plugins in place.
  • Otherwise, it goes through the (deprecated) plugins= kwarg.

Either way, any plugins you pass explicitly run alongside the governance plugin — ADK invokes every registered plugin's matching callback for a given hook point; there's no single-plugin-wins ordering the way MAF's middleware chain has.

InMemoryGovernedRunner

class InMemoryGovernedRunner(GovernedRunner):
    def __init__(
        self,
        agent: AdkBaseAgent | None = None,
        *,
        app_name: str | None = None,
        **kwargs: Any,
    ) -> None:

GovernedRunner + google.adk.runners.InMemoryRunner's own convenience defaults, combined — for the common case of wanting the simplest possible construction with no real session/artifact/memory backend. Real ADK samples commonly use InMemoryRunner(agent=..., plugins=[...]) directly, not the fully-spelled-out Runner(session_service=..., artifact_service=..., ...)GovernedRunner alone doesn't mirror that convenience, since it subclasses Runner directly, not InMemoryRunner. Use this instead of GovernedRunner whenever your own code would otherwise reach for InMemoryRunner.

Parameter Default Meaning
agent None, positional Optional ADK BaseAgent.
app_name None, keyword Defaults to "InMemoryRunner" — but only if neither app_name nor kwargs["app"] is given.
**kwargs Forwarded to GovernedRunner.__init__ — so the same governance kwargs (policy_dir, agent_id, control_plane_url, trust_session_user_id, ...) work exactly as they do on GovernedRunner itself. artifact_service/memory_service/session_service are pre-filled with fresh InMemory* instances via setdefault — pass any of them explicitly to override just that one and keep the in-memory convenience for the rest.
from parapetai_agent.adk import InMemoryGovernedRunner

runner = InMemoryGovernedRunner(agent=root_agent, policy_dir="./policies")

build_plugin()

The lower-level function GovernedRunner builds on:

def build_plugin(
    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,
    alter_transforms: Mapping[str, Callable[[Any], Any]] | None = None,
    trust_session_user_id: bool = False,
    vendor_scoped_resources: bool = False,
) -> ParapetPlugin:

The ADK equivalent of build_middleware() — same kwarg surface, same semantics for every parameter (policy resolution, control-plane pull, Ed25519 PEP identity, OTel auto-wiring, idempotent per-identity caching; control_plane.py/pep_identity.py/governance_runtime.py are already framework-agnostic). Returns a single ParapetPlugin:

from google.adk.runners import Runner
from parapetai_agent.adk import build_plugin

plugin = build_plugin(policy_dir="./policies")
runner = Runner(agent=root_agent, app_name="demo", session_service=..., plugins=[plugin])

How a deny surfaces

GovernedRunner never raises — it uses ADK's own "early exit" callback contract. See the ADK guide for the full explanation and streaming behavior.

See also