Agent Governance Foundation

How to Add Authorization Gates to LangChain Agents

Agent Governance Foundation
langchainauthorizationpythonintegration

LangChain makes it straightforward to build agents that can call tools, search the web, write code, and interact with external APIs. What it doesn't provide is a way to control which of those things an agent is actually allowed to do at runtime — based on policy, context, and trust.

That gap is what authorization infrastructure is for.

This post covers two patterns for adding authorization gates to LangChain agents: a lightweight gate tool the agent invokes explicitly, and a deeper per-tool guard that enforces policy on every tool call automatically.

Why you need this

Consider a LangChain agent with access to a ShellTool, a FileWriteTool, and a RequestsTool. In development, all of these are fine — you want the agent to be able to do things.

In production, you need answers to:

  • Is this specific agent allowed to run shell commands right now, given its current trust level?
  • What happens if the agent's context changes — for example, it receives a prompt-injected instruction?
  • If the agent does something it shouldn't, can you trace exactly what was authorized and why?

A system prompt saying "don't do X" is not an authorization control. It's a suggestion. An authorization gate is a hard check that happens before tool execution, regardless of what the agent believes it should do.

Prerequisites

Install the SDK with the LangChain adapter:

pip install agf-sdk[langchain]

Set your API key:

export AGF_API_KEY=agfk_your_key_here

Pattern 1 — Gate tool

The gate tool is a BaseTool the agent can call before performing a sensitive operation. The agent uses it explicitly — it asks "am I allowed to do this?" and the tool returns an authorization decision.

This pattern works well when:

  • You want the agent to be able to reason about its own authorization
  • You're retrofitting governance onto an existing agent without changing its other tools
  • You want ALLOW/DENY/REVIEW_REQUIRED to influence the agent's next action
import os
from agf import AgentGovernance
from langchain.agents import initialize_agent, AgentType
from langchain_openai import ChatOpenAI

agf = AgentGovernance(
    api_key=os.environ["AGF_API_KEY"],
    org_id="org_acme",
)

# Returns a BaseTool — add it to your agent's tool list
agf_tool = agf.langchain_tool(agent_id="did:agf:agt_01abc")

agent = initialize_agent(
    tools=[agf_tool, *your_other_tools],
    llm=ChatOpenAI(model="gpt-4o"),
    agent=AgentType.OPENAI_FUNCTIONS,
)

agent.run("Write a summary of last quarter's sales to /tmp/report.txt")

When the agent decides it needs to write a file, it calls agf_tool with the action and resource, gets back a decision, and proceeds (or doesn't) based on the result.

Checking the result yourself

If you want to check authorization in your own code before running the agent:

result = agf.authorize(
    agent_id="did:agf:agt_01abc",
    action="write:file",
    resource="/tmp/report.txt",
)

if result.allowed:
    agent.run(task)
else:
    print(f"Blocked: {result.reason}")

authorize() always returns an AuthResult — it never raises on DENY or REVIEW_REQUIRED. Check result.allowed before proceeding.

Pattern 2 — Per-tool guard (enforced on every call)

The guard pattern wraps individual tools so that every invocation is policy-checked, whether or not the agent decides to ask. This is the stronger pattern — it enforces policy even if the agent's reasoning has been corrupted by prompt injection.

import os
from agf import AGFClient
from agf.langchain import AGFGuardedTool
from langchain_community.tools import ShellTool

client = AGFClient(api_key=os.environ["AGF_API_KEY"])

# Wrap ShellTool — every call, sync or async, goes through the PDP first
guarded_shell = AGFGuardedTool(
    tool=ShellTool(),
    client=client,
    agent_id="did:agf:agt_01abc",
    action_type="exec:shell",
    resource="local-shell",
)

agent = initialize_agent(
    tools=[guarded_shell],
    llm=ChatOpenAI(model="gpt-4o"),
    agent=AgentType.OPENAI_FUNCTIONS,
)

AGFGuardedTool overrides both _run() and _arun() — it works correctly whether the agent calls tools synchronously or asynchronously. If the PDP returns DENY, the tool raises AGFDeniedError before the underlying tool's code runs.

Which pattern to use

| | Gate tool | Guard tool | |---|---|---| | Agent controls when to check | Yes | No | | Enforced even on prompt injection | No | Yes | | Visible in agent reasoning | Yes | No | | Good for | Soft controls, explanation | Hard enforcement |

For production systems handling real actions, use the guard pattern. For exploratory agents where you want to observe how the agent reasons about its own authorization, use the gate tool. You can combine both.

What the audit trail looks like

Every call to the PDP — whether via authorize() or AGFGuardedTool — produces a signed audit artifact:

{
  "artifact_id": "art_7xKpQ2n",
  "agent_id": "did:agf:agt_01abc",
  "action": { "type": "exec:shell", "resource": "local-shell" },
  "decision": "ALLOW",
  "trust_score": 82,
  "risk_score": 31,
  "policy_version": "v4",
  "timestamp": "2026-06-17T14:23:01Z"
}

These artifacts are cryptographically signed and stored in your org's audit log. You can export them via the dashboard or the /v1/audit API for compliance reporting.

Handling REVIEW_REQUIRED

Some decisions require human approval before the agent can proceed. When the PDP returns REVIEW_REQUIRED, the authorization service creates a pending approval request:

from agf.exceptions import AGFReviewRequiredError

try:
    result = client.decide({
        "chain": ["did:agf:agt_01abc"],
        "action": { "type": "exec:shell", "resource": "prod-infra" },
        "audience": "api.company.internal",
        "context": { "environment": "production" },
    })
except AGFReviewRequiredError as e:
    print(f"Approval required: {e.approval_request_id}")
    # Pause execution, notify reviewer, resume after approval

Reviewers can approve or deny from the AGF dashboard or via the /v1/approvals API.

Next steps


The AGF Python SDK is open source. GitHub →