Metadata-Version: 2.4
Name: langchain-nuggets
Version: 1.1.2
Summary: Nuggets authority middleware for LangChain / LangGraph
Project-URL: Homepage, https://nuggets.life
Project-URL: Repository, https://github.com/NuggetsLtd/langchain-nuggets
Project-URL: Changelog, https://github.com/NuggetsLtd/langchain-nuggets/blob/main/CHANGELOG.md
License-Expression: MIT
Keywords: agents,ai,authority,langchain,langgraph,middleware,nuggets,trust
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
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: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: httpx>=0.27.0
Requires-Dist: langchain-core>=0.3.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: pyjwt[crypto]>=2.8.0
Provides-Extra: agent
Requires-Dist: langchain>=1.0; (python_version >= '3.10') and extra == 'agent'
Provides-Extra: dev
Requires-Dist: langgraph-sdk>=0.1.0; extra == 'dev'
Requires-Dist: mypy>=1.13.0; extra == 'dev'
Requires-Dist: pyjwt[crypto]>=2.8.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: respx>=0.22.0; extra == 'dev'
Requires-Dist: ruff>=0.8.0; extra == 'dev'
Provides-Extra: langgraph
Requires-Dist: langgraph-sdk>=0.1.0; extra == 'langgraph'
Description-Content-Type: text/markdown

<p align="center">
  <a href="https://nuggets.life"><img src="https://mintcdn.com/nuggets-b89005a2/jRQbw6WveDYPxE-G/assets/logo-light-bg.svg?fit=max&auto=format&n=jRQbw6WveDYPxE-G&q=85&s=1d4b5047b72eae8cc80de51d2ed68ada" alt="Nuggets" height="56"></a>
</p>

# langchain-nuggets

