Metadata-Version: 2.4
Name: sentinel-agent-trust-langchain
Version: 0.1.0
Summary: LangChain adapter for Sentinel AI agent governance — wrap any LangChain tool so every call is authorized (and audited) by the Sentinel gateway before it runs. Private beta: cosimo@plannest.net
Author-email: "Cosimo (plannest.net)" <cosimo@plannest.net>
License: Apache-2.0
Keywords: ai-agents,langchain,governance,security,authorization,sentinel,tools
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: sentinel-agent-trust>=0.2.0
Requires-Dist: langchain-core>=0.3
Dynamic: license-file

# sentinel-agent-trust-langchain

LangChain adapter for **Sentinel AI** — the identity & governance layer for
AI agents. Wrap any LangChain tool so that **every call is authorized (and
audited) by the Sentinel gateway before it runs**: your tool code and your
agent code stay untouched.

> Sentinel is in **private beta**. For source access, a hosted sandbox
> organization key, or to collaborate: **cosimo@plannest.net**

## Install

```bash
pip install sentinel-agent-trust-langchain
```

Depends only on the public SDK (`sentinel-agent-trust`) and `langchain-core`
— no heavyweight extras.

## Quickstart

```python
import os
from langchain_core.tools import tool
from sentinel_langchain import SentinelGuard

@tool
def create_refund(customer: str, amount: float) -> str:
    """Issue a refund to a customer."""
    return billing.refund(customer, amount)

guard = SentinelGuard(
    agent_id="agt_...",
    private_key=os.environ["SENTINEL_AGENT_PRIVATE_KEY"],  # base64 Ed25519 seed
    gateway_url="https://your-gateway.example.com",
)

tools = guard.wrap_all([create_refund])   # ← the only change to your agent

# then use the governed tools anywhere LangChain tools go:
# from langchain.agents import create_agent
# agent = create_agent(model, tools)
```

Every tool call is signed with the agent's Ed25519 key and checked against
your policies in real time. The wrapped tool keeps the same name,
description, and argument schema — the LLM sees no difference.

## What the model sees

Sentinel's decision is translated into an observation the model can act on
(the underlying tool is **never executed** unless the gateway approves):

| Gateway outcome | Tool behavior |
|---|---|
| `200` approved | the wrapped tool runs, result returned as usual |
| `403` denied (policy, constraint, budget) | not executed; observation says the denial is **final** for these arguments — don't retry, branch |
| `202` approval required | not executed; observation carries the `approval_id` and says to inform the user and retry the identical call after a human approves |
| gateway unreachable | not executed (**fail-closed**); observation says it's transient and safe to retry |

This mapping is what keeps agents out of retry loops: a policy denial reads
as a hard "no", an approval-pending reads as "park it", an outage reads as
"try again shortly".

## Mapping tools to your policy taxonomy

By default a tool named `create_refund` is authorized as action
`tool.create_refund` on resource `tool://create_refund`, so a single policy
with `action_pattern: "tool.*"` governs every wrapped tool. For business-level
policies, map tools explicitly:

```python
tools = guard.wrap_all(
    [create_refund, read_contact],
    overrides={
        "create_refund": {"action": "crm.refund.create", "resource": "crm://refunds"},
        "read_contact":  {"action": "crm.contact.read",  "resource": "crm://contacts/*"},
    },
)
```

…and the policy on the Sentinel side:

```json
{
  "action_pattern": "crm.refund.create",
  "constraints": {"max_amount": 5000, "max_total_amount_per_day": 20000},
  "approval_thresholds": {"amount": 500}
}
```

*Each refund ≤ 5000, at most 20 000/day in total, and a human approves
anything from 500 up — enforced outside the model, no prompt engineering.*

The tool's parsed arguments are sent as the action payload, so constraints
like `max_amount` apply to the actual `amount` argument the model chose.

## Async

Both paths are supported: `tool.invoke(...)` and `await tool.ainvoke(...)`.
The authorization call runs in a worker thread in the async path, so it never
blocks the event loop.

## CrewAI and other frameworks

CrewAI (and most agent frameworks) can consume LangChain tools directly, so
wrapping with `SentinelGuard` before handing tools to your crew works today.
A native CrewAI adapter is on the roadmap.

## Demo

With the repo's demo gateway running (`cd gateway && go run .`):

```bash
pip install -e sdk/python -e sdk/adapters/langchain
python examples/langchain_crm_agent.py
```

## License

Apache 2.0 (the adapter and SDKs). The Sentinel platform is licensed
separately under BSL 1.1 — see the repository root.
