Skip to content

Commit e1a65b3

Browse files
committed
feat: add llama-index-callbacks-hdp and hdp-llamaindex packages
Three-layer integration providing cryptographic authorization provenance for LlamaIndex agents and RAG pipelines: - Layer 1: HdpInstrumentationHandler — modern dispatcher integration (LlamaIndex >=0.10.20), hooks QueryStartEvent, AgentToolCallEvent, LLMChatStartEvent, QueryEndEvent via root dispatcher - Layer 2: HdpCallbackHandler — legacy CallbackManager integration, hooks start_trace, end_trace, on_event_start/end for all FUNCTION_CALL and LLM events - Layer 3: HdpNodePostprocessor — inline retrieval hop recording and data classification enforcement in the RAG pipeline Shared ContextVar session state isolates tokens across concurrent async tasks. All crypto primitives bundled inline (Ed25519 + RFC 8785 JCS), consistent with all other HDP Python packages. hdp-llamaindex is a thin metapackage re-exporting from the llama_index namespace for users who discover HDP first. 43 tests passing across session isolation, chain verification, callback handler, and postprocessor.
1 parent 7a3af7f commit e1a65b3

18 files changed

Lines changed: 1945 additions & 0 deletions

File tree

packages/hdp-llamaindex/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# hdp-llamaindex
2+
3+
Metapackage for users who discover HDP first.
4+
5+
```bash
6+
pip install hdp-llamaindex
7+
```
8+
9+
This installs `llama-index-callbacks-hdp` and re-exports all classes from the `hdp_llamaindex` namespace.
10+
11+
For full documentation see [llama-index-callbacks-hdp](../llama-index-callbacks-hdp/README.md).
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "hdp-llamaindex"
7+
version = "0.1.0"
8+
description = "HDP (Human Delegation Provenance) integration for LlamaIndex — metapackage"
9+
readme = "README.md"
10+
license = { text = "CC-BY-4.0" }
11+
requires-python = ">=3.10"
12+
dependencies = [
13+
"llama-index-callbacks-hdp>=0.1.0",
14+
]
15+
16+
[project.urls]
17+
Homepage = "https://github.com/Helixar-AI/HDP"
18+
Repository = "https://github.com/Helixar-AI/HDP"
19+
20+
[tool.hatch.build.targets.wheel]
21+
packages = ["src/hdp_llamaindex"]
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""hdp-llamaindex — convenience re-export of the HDP LlamaIndex integration.
2+
3+
Install via `pip install hdp-llamaindex` if you discover HDP first.
4+
All classes are importable from here or from `llama_index.callbacks.hdp`.
5+
"""
6+
7+
from llama_index.callbacks.hdp import (
8+
DataClassification,
9+
HdpCallbackHandler,
10+
HdpInstrumentationHandler,
11+
HdpNodePostprocessor,
12+
HdpPrincipal,
13+
HdpScope,
14+
HdpToken,
15+
HDPScopeViolationError,
16+
HopRecord,
17+
HopVerification,
18+
ScopePolicy,
19+
VerificationResult,
20+
clear_token,
21+
get_token,
22+
set_token,
23+
verify_chain,
24+
)
25+
26+
__all__ = [
27+
"DataClassification",
28+
"HdpCallbackHandler",
29+
"HdpInstrumentationHandler",
30+
"HdpNodePostprocessor",
31+
"HdpPrincipal",
32+
"HdpScope",
33+
"HdpToken",
34+
"HDPScopeViolationError",
35+
"HopRecord",
36+
"HopVerification",
37+
"ScopePolicy",
38+
"VerificationResult",
39+
"clear_token",
40+
"get_token",
41+
"set_token",
42+
"verify_chain",
43+
]
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# llama-index-callbacks-hdp
2+
3+
HDP (Human Delegation Provenance) integration for LlamaIndex — cryptographic authorization provenance for agents and RAG pipelines.
4+
5+
HDP answers the question that observability tools like Arize Phoenix and Langfuse cannot: **who authorized this agent run, under what scope, and can you prove it offline?**
6+
7+
Every tool call, retrieval step, and LLM invocation is recorded in a tamper-evident, cryptographically signed delegation chain. The chain is fully verifiable offline — no network calls, no central registry.
8+
9+
## Installation
10+
11+
```bash
12+
pip install llama-index-callbacks-hdp
13+
```
14+
15+
## Usage
16+
17+
### Option 1 — Modern instrumentation dispatcher (LlamaIndex ≥0.10.20)
18+
19+
```python
20+
from llama_index.callbacks.hdp import HdpInstrumentationHandler, HdpPrincipal, ScopePolicy
21+
22+
HdpInstrumentationHandler.init(
23+
signing_key=ed25519_private_key_bytes,
24+
principal=HdpPrincipal(id="alice@corp.com", id_type="email"),
25+
scope=ScopePolicy(
26+
intent="Research pipeline",
27+
authorized_tools=["web_search", "retriever"],
28+
max_hops=10,
29+
),
30+
on_token_ready=lambda token: print(token["header"]["token_id"]),
31+
)
32+
```
33+
34+
### Option 2 — Legacy CallbackManager
35+
36+
```python
37+
from llama_index.callbacks.hdp import HdpCallbackHandler, HdpPrincipal, ScopePolicy
38+
from llama_index.core import Settings
39+
from llama_index.core.callbacks import CallbackManager
40+
41+
handler = HdpCallbackHandler(
42+
signing_key=ed25519_private_key_bytes,
43+
principal=HdpPrincipal(id="alice@corp.com", id_type="email"),
44+
scope=ScopePolicy(intent="Research pipeline"),
45+
)
46+
Settings.callback_manager = CallbackManager([handler])
47+
```
48+
49+
### Option 3 — Node postprocessor (inline retrieval enforcement)
50+
51+
```python
52+
from llama_index.callbacks.hdp import HdpNodePostprocessor
53+
54+
postprocessor = HdpNodePostprocessor(
55+
signing_key=ed25519_private_key_bytes,
56+
strict=False,
57+
check_data_classification=True,
58+
)
59+
query_engine = index.as_query_engine(node_postprocessors=[postprocessor])
60+
```
61+
62+
### Verifying a token
63+
64+
```python
65+
from llama_index.callbacks.hdp import verify_chain
66+
67+
result = verify_chain(token_dict, public_key_bytes)
68+
if result.valid:
69+
print(f"Chain verified: {result.hop_count} hops")
70+
```
71+
72+
## What makes HDP different from Arize/Langfuse?
73+
74+
| Capability | Arize / Langfuse | HDP |
75+
|---|---|---|
76+
| Records what happened |||
77+
| Records who authorized it |||
78+
| Cryptographically signed |||
79+
| Verifiable offline |||
80+
| Scope enforcement |||
81+
| No central registry | n/a ||
82+
83+
## License
84+
85+
CC-BY-4.0
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""HDP integration for LlamaIndex — cryptographic authorization provenance."""
2+
3+
from ._types import DataClassification, HdpPrincipal, HdpScope, HdpToken, HopRecord
4+
from .callbacks import HdpCallbackHandler, HDPScopeViolationError, ScopePolicy
5+
from .instrumentation import HdpInstrumentationHandler
6+
from .postprocessor import HdpNodePostprocessor
7+
from .session import clear_token, get_token, set_token
8+
from .verify import HopVerification, VerificationResult, verify_chain
9+
10+
__all__ = [
11+
# Core types
12+
"DataClassification",
13+
"HdpPrincipal",
14+
"HdpScope",
15+
"HdpToken",
16+
"HopRecord",
17+
# Policy
18+
"ScopePolicy",
19+
"HDPScopeViolationError",
20+
# Integration layers
21+
"HdpCallbackHandler",
22+
"HdpInstrumentationHandler",
23+
"HdpNodePostprocessor",
24+
# Session
25+
"get_token",
26+
"set_token",
27+
"clear_token",
28+
# Verification
29+
"verify_chain",
30+
"VerificationResult",
31+
"HopVerification",
32+
]
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""Cryptographic primitives for HDP — Ed25519 signing/verification with RFC 8785 canonical JSON.
2+
3+
Matches the signing scheme in the TypeScript SDK (src/crypto/sign.ts + src/crypto/verify.ts):
4+
- Root: canonicalize({header, principal, scope}) → Ed25519 → base64url
5+
- Hop: canonicalize({chain: [...], root_sig: <value>}) → Ed25519 → base64url
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import base64
11+
from typing import Any
12+
13+
import jcs
14+
from cryptography.exceptions import InvalidSignature
15+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
16+
17+
18+
def _b64url(sig_bytes: bytes) -> str:
19+
return base64.urlsafe_b64encode(sig_bytes).rstrip(b"=").decode()
20+
21+
22+
def _canonicalize(obj: Any) -> bytes:
23+
return jcs.canonicalize(obj)
24+
25+
26+
def sign_root(unsigned_token: dict, private_key_bytes: bytes, kid: str) -> dict:
27+
subset = {f: unsigned_token[f] for f in ["header", "principal", "scope"] if f in unsigned_token}
28+
message = _canonicalize(subset)
29+
key = Ed25519PrivateKey.from_private_bytes(private_key_bytes)
30+
sig_bytes = key.sign(message)
31+
return {
32+
"alg": "Ed25519",
33+
"kid": kid,
34+
"value": _b64url(sig_bytes),
35+
"signed_fields": ["header", "principal", "scope"],
36+
}
37+
38+
39+
def sign_hop(cumulative_chain: list[dict], root_sig_value: str, private_key_bytes: bytes) -> str:
40+
payload = {"chain": cumulative_chain, "root_sig": root_sig_value}
41+
message = _canonicalize(payload)
42+
key = Ed25519PrivateKey.from_private_bytes(private_key_bytes)
43+
sig_bytes = key.sign(message)
44+
return _b64url(sig_bytes)
45+
46+
47+
def _b64url_decode(s: str) -> bytes:
48+
padding = 4 - len(s) % 4
49+
return base64.urlsafe_b64decode(s + "=" * padding)
50+
51+
52+
def verify_root(token: dict, public_key: Ed25519PublicKey) -> bool:
53+
try:
54+
subset = {f: token[f] for f in ["header", "principal", "scope"] if f in token}
55+
message = _canonicalize(subset)
56+
sig_bytes = _b64url_decode(token["signature"]["value"])
57+
public_key.verify(sig_bytes, message)
58+
return True
59+
except (InvalidSignature, KeyError, Exception):
60+
return False
61+
62+
63+
def verify_hop(cumulative_chain: list[dict], root_sig_value: str, hop_signature: str, public_key: Ed25519PublicKey) -> bool:
64+
try:
65+
payload = {"chain": cumulative_chain, "root_sig": root_sig_value}
66+
message = _canonicalize(payload)
67+
sig_bytes = _b64url_decode(hop_signature)
68+
public_key.verify(sig_bytes, message)
69+
return True
70+
except (InvalidSignature, Exception):
71+
return False
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""Python types mirroring the HDP TypeScript SDK schema."""
2+
3+
from __future__ import annotations
4+
5+
from dataclasses import dataclass, field
6+
from typing import Any, Literal, Optional
7+
8+
DataClassification = Literal["public", "internal", "confidential", "restricted"]
9+
AgentType = Literal["orchestrator", "sub-agent", "tool-executor", "custom"]
10+
PrincipalIdType = Literal["email", "uuid", "did", "poh", "opaque"]
11+
12+
13+
@dataclass
14+
class HdpHeader:
15+
token_id: str
16+
issued_at: int
17+
expires_at: int
18+
session_id: str
19+
version: str = "0.1"
20+
parent_token_id: Optional[str] = None
21+
22+
23+
@dataclass
24+
class HdpPrincipal:
25+
id: str
26+
id_type: PrincipalIdType
27+
display_name: Optional[str] = None
28+
metadata: Optional[dict[str, Any]] = None
29+
30+
31+
@dataclass
32+
class HdpScope:
33+
intent: str
34+
data_classification: DataClassification
35+
network_egress: bool
36+
persistence: bool
37+
authorized_tools: Optional[list[str]] = None
38+
authorized_resources: Optional[list[str]] = None
39+
max_hops: Optional[int] = None
40+
41+
42+
@dataclass
43+
class HdpSignature:
44+
alg: str
45+
kid: str
46+
value: str
47+
signed_fields: list[str] = field(default_factory=lambda: ["header", "principal", "scope"])
48+
49+
50+
@dataclass
51+
class HopRecord:
52+
seq: int
53+
agent_id: str
54+
agent_type: AgentType
55+
timestamp: int
56+
action_summary: str
57+
parent_hop: int
58+
hop_signature: str
59+
agent_fingerprint: Optional[str] = None
60+
61+
62+
@dataclass
63+
class HdpToken:
64+
hdp: str
65+
header: HdpHeader
66+
principal: HdpPrincipal
67+
scope: HdpScope
68+
chain: list[HopRecord]
69+
signature: HdpSignature

0 commit comments

Comments
 (0)