Skip to content

Commit 501476d

Browse files
Merge pull request #10 from sidnz/feature/hdp-langchain
feat: add hdp-langchain - HDP middleware for LangChain and LangGraph
2 parents 7a3af7f + e0edf59 commit 501476d

15 files changed

Lines changed: 1900 additions & 94 deletions

File tree

.github/workflows/ci.yml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: CI
22

33
on:
44
push:
5-
branches: [main, 'implement/**']
5+
branches: [main, "implement/**"]
66
pull_request:
77
branches: [main]
88

@@ -15,15 +15,15 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
node-version: ['20', '22']
18+
node-version: ["20", "22"]
1919
steps:
2020
- uses: actions/checkout@v5
2121

2222
- name: Set up Node.js ${{ matrix.node-version }}
2323
uses: actions/setup-node@v4
2424
with:
2525
node-version: ${{ matrix.node-version }}
26-
cache: 'npm'
26+
cache: "npm"
2727

2828
- name: Install dependencies
2929
run: npm ci
@@ -42,7 +42,7 @@ jobs:
4242
runs-on: ubuntu-latest
4343
strategy:
4444
matrix:
45-
python-version: ['3.10', '3.11', '3.12']
45+
python-version: ["3.10", "3.11", "3.12"]
4646
steps:
4747
- uses: actions/checkout@v5
4848

@@ -64,7 +64,7 @@ jobs:
6464
runs-on: ubuntu-latest
6565
strategy:
6666
matrix:
67-
python-version: ['3.10', '3.11', '3.12']
67+
python-version: ["3.10", "3.11", "3.12"]
6868
steps:
6969
- uses: actions/checkout@v5
7070

@@ -113,7 +113,7 @@ jobs:
113113
runs-on: ubuntu-latest
114114
strategy:
115115
matrix:
116-
python-version: ['3.10', '3.11', '3.12']
116+
python-version: ["3.10", "3.11", "3.12"]
117117
steps:
118118
- uses: actions/checkout@v5
119119

@@ -130,6 +130,12 @@ jobs:
130130
working-directory: packages/hdp-autogen
131131
run: pytest tests/ -v
132132

133+
test-hdp-langchain:
134+
name: Test hdp-langchain (Python ${{ matrix.python-version }})
135+
runs-on: ubuntu-latest
136+
strategy:
137+
matrix:
138+
python-version: ["3.10", "3.11", "3.12"]
133139
test-hdp-physical-py:
134140
name: Test hdp-physical-py (Python ${{ matrix.python-version }})
135141
runs-on: ubuntu-latest
@@ -147,6 +153,11 @@ jobs:
147153
python-version: ${{ matrix.python-version }}
148154

149155
- name: Install dependencies
156+
working-directory: packages/hdp-langchain
157+
run: pip install -e ".[dev]"
158+
159+
- name: Run tests
160+
working-directory: packages/hdp-langchain
150161
working-directory: packages/hdp-physical-py
151162
run: pip install -e ".[dev]"
152163

README.md

Lines changed: 112 additions & 88 deletions
Large diffs are not rendered by default.

packages/hdp-langchain/.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
__pycache__/
2+
*.py[cod]
3+
*.egg-info/
4+
dist/
5+
build/
6+
.eggs/
7+
*.egg
8+
.venv/
9+
venv/
10+
.pytest_cache/
11+
.coverage
12+
htmlcov/
13+
.mypy_cache/
14+
.releaseguard/
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# ReleaseGuard policy for hdp-langchain Python artifacts
2+
# https://github.com/Helixar-AI/ReleaseGuard
3+
#
4+
# Scans the built wheel and sdist before they are published to PyPI.
5+
# Run locally: releaseguard check ./dist
6+
version: 2
7+
8+
project:
9+
name: hdp-langchain
10+
mode: release
11+
12+
inputs:
13+
- path: ./dist
14+
type: directory
15+
16+
sbom:
17+
enabled: true
18+
ecosystems: [python]
19+
formats: [cyclonedx]
20+
enrich_cve: false
21+
22+
scanning:
23+
secrets:
24+
enabled: true
25+
metadata:
26+
enabled: true
27+
fail_on_source_maps: false
28+
fail_on_internal_urls: false
29+
fail_on_build_paths: false
30+
unexpected_files:
31+
enabled: true
32+
deny:
33+
- ".env"
34+
- "*.bak"
35+
- "*.tmp"
36+
- "*.key"
37+
- "*.pem"
38+
licenses:
39+
enabled: true
40+
require:
41+
- LICENSE
42+
43+
transforms:
44+
add_checksums: false # PyPI rejects non-dist files — checksums are handled by PyPI itself
45+
add_manifest: false
46+
47+
policy:
48+
fail_on:
49+
- severity: critical
50+
- category: secret
51+
warn_on:
52+
- severity: high
53+
54+
output:
55+
reports:
56+
- cli
57+
- sarif
58+
directory: ./.releaseguard

