Agents and Agentic governance
The SDK exposes two agent-related layers:
| Surface | Purpose | Gateway route |
|---|---|---|
shield.agent | Explicit input/output/action/MCP guardrail checks | /api/guardrails/evaluate |
shield.agentic | Identity-aware policy decisions, Agentic Registry discovery, blueprint attestation, approvals, obligations, and automatic tool enforcement | /api/agentic-new/* with older-gateway compatibility where implemented |
Use shield.agent when content/tool classification is enough. Use
shield.agentic when execution depends on the registered agent, acting user,
resource permission, delegation, tool/action, approval, or workload identity.
Plans and workspace security
Section titled “Plans and workspace security”Developer and Team workflows run without Agentic Security registration, blueprint review, or approval calls. On Business and above, security is enabled by default. A workspace administrator can turn Agentic Security off or on from Agentic → Work Queue.
The SDK reads the authenticated workspace setting before each new run. When
security is off, it executes the workflow without Agentic governance or run-ledger
calls. Existing activity remains available where the plan includes it; these
unguarded runs do not appear as governed runs. Gateway credentials, workspace
isolation, ordinary LLM guardrails, and Virtual Key tool restrictions still apply.
Turning security back on restores registration, blueprint, and approval checks
on the next invocation. Network and authentication failures never turn it off.
This requires a gateway and SDK version supporting /api/agentic-new/runtime-config;
older gateways retain their existing enforcement behavior.
Explicit agent guardrails
Section titled “Explicit agent guardrails”shield.agent.check_input("user message")shield.agent.evaluate_tool( name="ledger_read", args={"account": "A-7"}, action_class="read",)shield.agent.check_output("assistant response")See Chat and guardrails for result and exception semantics.
Automatic Agentic enforcement
Section titled “Automatic Agentic enforcement”Constructing a live DeepintShield client installs idempotent guards for
supported framework modules already imported and watches supported late
imports. Current integrations cover LangGraph, LangChain, LlamaIndex,
AutoGen/AG2, PydanticAI, OpenAI Agents SDK, AWS Strands, Google ADK,
Temporal, and the Hermes tool dispatcher. A framework is inspected only after
its package has finished importing: a nested submodule import (for example a
helper that LangChain loads during its own initialization) defers the guard until
the enclosing import completes, avoiding instrumentation of partially
initialized modules. An unsupported version still stops with
governance_configuration_error rather than importing ungoverned.
LiteLLM completions are provider inference calls. They use the gateway’s normal
inference policies and do not create an Agentic tool registration or blueprint,
including when called inside shield.agentic.run(). Surrounding workflow/tool
guards and explicit shield.agentic.decide() calls still enforce Agentic policy.
from deepintshield import DeepintShieldfrom langgraph.graph import StateGraph
shield = DeepintShield.from_env()
builder = StateGraph(State)# Add your ordinary nodes and tools.app = builder.compile()app.invoke(initial_state)The framework remains a third-party object. At its supported build/run/tool boundary, the SDK reports bounded topology/source evidence, obtains the authoritative decision, applies supported obligations, and stops execution on deny or approval-required outcomes.
Route native model traffic
Section titled “Route native model traffic”Keep the framework’s agent, tools, and runner, and use its native model binder for gateway inference:
from agents import Agent, Runner
async def run_support(shield): # shield has this workload's registered agent_name. async with shield.async_openai() as client: model = shield.bind("openai_agents").model( "openai/gpt-4o-mini", api="responses", openai_client=client ) agent = Agent( name="Support", instructions="Help with support questions.", model=model ) return await Runner.run(agent, "How can I track a refund?")Model binding configures inference; the live DeepintShield client installs
the supported tool-governance boundary. Neither the model binder nor plain
OpenAI inference enrolls or approves an agent. Keep shield alive during agent
execution, select the correct run scope when sharing a process, and close
native model clients at shutdown. See Providers and frameworks
for each binder’s API selector, capability fields, and dependency combination.
Durable and plugin runtime boundaries
Section titled “Durable and plugin runtime boundaries”| Runtime | Integration boundary |
|---|---|
| Temporal | Automatic activity interceptor; call native AsyncOpenAI inside an activity, outside deterministic workflow code |
| AWS Strands | Final tool dispatch; the current middleware terminal authorizes the final arguments after input middleware and before execution |
| Google ADK | Supported normal, live, and threaded tool dispatch; its model binder uses ADK’s public LiteLLM connector |
| Hermes | Load the DeepIntShield host plugin so the Python client can arm the central tool dispatcher |
| OpenClaw | Generated provider configuration for inference plus the separate TypeScript plugin for before_tool_call governance |
These runtimes keep their native execution and session lifecycle. Installing a model connector alone does not install a non-Python host plugin or grant tool access. Strands execution shapes the SDK cannot instrument fail closed.
Discovery labels the network with a detected framework. Detection reads the shape of the object you pass, so an ambiguous target - a bare list of callables, for example - can be filed under a framework you do not use. Pass the label explicitly when you know it:
shield.agentic.discover(tools, framework="generic", name="refund-network")Registration lifecycle
Section titled “Registration lifecycle”A new or changed agent blueprint is not automatically trusted. On first execution, the SDK captures bounded, credential-redacted implementation evidence. Common outcomes are:
agent_registration_pending: review and activate the captured agent.agent_not_registered: capture did not persist; verify gateway connectivity and rerun discovery.blueprint_coverage_incomplete: executable source coverage was missing, partial, truncated, or omitted.blueprint_scan_unavailable: the required scan/registration acknowledgement could not be obtained.
Review registrations and blueprint findings in Agentic before retrying. An unchanged approved blueprint avoids repeated scan work, but each governed tool still requires authorization (or a valid decision-cache hit) and therefore has workload-dependent request-path latency.
Check enrolment before serving traffic
Section titled “Check enrolment before serving traffic”status() asks the gateway what state this workload’s enrolment is in, so a
service can refuse to start rather than discover the answer one denied tool
call at a time:
state = shield.agentic.status()
if state["state"] != "live": log.error( "agent not governed yet: %s - %s", state["state"], state.get("reason", ""), ) raise SystemExit(1)state | Meaning |
|---|---|
live | The agent is registered, approved, and its blueprint is current. Governed calls are decided normally. |
pending | The registration is captured and waiting for an operator in Work Queue. |
denied | The registration was denied. Enrol under a different agent name, or ask an operator to supersede the denial. |
quarantined | A blueprint scan quarantined the agent. Review the scan findings to restore it. |
approved | The registration was approved but the agent’s governance profile is not enabled yet. |
not_registered | No registration exists for this agent on this Virtual Key. |
unknown | The gateway could not be reached, or answered with an error. reason carries the detail. |
status() never raises and never blocks a governed call - it is a read for
your own healthcheck. Alongside state and reason it carries an action
naming the one thing that resolves it, the resolved agent_subject, and a
review_url pointing at the workspace page an operator should open. It requires
a Virtual Key; anything else comes back as unknown with the reason.
Discovery reports the same lifecycle states on its own: when a discovery run
comes back pending, denied, quarantined, or coverage-incomplete, the SDK
logs one warning per process naming the state and what resolves it. Ordinary
transport failures are not reported that way, so a flaky network does not turn
into a stream of governance warnings.
Direct decision probe
Section titled “Direct decision probe”decide() returns a Decision; it does not raise merely because the verdict is
DENY:
decision = shield.agentic.decide( tool="ledger.post", args={"amount": 12}, action="create_entry", action_class="write", prompt="Post the approved journal entry.",)
if not decision.proceed: print(decision.verdict, decision.decision_id)prompt is supplied for scanning at the PDP boundary. The SDK contract treats
it as scan-only, but applications should still avoid sending secrets that are
not necessary for the decision.
For full control, construct DelegationContext and ContextBag. Optional
application-observed signals include memory integrity, hallucination risk, goal
drift, inter-agent communication integrity, output manipulation, recovery cost,
and RAG provenance. These values influence policy only when your workspace
policy evaluates them; the SDK does not calculate their truth for you.
Govern one function
Section titled “Govern one function”@shield.agentic.tool( "ledger.post", action="create_entry", action_class="write", recovery_cost="high",)def post_entry(row: dict) -> dict: return ledger.insert(row)The decorator supports synchronous functions, coroutines, generators, and
async generators. It performs blueprint preflight and authorization before the
function body or iteration begins. shield_tool(tool=..., client=...) is the
standalone equivalent.
When exactly one live client exists, a bare decorator can resolve it. With
multiple clients, pass client= or bind a request-local run scope; ambiguity
fails closed.
Obligations
Section titled “Obligations”A verdict can Allow a call and attach an obligation: the tool runs, but with protected values redacted first. The SDK applies the obligations it can carry out locally before the function body sees its arguments.
| Obligation | Applies to |
|---|---|
mask:pii | Recognised personal-data fields. |
redact:secrets | Credential-shaped field names, and credential-shaped values anywhere in a string argument (issued API keys, personal access tokens, cloud access-key ids, JWTs, PEM private keys). |
redact:phi | Protected-health-information fields - diagnosis, medication, medical record number, patient and insurance identifiers. |
redact:card-numbers | Luhn-valid card numbers found in any string value. Ordinary long numbers such as order ids are left intact. |
redact:bank-accounts | Account number, IBAN, routing number, sort code, SWIFT/BIC. |
redact:value | Value-carrying fields (value, secret_value, plaintext, and similar). The secrets tool templates pair this with a result-side fingerprint-only rule, which the gateway enforces. |
mask:pii matches field names exactly - email, not customer_email.
Redaction covers positional and keyword arguments alike, so the calling
convention cannot change what gets protected - send(email) and
send(email=...) are redacted identically. Arguments the SDK cannot bind to a
parameter name (a builtin with no signature, *args packing) are passed
through unchanged rather than guessed at.
An obligation the SDK does not recognise is a local no-op; the gateway remains responsible for enforcing it. The argument digest sent to the PDP is computed from the original call, so redaction never invalidates an approval pinned to that digest.
Acting user and run scope
Section titled “Acting user and run scope”with shield.agentic.run( email="alice@example.com", session_id="checkout-req-018",) as run_id: app.invoke(initial_state)run() uses request/task-local context, restores nested state, and carries the
acting principal plus session grouping into governed calls. as_user() binds a
principal until changed; start_run()/end_run() manage a run override
manually. Prefer run() for shared clients and concurrent request handlers.
Identity resolution is designed to be fail-soft for directory creation: if the gateway cannot be reached, it returns a deterministic local subject. Tool authorization itself is fail-closed.
Explicit integration factories
Section titled “Explicit integration factories”Automatic boundaries are preferred for supported versions. Compatibility and plugin APIs remain available:
| Method | Result |
|---|---|
guard() / callback() | LangChain callback handler |
govern(target) | Describe, report, and instrument a supported target |
langgraph(graph) | In-place LangGraph adapter |
openai_agents(target) | OpenAI Agents adapter |
llamaindex(tools) | LlamaIndex tool adapter |
autogen(target) | AutoGen tool adapter |
pydanticai(agent) | PydanticAI adapter |
temporal() | Temporal interceptor |
strands() | AWS Strands hook provider |
google_adk() | Google ADK plugin |
hermes(ctx) | Install hooks on a Hermes plugin context |
openclaw_config(...) | OpenClaw provider configuration; tool governance still requires its TypeScript plugin |
Error boundary
Section titled “Error boundary”Application-facing Agentic boundaries translate internal errors to marked standard exceptions:
PermissionErrorfor denials.ConnectionErrorfor gateway/unusable-response failures.RuntimeErrorfor approval, configuration, dependency, registration, and blueprint lifecycle failures.
Read the stable marker through the public error helper rather than coupling to private attribute names:
from deepintshield import get_error_definition, get_exception_error_code
try: post_entry({"amount": 12})except (PermissionError, ConnectionError, RuntimeError) as exc: code = get_exception_error_code(exc) definition = get_error_definition(code) if definition is None: raise log.error("Agentic operation stopped", extra={"code": code})See Error codes for trusted descriptions, retryability, operator actions, and stability guidance, and Agentic control-plane setup for registration and policy workflows.
Proof-of-possession
Section titled “Proof-of-possession”When a workspace identity provider requires proof-of-possession, the agent token is no longer a plain bearer: it carries the thumbprint of a key this process holds, and every request must present a fresh DPoP proof (RFC 9449) signed by that key. A token lifted from a log or a trace is then useless without the key.
Install the optional dependency to enable it:
pip install "deepintshield[dpop]==2.8.3" # or: pip install cryptographyThe key is generated per process and never written to disk - a restarted workload simply obtains a token bound to its new key. Without the dependency, the SDK sends a plain bearer token, which a provider that requires proof-of-possession refuses. See Agent workload identity for the workspace-side configuration.