Skip to content

Agents and Agentic governance

The SDK exposes two agent-related layers:

SurfacePurposeGateway route
shield.agentExplicit input/output/action/MCP guardrail checks/api/guardrails/evaluate
shield.agenticIdentity-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.

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.

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.

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 DeepintShield
from 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.

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.

RuntimeIntegration boundary
TemporalAutomatic activity interceptor; call native AsyncOpenAI inside an activity, outside deterministic workflow code
AWS StrandsFinal tool dispatch; the current middleware terminal authorizes the final arguments after input middleware and before execution
Google ADKSupported normal, live, and threaded tool dispatch; its model binder uses ADK’s public LiteLLM connector
HermesLoad the DeepIntShield host plugin so the Python client can arm the central tool dispatcher
OpenClawGenerated 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")

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.

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)
stateMeaning
liveThe agent is registered, approved, and its blueprint is current. Governed calls are decided normally.
pendingThe registration is captured and waiting for an operator in Work Queue.
deniedThe registration was denied. Enrol under a different agent name, or ask an operator to supersede the denial.
quarantinedA blueprint scan quarantined the agent. Review the scan findings to restore it.
approvedThe registration was approved but the agent’s governance profile is not enabled yet.
not_registeredNo registration exists for this agent on this Virtual Key.
unknownThe 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.

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.

@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.

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.

ObligationApplies to
mask:piiRecognised personal-data fields.
redact:secretsCredential-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:phiProtected-health-information fields - diagnosis, medication, medical record number, patient and insurance identifiers.
redact:card-numbersLuhn-valid card numbers found in any string value. Ordinary long numbers such as order ids are left intact.
redact:bank-accountsAccount number, IBAN, routing number, sort code, SWIFT/BIC.
redact:valueValue-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.

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.

Automatic boundaries are preferred for supported versions. Compatibility and plugin APIs remain available:

MethodResult
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

Application-facing Agentic boundaries translate internal errors to marked standard exceptions:

  • PermissionError for denials.
  • ConnectionError for gateway/unusable-response failures.
  • RuntimeError for 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.

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:

Terminal window
pip install "deepintshield[dpop]==2.8.3" # or: pip install cryptography

The 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.