packages/hdp-langchain/README.md

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# hdp-langchain
2+
3+
**HDP (Human Delegation Provenance) middleware for LangChain** — attach a cryptographic audit trail to any chain, agent, or tool with a single callback handler.
4+
5+
Every tool call in a LangChain agent is recorded in a tamper-evident chain of Ed25519 signatures, verifiable offline with a single public key.
6+
7+
```
8+
pip install hdp-langchain
9+
```
10+
11+
---
12+
13+
## Quick start — LangChain
14+
15+
```python
16+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
17+
from langchain_core.tools import tool
18+
from langchain.agents import AgentExecutor, create_react_agent
19+
from hdp_langchain import HdpMiddleware, HdpPrincipal, ScopePolicy, verify_chain
20+
21+
# 1. Your signing key (store in a secrets manager, never in code)
22+
private_key = Ed25519PrivateKey.generate()
23+
24+
# 2. Define what the human is authorising
25+
scope = ScopePolicy(
26+
intent="Research agent to summarise recent papers",
27+
authorized_tools=["web_search", "file_reader"],
28+
max_hops=10,
29+
)
30+
31+
# 3. Create the middleware
32+
middleware = HdpMiddleware(
33+
signing_key=private_key.private_bytes_raw(),
34+
session_id="research-2026-q1",
35+
principal=HdpPrincipal(id="researcher@lab.edu", id_type="email"),
36+
scope=scope,
37+
)
38+
39+
# 4. Build your agent as normal
40+
agent_executor = AgentExecutor(agent=..., tools=[...])
41+
42+
# 5. Attach HDP — one line, zero agent changes
43+
handler = middleware.get_callback_handler()
44+
agent_executor.invoke(
45+
{"input": "Summarise recent LLM papers"},
46+
config={"callbacks": [handler]},
47+
)
48+
49+
# 6. Verify the delegation chain offline
50+
result = verify_chain(middleware.export_token(), private_key.public_key())
51+
print(result.valid, result.hop_count, result.violations)
52+
```
53+
54+
---
55+
56+
## Quick start — LangGraph
57+
58+
```python
59+
from langgraph.graph import StateGraph, END
60+
from hdp_langchain import HdpMiddleware, HdpPrincipal, ScopePolicy, verify_chain
61+
from hdp_langchain.graph import hdp_node
62+
63+
middleware = HdpMiddleware(
64+
signing_key=private_key.private_bytes_raw(),
65+
session_id="graph-session-1",
66+
principal=HdpPrincipal(id="user@example.com", id_type="email"),
67+
scope=ScopePolicy(intent="Multi-node research pipeline"),
68+
)
69+
70+
# Wrap node functions — each execution records a delegation hop
71+
@hdp_node(middleware, agent_id="planner")
72+
def planner_node(state):
73+
return {**state, "plan": "step 1, step 2"}
74+
75+
@hdp_node(middleware, agent_id="executor")
76+
def executor_node(state):
77+
return {**state, "result": "done"}
78+
79+
# Build and run the graph
80+
graph = StateGraph(dict)
81+
graph.add_node("planner", planner_node)
82+
graph.add_node("executor", executor_node)
83+
graph.add_edge("planner", "executor")
84+
graph.add_edge("executor", END)
85+
graph.set_entry_point("planner")
86+
87+
app = graph.compile()
88+
app.invoke({})
89+
90+
# Verify the full delegation chain
91+
result = verify_chain(middleware.export_token(), private_key.public_key())
92+
print(result.valid, result.hop_count) # True, 2
93+
```
94+
95+
---
96+
97+
## Five design considerations
98+
99+
| # | Consideration | How it's handled |
100+
|---|---|---|
101+
| **1** | **Scope enforcement** | Tool calls are checked against `authorized_tools` in `on_tool_start`. Default: logs + records violation in token. `strict=True`: raises `HDPScopeViolationError`. |
102+
| **2** | **Delegation depth** | `ScopePolicy(max_hops=N)` enforced per run; hops beyond the limit are skipped and logged. |
103+
| **3** | **Token size / performance** | Ed25519 signatures are 64 bytes each (~2.6 KB for a 10-hop run). All HDP operations are non-blocking — failures log as warnings, execution always continues. |
104+
| **4** | **Verification** | `verify_chain(token, public_key)` validates root + every hop signature offline. Returns `VerificationResult` with `valid`, `hop_count`, `violations`, and per-hop outcomes. |
105+
| **5** | **Callback integration** | `get_callback_handler()` returns an `HdpCallbackHandler` compatible with LangChain's `RunnableConfig`. For LangGraph, use `hdp_node()` to wrap node functions. |
106+
107+
---
108+
109+
## API reference
110+
111+
### `HdpMiddleware`
112+
113+
```python
114+
HdpMiddleware(
115+
signing_key: bytes, # Ed25519 private key (raw 32 bytes)
116+
session_id: str, # unique ID for this run
117+
principal: HdpPrincipal, # the human delegating authority
118+
scope: ScopePolicy, # what is authorised
119+
key_id: str = "default", # label stored in the token header
120+
expires_in_ms: int = 86400000,
121+
strict: bool = False, # True → raise on scope violations
122+
)
123+
```
124+
125+
| Method | Description |
126+
|---|---|
127+
| `get_callback_handler()` | Return an `HdpCallbackHandler` for use with LangChain's `RunnableConfig` |
128+
| `export_token()` | Return the token dict (or `None` before first run) |
129+
| `export_token_json()` | Return the token as a JSON string |
130+
131+
### `HdpCallbackHandler`
132+
133+
A `langchain_core.callbacks.BaseCallbackHandler` subclass. Attach via `RunnableConfig`:
134+
135+
```python
136+
config = {"callbacks": [middleware.get_callback_handler()]}
137+
chain.invoke(input, config=config)
138+
```
139+
140+
### `hdp_node(middleware, node_fn=None, *, agent_id=None)`
141+
142+
Wraps a LangGraph node function to record a delegation hop on each invocation:
143+
144+
```python
145+
@hdp_node(middleware, agent_id="researcher")
146+
def researcher_node(state):
147+
...
148+
return state
149+
```
150+
151+
### `verify_chain(token, public_key)`
152+
153+
```python
154+
result = verify_chain(token_dict, public_key) # Ed25519PublicKey or raw bytes
155+
result.valid # bool
156+
result.hop_count # int
157+
result.violations # list[str]
158+
result.hop_results # list[HopVerification]
159+
```
160+
161+
### `ScopePolicy`
162+
163+
```python
164+
ScopePolicy(
165+
intent: str,
166+
data_classification: str = "internal", # "public" | "internal" | "confidential" | "restricted"
167+
network_egress: bool = True,
168+
persistence: bool = False,
169+
authorized_tools: list[str] | None = None,
170+
authorized_resources: list[str] | None = None,
171+
max_hops: int | None = None,
172+
)
173+
```
174+
175+
---
176+
177+
## Error handling
178+
179+
By default, HDP middleware is **non-blocking** — signing or scope-check failures are logged as warnings and execution continues normally. Violations are recorded in the token for post-hoc audit.
180+
181+
```python
182+
# Default (non-blocking): violations are logged, execution keeps running
183+
middleware = HdpMiddleware(
184+
signing_key=key, session_id="s1",
185+
principal=HdpPrincipal(id="alice", id_type="handle"),
186+
scope=ScopePolicy(intent="research", authorized_tools=["web_search"]),
187+
)
188+
handler = middleware.get_callback_handler()
189+
# If the agent calls an unauthorised tool (e.g. "execute_code"):
190+
# → WARNING is logged, violation attached to the token
191+
# → execution is NOT interrupted
192+
193+
# Strict mode: violations raise immediately
194+
middleware_strict = HdpMiddleware(
195+
...,
196+
strict=True,
197+
)
198+
# If the agent calls "execute_code" → raises HDPScopeViolationError
199+
```
200+
201+
---
202+
203+
## Cross-language compatibility
204+
205+
Python and TypeScript HDP tokens use the same wire format (RFC 8785 canonical JSON + Ed25519). A token issued by `hdp-langchain` (Python) can be verified by `@helixar_ai/hdp` (TypeScript) and vice versa.
206+
207+
```python
208+
# Python: export token
209+
token_json = middleware.export_token_json()
210+
# → pass to TypeScript service via API, message queue, etc.
211+
```
212+
213+
```typescript
214+
// TypeScript: verify a token issued by Python
215+
import { verifyChain } from "@helixar_ai/hdp";
216+
const result = verifyChain(JSON.parse(tokenJson), publicKey);
217+
```
218+
219+
---
220+
221+
## Spec
222+
223+
Human Delegation Provenance (HDP) is an IETF draft:
224+
[draft-helixar-hdp-agentic-delegation](https://datatracker.ietf.org/doc/draft-helixar-hdp-agentic-delegation/)
225+
226+
## License
227+
228+
[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) — Helixar Limited

0 commit comments

Comments
 (0)