[![CI](https://github.com/NuggetsLtd/langchain-nuggets/actions/workflows/ci.yml/badge.svg)](https://github.com/NuggetsLtd/langchain-nuggets/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/langchain-nuggets.svg)](https://pypi.org/project/langchain-nuggets/)
[![Python versions](https://img.shields.io/pypi/pyversions/langchain-nuggets.svg)](https://pypi.org/project/langchain-nuggets/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/NuggetsLtd/langchain-nuggets/blob/main/LICENSE)

Authority middleware for LangChain / LangGraph — pre-execution trust enforcement on every tool call.

Wrap any `ToolNode` and the middleware calls the Nuggets authority endpoint before each tool executes. The backend evaluates a scoped delegation, returns an `ALLOW` or `DENY` decision, and signs an audit proof. Tools that aren't allowed never run.

## Why Nuggets Authority?

Most agent middleware shapes *prompts* or guardrails *outputs*. Nuggets Authority governs *actions* — "is this agent allowed to do **this**, right now, on whose authority?" — before a tool runs, and leaves cryptographic proof.

- **Pre-execution enforcement**, not after-the-fact logging — unauthorized calls fail closed and never run.
- **Cryptographic accountability** — every decision is a signed, independently verifiable proof artifact; verification on by default.
- **Scoped, revocable authority** — delegations bound by capability, target, invocation cap, and expiry.
- **Intent binding** — optional `intent_resolver` support adds an `intent_hash` to proofs, so reviewers can distinguish the same action taken for different business intents.
- **Trusted agent identity** — each request signed (RS256) and bound to the agent's DID, ownership verified server-side.
- **Drop-in** for both `ToolNode` and `create_agent`, with no changes to your tools.

Built on [Nuggets](https://nuggets.life), the universal trust infrastructure for autonomous AI. Nuggets governs at the point of execution.

## Installation

```bash
pip install langchain-nuggets
```

For LangGraph Platform OIDC auth:

```bash
pip install langchain-nuggets[langgraph]
```

## Authority Middleware

```python
from langchain_nuggets.middleware import NuggetsAuthorityMiddleware, MiddlewareConfig
from langgraph.prebuilt import ToolNode

config = MiddlewareConfig(
    api_url="https://accounts.nuggets.life",
    oidc_issuer_url="https://auth.nuggets.life",
    agent_id="did:web:auth.nuggets.life:your-agent-id",
    controller_id="did:web:auth.nuggets.life:your-controller-id",
    delegation_id="42",
    agent_private_key="/secrets/agent-jwks.json",
)

middleware = NuggetsAuthorityMiddleware(config)

tool_node = ToolNode(
    tools=your_tools,
    wrap_tool_call=middleware.wrap_tool_call,
)
```

**Execution model:** `Agent → Tool Call → Nuggets Authority Check → Allow/Deny → Emit Proof`

**Trust primitives enforced:** Actor Identity, Authority (delegation), Policy, Intent, Consent, Accountability (provenance).

| Behaviour | Detail |
|-----------|--------|
| **ALLOW** | Tool executes; cryptographic proof artifact emitted |
| **DENY** | Tool blocked; structured error returned with `reason_code` |
| **ESCALATE** | Human approval required; verified `PENDING_APPROVAL` returned, tool **not** executed, no proof artifact (see [Payments & approvals](#payments--approvals)) |
| **ERROR** | Fail closed — tool not executed |
| **Proof binding** | Proofs bind actor, controller, delegation, tool, parameters, result hash, constraints, and optional intent hash |

To provision the agent identity, private key, and delegation referenced above, see [the agent provisioning runbook](https://github.com/NuggetsLtd/langchain-nuggets/blob/main/docs/agent-provisioning.md).

### Proof verification (on by default)

Every `ALLOW` carries a proof signed by the authority, and the SDK **verifies it before the tool runs** — discovering the authority's signing identity from `{api_url}/.well-known/authority-configuration`, pinning the proof's issuer to that authority, verifying the signature against the published JWKS, and binding the proof to the request. Any failure fails **closed**: `DENY` with `reason_code = PROOF_VERIFICATION_FAILED`, tool not run. On by default — no config.

Every decision is therefore **independently verifiable**. A third party can validate an emitted proof out-of-band:

```python
from langchain_nuggets.middleware import verify_authority_proof, discover_authority

issuer, jwks_uri = discover_authority("https://accounts.nuggets.life")
verify_authority_proof(proof_jws, expected={...}, issuer=issuer, jwks_uri=jwks_uri)
```

Opt out only deliberately (e.g. an offline harness verifying proofs separately): `MiddlewareConfig(..., verify_proofs=False)`.

### Intent binding

Set `intent_resolver` when the agent can identify why a tool call is being made. The SDK hashes the intent with the request parameters and timestamp, sends the `intent_hash` to the authority, and includes it in the emitted proof artifact.

```python
MiddlewareConfig(
    ...,
    intent_resolver=lambda tool, args: "KYC lookup for compliance review",
)
```

### Payments & approvals

For monetary tools, supply an **action-context resolver** to attach the payment `amount_minor` (minor units, integer) and `currency` (ISO-4217, uppercase) to the signed action. The resolver is the *only* source of money fields — they are never inferred from tool args — and the tool name must **exactly match** the delegation capability (e.g. `nuggets.payments.send`).

```python
MiddlewareConfig(
    ...,
    action_context_resolver=lambda tool, args: {
        "amount_minor": 500,           # £5.00
        "currency": "GBP",
        "target": "did:web:merchant",  # optional; overrides the args-derived target
    },
)
```

`amount_minor` and `currency` are validated as a pair — supply both or neither. Invalid money fields (negative/non-integer amount, non-`^[A-Z]{3}$` currency, one without the other) fail **closed** with an `ERROR` `ToolMessage` before the tool runs.

**ESCALATE (human approval).** When the authority requires approval it returns `ESCALATE`. The middleware verifies the signed decision — exactly as it does for `ALLOW` — then returns a `PENDING_APPROVAL` `ToolMessage`. This is **not** an error, and the wrapped tool never runs:

```json
{ "status": "PENDING_APPROVAL", "approval_id": 500, "reason_code": "APPROVAL_REQUIRED", "proof_id": "...", "signature": "..." }
```

Operational boundary:

- **No payment handler runs** on `PENDING_APPROVAL` — nothing is executed or charged.
- The **application owns polling/redeem** of the approval, out-of-band, using `approval_id`.
- `approval_id` is a **server-issued handle, not part of the signed receipt** — treat it as an opaque identifier, not a cryptographically verified field. (The ESCALATE *decision* signature is verified.)

### With `create_agent`

For the LangChain `create_agent` API, install the `agent` extra and use the `AgentMiddleware` adapter (same config, same enforcement):

```bash
pip install langchain-nuggets[agent]
```

```python
from langchain.agents import create_agent
from langchain_nuggets.middleware import NuggetsAuthorityAgentMiddleware, MiddlewareConfig

agent = create_agent(
    model="...",
    tools=your_tools,
    middleware=[NuggetsAuthorityAgentMiddleware(MiddlewareConfig(...))],
)
```

### Agent private key

The accounts portal generates an RS256 keypair at agent creation and lets you download the private key as a JWKS file. `MiddlewareConfig.agent_private_key` accepts:

- A filesystem path to a PEM, JWK JSON, or JWKS JSON file
- A raw PEM string
- A JWK or JWKS dict

The key is never transmitted; only the signed `agent_proof` JWS is sent.

Keep the private JWKS in a secret store or mounted secret — never in source control, logs, or `Downloads`; treat any previously downloaded key as stale. For demos and smoke runs, use a **disposable, scoped delegation** and a freshly downloaded key, and **revoke both** afterwards.

### Test mode

`test_mode=True` short-circuits the live auth flow during local development — no HTTP is made and each check returns a synthetic `ALLOW` with a proof artifact flagged as test-mode-unverifiable. Action-context resolution still runs first: a configured `action_context_resolver` that returns invalid money fields (or raises) fails **closed** with an `ERROR` `ToolMessage` before the short-circuit, so validation behaves identically in and out of test mode.

## LangGraph Platform OIDC auth

```python
from langchain_nuggets.langgraph import NuggetsAuth

nuggets = NuggetsAuth(issuer_url="https://auth.nuggets.life")
auth = nuggets.auth  # pass to langgraph.json
```

JWTs are verified with a fixed `RS256` allowlist (never the token header's `alg`), and JWKS keys are filtered by `kty`/`use`/`alg`. If you pass `audience=`, it is enforced per RFC 9068.

> **Audience enforcement is not yet mandatory.** Making a configured `audience` **required** (failing closed when unset) is deferred until the Nuggets issuer defines a LangGraph resource-server `aud` — there is no concrete `audience=` value to set today (tracked in [#63](https://github.com/NuggetsLtd/langchain-nuggets/issues/63)). When it lands, set `audience=` to your deployment's resource URI; do **not** use the authority API resource (`…/api/authority`), which is a different resource.

Pre-built authorization helpers:

```python
from langchain_nuggets.langgraph import require_scopes, ownership_filter

# Owner-scope every operation. ownership_filter stamps value["metadata"]["owner"]
# on writes and returns an {"owner": identity} filter for reads/searches; it
# fails closed (403) when there is no authenticated identity.
owned = ownership_filter()
for op in (
    auth.on.threads.create, auth.on.threads.read, auth.on.threads.update,
    auth.on.threads.delete, auth.on.threads.search,
):
    op(owned)
```

Register it for **every** operation you want scoped — a create handler alone stamps the owner but leaves reads/updates/deletes unfiltered.

## Self-hosted / private CA

Point the URLs at your own deployment and pass `ca_cert` to either constructor:

```python
MiddlewareConfig(
    api_url="https://nuggets.internal.example.com",
    oidc_issuer_url="https://oidc.internal.example.com",
    # ...
    ca_cert="/etc/ssl/private-ca/nuggets-ca.pem",
)
```

Set `verify_ssl=False` to disable TLS verification (development only).

## About Nuggets

Nuggets is the universal trust infrastructure for autonomous AI. Nuggets governs at the point of execution. Learn more at [nuggets.life](https://nuggets.life).

## License

MIT

## Trademarks

`langchain-nuggets` is an independent, community-maintained integration and is not affiliated with, sponsored by, or endorsed by LangChain, Inc. "LangChain" and "LangGraph" are trademarks of LangChain, Inc. All other trademarks are the property of their respective owners.
