A cryptographic chain-of-custody protocol for agentic AI systems. Signed delegation context and agent activity, preserved for audit.
HDP (Human Delegation Provenance) is an open protocol that captures, structures, cryptographically signs, and verifies records of human delegation context in agentic AI systems.
When a person delegates a task to an AI agent, and that agent delegates to another agent, HDP creates a tamper-evident chain from the issuer's signed statement to the activity each hop records. The full trail is encoded in a compact, self-contained token signed with Ed25519 and canonicalized with RFC 8785. Integrity verification is fully offline and uses the issuer's public key; it does not depend on the current time, a session, or verifier state.
Who it is for: developers building AI agents with Grok/xAI, CrewAI, MCP servers, or any OpenAI-compatible API who need accountable, auditable records of delegation context and subsequent agent activity.
Boundary: HDP is not an authorization protocol, capability, access token, or credential. A valid token proves that its signed record is authentic and intact; it does not grant access, prove that an action occurred, or show that a named delegate consented. Services must make authorization decisions using their own access-control system.
Standardization: HDP is specified in the IETF individual Internet-Draft draft-helixar-hdp-agentic-delegation (Informational). This implementation follows draft-helixar-hdp-agentic-delegation-03. Draft -03 defines HDP as record-only: verification establishes record integrity, while each service makes action decisions through its own access control system. The v0.1 token structure and signature payloads are unchanged.
→ Protocol boundaries and audit semantics
Draft -03 record-only semantics: Use the standard HTTP field names
HDP-TokenandHDP-Token-Ref. Maintained middleware accepts the formerX-HDP-*names as deprecated input aliases during migration. Missing or invalid HDP input is an audit finding and does not decide whether a handler runs. Earlier SDK releases signed different, non-interoperable root and hop payload shapes; tokens they emitted must be reissued because corrected implementations do not silently fall back to the earlier signature scheme.
| Package | Registry | Language | Framework | Description |
|---|---|---|---|---|
@helixar_ai/hdp |
npm | TypeScript | Any | Core SDK — issue, extend, verify HDP tokens |
@helixar_ai/hdp-mcp |
npm | TypeScript | MCP | MCP middleware — attaches HDP to any MCP server |
@helixar_ai/hdp-physical |
npm | TypeScript | Physical AI / Robotics | HDP-P guardrails — signs EDTs and blocks unsafe robot actions pre-execution |
hdp-physical |
PyPI | Python | Physical AI / Robotics | HDP-P guardrails — Python SDK for EDT issuance and pre-execution checks |
hdp-crewai |
PyPI | Python | CrewAI | CrewAI middleware — attaches HDP to any crew |
hdp-grok |
PyPI | Python | Grok / xAI | Grok middleware — attaches HDP to any xAI conversation |
hdp-autogen |
PyPI | Python | AutoGen | AutoGen middleware — attaches HDP to any AutoGen agent or GroupChat |
hdp-agent-framework |
PyPI | Python | Microsoft agent-framework | agent-framework middleware — attaches HDP to any Agent or workflow |
@helixar_ai/hdp-autogen |
npm | TypeScript | AutoGen | AutoGen middleware — HdpAgentWrapper + hdpMiddleware for AutoGen flows |
hdp-langchain |
PyPI | Python | LangChain / LangGraph | LangChain middleware — attaches HDP to any chain, agent, or LangGraph node |
llama-index-callbacks-hdp |
PyPI | Python | LlamaIndex | LlamaIndex integration — callback handler, instrumentation dispatcher, node postprocessor |
hdp-llamaindex |
PyPI | Python | LlamaIndex | Metapackage. Install with pip install hdp-llamaindex. |
TypeScript / Node.js
npm install @helixar_ai/hdpTypeScript / Physical AI
npm install @helixar_ai/hdp-physicalPython / CrewAI
pip install hdp-crewaiPython / Physical AI
pip install hdp-physicalPython / Grok (xAI API)
pip install hdp-grok openaiPython / AutoGen
pip install hdp-autogenPython / Microsoft agent-framework
pip install hdp-agent-framework agent-framework-foundry azure-identity
# Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL for the deployment.Python / LangChain
pip install hdp-langchainPython / LlamaIndex
pip install llama-index-callbacks-hdp
# or, from the HDP side:
pip install hdp-llamaindexHDP 0.2.0 follows draft -03. The token wire format remains HDP v0.1.
In 0.2.0, VerificationOptions contains only publicKey.
| 0.1.x | 0.2.0 | Note |
|---|---|---|
VerificationOptions.currentSessionId |
AuditOptions.sessionId in auditToken() |
verifyToken() checks integrity only. auditToken() reports session match status. |
VerificationOptions.pohVerifier |
AuditOptions.pohVerifier in auditToken() |
The callback receives (credential, token) and may return boolean or Promise<boolean>. |
VerificationResult { valid: boolean; error?: HdpError } |
VerificationResult union: { valid: true } or { valid: false; failedStep: IntegrityStep; error: HdpError } |
verifyToken() identifies the first failed integrity step. |
verifyPrincipalChain(chain, opts: Omit<VerificationOptions, 'publicKey'>) |
verifyPrincipalChain(chain, opts?: PrincipalChainVerificationOptions) |
The result adds `relationship: 'joint_approval' |
issueReAuthToken(opts: ReAuthOptions) |
issueSupersedingToken(opts: SupersedingTokenOptions) |
issueReAuthToken() remains as a deprecated alias. |
storeToken(store, token) |
storeToken(store, token, reference?) |
Without reference, both the token ID and content reference are stored. |
resolveToken(store, tokenId) |
resolveToken(store, reference) |
Accepts a token ID or a sha256: content reference and checks the resolved token. |
HDP_HEADER = 'X-HDP-Token'; HDP_REF_HEADER = 'X-HDP-Token-Ref' |
HDP_HEADER = 'HDP-Token'; HDP_REF_HEADER = 'HDP-Token-Ref' |
HDP_LEGACY_HEADER and HDP_LEGACY_REF_HEADER retain the X-prefixed inbound names. |
verifyToken() returns HdpTokenExpiredError or HdpSessionMismatchError in VerificationResult.error |
auditToken() reports AuditReport.recordingPeriod and AuditReport.session |
The error classes remain exported for compatibility. Expiry and session mismatch do not invalidate record integrity. |
hdp-validate <token.json> exits 1 for an expired token |
hdp-validate <token.json> exits 0 for a structurally valid expired token |
The command reports the elapsed authorization period and late hops in notes. |
HdpMiddlewareOptions.hdp_required: true on hdpMiddleware() |
Construction error; HdpMiddlewareOptions.onMissing and HdpMiddlewareOptions.onInvalid report findings |
The middleware runs in observe mode and continues to the handler. |
HdpAgentOptions.strict: true on HdpAgentWrapper |
Construction error; HdpAgentOptions.onScopeViolation reports findings |
HdpScopeViolationError remains exported for compatibility and is not thrown. |
Python HdpMiddleware(strict=True) in hdp-crewai, hdp-autogen, hdp-langchain, and hdp-agent-framework; HdpCallbackHandler(strict=True) and HdpNodePostprocessor(strict=True) in llama-index-callbacks-hdp |
ValueError during construction when strict=True |
Omit strict; the adapters record findings and continue. HDPScopeViolationError remains importable and is not raised. |
HdpInstrumentationHandler.init(on_violation="raise") |
ValueError during initialization |
on_violation="log" records findings and continues. |
Python verify_chain() returns valid: false and an expiry violation for expired tokens |
VerificationResult.recorded_after_period |
VerificationResult.valid reports integrity only; recorded_after_period lists late hop sequence numbers. |
hdp-grok HdpMiddleware.extend_chain() raises HdpTokenExpiredError; HdpMiddleware.verify_token() returns expired |
HdpMiddleware.extend_chain() records the hop; HdpMiddleware.verify_token() reports recorded_after_period |
The expired result field remains available. HdpTokenExpiredError remains importable and is not raised. |
VerificationOptions.now was removed with no replacement because draft Section 1.1 defines HDP as a record, and HDP does not decide acceptance.
Unknown top-level token members are rejected by validateToken() and verifyToken(). Supported top-level members are hdp, header, principal, scope, chain, and signature.
Code written against unreleased main-branch versions between 0.1.3 and 0.2.0 may also use interim names such as HistoricalAuditOptions, HistoricalAuditReport, currentAcceptance, historicalAcceptance, revokedTokenIds, expectedPresenterAgentId, and joint_authorization; HistoricalAuditOptions and HistoricalAuditReport map to AuditOptions and AuditReport, joint_authorization maps to joint_approval, and acceptance and revocation fields are removed.
import { auditToken, generateKeyPair, issueToken, verifyToken } from "@helixar_ai/hdp";
async function main() {
const sessionId = "upgrade-example";
const { privateKey, publicKey } = await generateKeyPair();
const token = await issueToken({
sessionId,
principal: { id: "user-1", id_type: "opaque" },
scope: { intent: "review", data_classification: "internal", network_egress: false, persistence: false },
signingKey: privateKey,
keyId: sessionId,
});
// 0.1.3:
// const verification = await verifyToken(token, { publicKey, currentSessionId: sessionId });
// 0.2.0:
const verification = await verifyToken(token, { publicKey });
const report = await auditToken(token, { publicKey, sessionId });
console.log(verification.valid, report.integrity.status, report.session.status);
}
void main();Issue a root token, extend it through a delegation chain, verify it offline. Under 2 minutes.
import {
generateKeyPair,
issueToken,
extendChain,
verifyToken,
} from "@helixar_ai/hdp";
// 1. Generate a key pair for the issuer
const { privateKey, publicKey } = await generateKeyPair();
// 2. Issue a token (the issuer's signed record of the delegation context)
let token = await issueToken({
sessionId: "sess-20260326-abc123",
principal: {
id: "usr_alice_opaque",
id_type: "opaque",
display_name: "Alice Chen",
},
scope: {
intent: "Analyze Q1 sales data and generate a summary report.",
authorized_tools: ["database_read", "file_write"],
authorized_resources: ["db://sales/q1-2026"],
data_classification: "confidential",
network_egress: false,
persistence: true,
max_hops: 3, // issuer's choice of delegation budget, not a protocol limit
},
signingKey: privateKey,
keyId: "alice-signing-key-v1",
});
// 3. Extend the chain as the task delegates to agents
token = await extendChain(
token,
{
agent_id: "orchestrator-v2",
agent_type: "orchestrator",
action_summary: "Decompose analysis task and delegate to sub-agents.",
parent_hop: 0,
},
privateKey,
);
token = await extendChain(
token,
{
agent_id: "sql-agent-v1",
agent_type: "sub-agent",
action_summary: "Execute read query against sales database.",
parent_hop: 1,
},
privateKey,
);
// 4. Verify record integrity at any point in the chain
const result = await verifyToken(token, { publicKey });
console.log({ token_id: token.header.token_id, valid: result.valid });
if (!result.valid) {
console.log({
token_id: token.header.token_id,
failedStep: result.failedStep,
errorCode: result.error.code,
});
}
console.log({ token_id: token.header.token_id, hopCount: token.chain.length });@helixar_ai/hdp-physical and hdp-physical extend HDP into robotics with Embodied Delegation Tokens (EDTs) and a pre-execution guard. Before a motion command reaches an actuator, HDP-P verifies the EDT signature, checks the irreversibility ceiling, enforces excluded zones, and blocks actions that exceed force or velocity limits.
import {
EdtBuilder,
IrreversibilityClass,
PreExecutionGuard,
signEdt,
} from "@helixar_ai/hdp-physical";
import { generateKeyPair } from "@helixar_ai/hdp";
const { privateKey, publicKey } = await generateKeyPair();
const edt = new EdtBuilder()
.setEmbodiment({
agent_type: "robot_arm",
platform_id: "aloha_v2",
workspace_scope: "zone_A",
})
.setActionScope({
permitted_actions: ["pick", "place", "move"],
excluded_zones: ["human_zone"],
max_force_n: 45,
max_velocity_ms: 0.5,
})
.setIrreversibility({
max_class: IrreversibilityClass.REVERSIBLE_WITH_EFFORT,
class2_requires_confirmation: true,
class3_prohibited: true,
})
.setPolicyAttestation({
policy_hash: "sha256-of-weights",
training_run_id: "run-1",
sim_validated: true,
})
.setDelegationScope({
allow_fleet_delegation: false,
max_delegation_depth: 1,
sub_agent_whitelist: [],
})
.build();
const signedEdt = await signEdt(edt, privateKey, "robot-key-v1");
const guard = new PreExecutionGuard();
const decision = await guard.authorize(
{
description: "pick box from left bin",
force_n: 5,
velocity_ms: 0.2,
},
signedEdt,
publicKey,
);
console.log(decision.approved);For Python, install hdp-physical and use the same EDT model and guard flow, with optional lerobot and gemma extras for adapters and interception.
→ Full TypeScript physical AI docs → Full Python physical AI docs
hdp-grok attaches HDP to a Grok conversation through three native tool schemas. Grok calls hdp_issue_token, hdp_extend_chain, and hdp_verify_token as regular tool calls. HdpMiddleware holds the token and hop counter for the conversation.
import json
import os
from openai import OpenAI
from hdp_grok import HdpMiddleware, get_hdp_tools
# xAI API — OpenAI-compatible endpoint
client = OpenAI(
api_key=os.environ["XAI_API_KEY"],
base_url="https://api.x.ai/v1",
)
# One middleware instance per conversation
middleware = HdpMiddleware(
signing_key=os.getenv("HDP_SIGNING_KEY"), # base64url Ed25519 private key
principal_id="user@example.com",
)
messages = [{"role": "user", "content": "Issue an HDP token and delegate to research-agent."}]
while True:
response = client.chat.completions.create(
model="grok-3",
messages=messages,
tools=get_hdp_tools(), # inject the three HDP tool schemas
)
choice = response.choices[0]
if choice.finish_reason == "tool_calls":
messages.append(choice.message)
for tc in choice.message.tool_calls:
result = middleware.handle_tool_call(
name=tc.function.name,
args=json.loads(tc.function.arguments),
)
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps(result),
})
else:
print({"response_received": choice.message.content is not None})
break
# Export the record for later audit without printing its contents
token = middleware.export_current_token()
if token is not None:
print({"token_id": token["header"]["token_id"]})| Tool | Required args | What it does |
|---|---|---|
hdp_issue_token |
— | Signs a root token for the session and principal |
hdp_extend_chain |
delegatee_id |
Appends a signed delegation hop (e.g. to a sub-agent) |
hdp_verify_token |
token |
Verifies the full chain using the middleware's public key |
- Holds the Ed25519 signing key (bytes, hex, base64url, or
HDP_SIGNING_KEYenv var) - Maintains the current token and hop counter for the conversation lifetime
- Routes all
hdp_*tool calls viahandle_tool_call(name, args) - Handles both snake_case and camelCase argument names from Grok
- Extending before issuing a token raises
HdpTokenMissingError; a missing signing key raisesHdpSigningKeyError
HDP tokens are records. Expiry does not affect integrity: the legacy expired result key remains for compatibility, and recorded_after_period lists hop sequence numbers at or after expires_at. HdpTokenExpiredError remains importable but is deprecated and never raised.
hdp-crewai attaches HDP to a CrewAI crew with one middleware.configure(crew) call. Existing agents, tasks, and crew configuration remain unchanged.
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from crewai import Agent, Crew, Task
from hdp_crewai import HdpMiddleware, HdpPrincipal, ScopePolicy, verify_chain
private_key = Ed25519PrivateKey.generate()
middleware = HdpMiddleware(
signing_key=private_key.private_bytes_raw(),
session_id="q1-review-2026",
principal=HdpPrincipal(id="analyst@company.com", id_type="email"),
scope=ScopePolicy(
intent="Analyse Q1 sales data and produce a summary",
authorized_tools=["FileReadTool", "CSVAnalysisTool"],
max_hops=5,
),
)
researcher = Agent(
role="Research analyst",
goal="Summarize Q1 sales data",
backstory="An analyst preparing a concise internal report.",
)
task = Task(
description="Summarize the Q1 sales data for the review team.",
expected_output="A concise summary of the Q1 sales data.",
agent=researcher,
)
crew = Crew(agents=[researcher], tasks=[task])
middleware.configure(crew) # attach HDP — one line, zero crew changes
crew.kickoff()
# Verify the full delegation chain offline
result = verify_chain(middleware.export_token(), private_key.public_key())
print(result.valid, result.hop_count, result.recorded_after_period)| # | Consideration | Behaviour |
|---|---|---|
| 1 | Scope observation | step_callback compares each tool call with declared authorized_tools and records out-of-scope attempts without gating the tool. |
| 2 | Recording depth | max_hops caps the recorded chain. CrewAI actions continue after the chain is full. |
| 3 | Recording failures | Failures are logged as warnings and do not stop the crew. |
| 4 | Verification | verify_chain(token, public_key) checks integrity offline. valid covers integrity only; recorded_after_period lists hop sequence numbers at or after expires_at. |
| 5 | Memory integration | Signed token is persisted to CrewAI's storage directory for retroactive auditing. |
→ Full CrewAI integration docs
For compatibility, strict=True raises ValueError during middleware construction. HDPScopeViolationError remains importable, is deprecated, and is never raised.
hdp-autogen attaches HDP to any AutoGen ConversableAgent or GroupChatManager with a single middleware.configure(target) call. Each speaker turn in a GroupChat is recorded as a delegation hop.
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from autogen import ConversableAgent, GroupChat, GroupChatManager, UserProxyAgent
from hdp_autogen import HdpMiddleware, HdpPrincipal, ScopePolicy, verify_chain
private_key = Ed25519PrivateKey.generate()
middleware = HdpMiddleware(
signing_key=private_key.private_bytes_raw(),
session_id="research-2026-q1",
principal=HdpPrincipal(id="researcher@lab.edu", id_type="email"),
scope=ScopePolicy(
intent="Coordinate research agents to summarise recent papers",
authorized_tools=["web_search", "file_reader"],
max_hops=10,
),
)
researcher = ConversableAgent("researcher", llm_config=False)
reviewer = ConversableAgent("reviewer", llm_config=False)
groupchat = GroupChat(agents=[researcher, reviewer], messages=[])
manager = GroupChatManager(groupchat=groupchat, llm_config=False)
middleware.configure(manager) # attaches recording hooks to the manager and agents
# Start the GroupChat through the host agent. The wrapped manager.run_chat issues the token.
user_proxy = UserProxyAgent(
"user_proxy",
human_input_mode="NEVER",
code_execution_config=False,
)
user_proxy.initiate_chat(
manager,
message="Summarise recent LLM papers",
max_turns=1,
)
# Inspect the record after the host run.
token = middleware.export_token()
if token is not None:
result = verify_chain(token, private_key.public_key())
print(result.valid, result.hop_count, result.recorded_after_period)| # | Consideration | Behaviour |
|---|---|---|
| 1 | Scope observation | Incoming messages are compared with declared authorized_tools; out-of-scope attempts are recorded without gating the message. |
| 2 | Recording depth | max_hops caps the recorded chain. AutoGen actions continue after the chain is full. |
| 3 | Recording failures | Failures are logged as warnings and do not stop agents. |
| 4 | Verification | verify_chain(token, public_key) checks integrity offline. valid covers integrity only; recorded_after_period lists hop sequence numbers at or after expires_at. |
| 5 | GroupChat integration | configure() detects ConversableAgent vs GroupChatManager and attaches the appropriate hooks automatically. |
→ Full AutoGen integration docs
For compatibility, strict=True raises ValueError during middleware construction. HDPScopeViolationError remains importable, is deprecated, and is never raised.
hdp-agent-framework attaches HDP to any Microsoft agent-framework Agent via the native ChatMiddleware and function middleware protocols. A single middleware.configure(agent) call appends both middlewares to agent.middleware — no other changes required.
import asyncio
import os
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from agent_framework import Agent
from agent_framework.foundry import FoundryChatClient
from azure.identity.aio import AzureCliCredential
from hdp_agent_framework import HdpMiddleware, HdpPrincipal, ScopePolicy, verify_chain
private_key = Ed25519PrivateKey.generate()
middleware = HdpMiddleware(
signing_key=private_key.private_bytes_raw(),
session_id="analysis-2026",
principal=HdpPrincipal(id="analyst@corp.com", id_type="email"),
scope=ScopePolicy(
intent="Analyse Q1 sales data and generate a summary",
authorized_tools=["fetch_data", "write_report"],
max_hops=5,
),
)
agent = Agent(
client=FoundryChatClient(
credential=AzureCliCredential(),
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
),
name="sales_analyst",
tools=[],
)
middleware.configure(agent) # attaches chat + function middleware — one line
asyncio.run(agent.run("Analyse Q1 EMEA sales and write a summary."))
token = middleware.export_token()
if token is not None:
result = verify_chain(token, private_key.public_key())
print(result.valid, result.hop_count)| # | Consideration | Behaviour |
|---|---|---|
| 1 | Scope observation | Tool calls are compared with declared authorized_tools; out-of-scope attempts are recorded without gating the call. |
| 2 | Recording depth | max_hops caps the recorded chain. Agent-framework actions continue after the chain is full. |
| 3 | Recording failures | Failures are logged as warnings and do not stop agents. |
| 4 | Verification | verify_chain(token, public_key) checks integrity offline. valid covers integrity only; recorded_after_period lists hop sequence numbers at or after expires_at. |
| 5 | Agent integration | configure() appends HdpMiddleware and _function_middleware to agent.middleware — idempotent, duck-typed, no hard dependency on agent-framework internals. |
→ Full agent-framework integration docs
For compatibility, strict=True raises ValueError during middleware construction. HDPScopeViolationError remains importable, is deprecated, and is never raised.
llama-index-callbacks-hdp covers all three LlamaIndex hook points. The layers share the same ContextVar-backed session and can be active simultaneously.
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from llama_index.callbacks.hdp import HdpInstrumentationHandler, HdpPrincipal, ScopePolicy, verify_chain
private_key = Ed25519PrivateKey.generate()
HdpInstrumentationHandler.init(
signing_key=private_key.private_bytes_raw(),
principal=HdpPrincipal(id="alice@corp.com", id_type="email"),
scope=ScopePolicy(
intent="Research RAG pipeline",
authorized_tools=["web_search", "retriever"],
max_hops=10,
),
on_token_ready=lambda token: print(token["header"]["token_id"]),
)
# All subsequent LlamaIndex queries are now covered — no further changes requiredfrom cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from llama_index.callbacks.hdp import HdpCallbackHandler, HdpPrincipal, ScopePolicy
from llama_index.core import Settings
from llama_index.core.callbacks import CallbackManager
private_key = Ed25519PrivateKey.generate()
handler = HdpCallbackHandler(
signing_key=private_key.private_bytes_raw(),
principal=HdpPrincipal(id="alice@corp.com", id_type="email"),
scope=ScopePolicy(intent="Research pipeline"),
)
Settings.callback_manager = CallbackManager([handler])from llama_index.callbacks.hdp import HdpNodePostprocessor
def add_hdp_postprocessor(index, signing_key: bytes):
postprocessor = HdpNodePostprocessor(
signing_key=signing_key,
check_data_classification=True,
)
return index.as_query_engine(node_postprocessors=[postprocessor])The postprocessor returns every node. It records classification findings only when a signing key is configured and the chain has capacity; when recording is unavailable or fails, retrieval continues.
from llama_index.callbacks.hdp import verify_chain
def verify_record(token_dict, public_key):
result = verify_chain(token_dict, public_key)
print(result.valid, result.hop_count, result.recorded_after_period)
return result| # | Consideration | Behaviour |
|---|---|---|
| 1 | Hook coverage | Instrumentation dispatcher captures QueryStartEvent, AgentToolCallEvent, LLMChatStartEvent, QueryEndEvent. Callback handler covers legacy FUNCTION_CALL and LLM events. |
| 2 | Shared session | All three layers read/write the same ContextVar — a token issued by the instrumentation handler is visible to the node postprocessor in the same asyncio task. |
| 3 | Scope observation | Out-of-scope tool calls are recorded as findings; callbacks do not gate tool execution. |
| 4 | Data classification | The postprocessor records a classification finding when configured and the chain has capacity; it returns the node so retrieval continues. |
| 5 | Observability overlap | HDP complements Arize Phoenix and Langfuse — they observe runtime activity; HDP authenticates the issuer's delegation record and signed chain entries. |
→ Full LlamaIndex integration docs
For compatibility, strict=True and instrumentation on_violation="raise" raise ValueError during construction. HDPScopeViolationError remains importable, is deprecated, and is never raised.
HDP ships a KeyRegistry for kid → publicKey resolution and a well-known endpoint format for automated key distribution.
import { KeyRegistry, generateKeyPair, exportPublicKey } from "@helixar_ai/hdp";
const registry = new KeyRegistry();
const { publicKey: oldPublicKey } = await generateKeyPair();
registry.register("signing-key-v1", oldPublicKey);
const exportedPub = exportPublicKey(oldPublicKey); // base64url string
// Resolve the key ID read from token.signature.kid
const kid = "signing-key-v1";
const key = registry.resolve(kid); // Uint8Array | null
// Stop publishing the old key for new resolution, then register the new key
registry.revoke("signing-key-v1");
const { publicKey: newPublicKey } = await generateKeyPair();
registry.register("signing-key-v2", newPublicKey);
// Export for /.well-known/hdp-keys.json
const doc = registry.exportWellKnown();
// → { keys: [{ kid, alg: 'Ed25519', pub: '<base64url>' }] }| Environment | Recommended storage |
|---|---|
| Development | In-memory KeyRegistry, keys generated per-process |
| Staging | Environment variables via secrets manager |
| Production | HSM or cloud KMS (AWS KMS, GCP Cloud HSM, Azure Key Vault) |
| Edge / serverless | Pre-distributed public keys; private key in secure enclave |
Key rotation: Issue new tokens with a new kid. Keep archived public keys available for as long as historical records need integrity verification. KeyRegistry.revoke() removes a key from that registry; it does not revoke tokens.
verifyToken() verifies record integrity offline with the issuer's public key only. It does not inspect current time, session state, presenter identity, or token revocation state. Input validation is reported as API step 0; the five integrity steps are:
- Version check: recognized
hdpvalue and matchingheader.version. - Root signature verification over the canonical issuance record.
- Hop sequence and structure:
seq,parent_hop, and nondecreasing timestamps. - Hop signature verification in chain order.
- Recorded depth:
chain.lengthdoes not exceedscope.max_hops, when defined.
import { generateKeyPair, issueToken, verifyToken } from "@helixar_ai/hdp";
const { privateKey, publicKey } = await generateKeyPair();
const token = await issueToken({
sessionId: "sess-offline-1",
principal: { id: "user-42", id_type: "opaque" },
scope: {
intent: "Prepare a report",
data_classification: "internal",
network_egress: false,
persistence: false,
},
signingKey: privateKey,
keyId: "issuer-key-v1",
});
const result = await verifyToken(token, { publicKey });
if (result.valid) {
console.log({ token_id: token.header.token_id, integrity: "valid" });
} else {
console.log({
token_id: token.header.token_id,
failedStep: result.failedStep,
errorCode: result.error.code,
});
}For a parsed object, duplicate-member detection rests with the parser that produced it. Passing serialized JSON text to verifyToken() lets the verifier reject duplicate members during input validation. A failed integrity check is an audit result and does not decide whether an action proceeds.
import { auditToken, generateKeyPair, issueToken, issueSupersedingToken } from "@helixar_ai/hdp";
const { privateKey, publicKey } = await generateKeyPair();
const original = await issueToken({
sessionId: "sess-archive-1",
principal: { id: "user-42", id_type: "opaque" },
scope: {
intent: "Prepare a report",
data_classification: "internal",
network_egress: false,
persistence: false,
},
signingKey: privateKey,
keyId: "issuer-key-v1",
});
const archivedToken = await issueSupersedingToken({
original,
signingKey: privateKey,
keyId: "issuer-key-v1",
});
const report = await auditToken(archivedToken, {
publicKey,
sessionId: archivedToken.header.session_id,
// Supply only when trusted application context confirms the link meaning.
linkedRecordRelationship: "supersession",
});
const errorCode = report.integrity.status === "invalid"
? report.integrity.error.code
: undefined;
const recordingPeriod = report.recordingPeriod.status === "recorded_after_period"
? { status: report.recordingPeriod.status, hopSeqs: report.recordingPeriod.hopSeqs }
: { status: report.recordingPeriod.status };
console.log({
token_id: archivedToken.header.token_id,
integrity: report.integrity.status,
failedStep: report.integrity.status === "invalid" ? report.integrity.failedStep : undefined,
errorCode,
recordingPeriod,
session: report.session.status,
linkedRecords: report.linkedRecords.status === "linked"
? report.linkedRecords.relationship
: report.linkedRecords.status,
poh: report.poh.status,
});AuditReport separates integrity, recording period, optional session comparison, linked-record relationship, and optional Proof-of-Humanity verification. Leave linkedRecordRelationship unset unless trusted application context establishes it; the report then uses unknown for a linked record.
import {
extendChain,
generateKeyPair,
InMemoryTokenStore,
issueToken,
storeToken,
storeTokenByReference,
resolveToken,
} from "@helixar_ai/hdp";
const { privateKey } = await generateKeyPair();
const token = await issueToken({
sessionId: "sess-reference-1",
principal: { id: "user-42", id_type: "opaque" },
scope: {
intent: "Prepare a report",
data_classification: "internal",
network_egress: false,
persistence: false,
},
signingKey: privateKey,
keyId: "issuer-key-v1",
});
const extendedToken = await extendChain(token, {
agent_id: "report-agent",
agent_type: "sub-agent",
action_summary: "Format the report",
parent_hop: 0,
}, privateKey);
const store = new InMemoryTokenStore();
const tokenIdRef = await storeToken(store, token); // immutable first snapshot
const digestRef = await storeTokenByReference(store, extendedToken); // sha256:...
const snapshot = await resolveToken(store, digestRef); // digest checked on resolution
console.log({ token_id: snapshot?.header.token_id, reference: digestRef });UUID and digest references are write-once snapshots. Extending a token preserves its token_id, so later chain states use a new content-addressed reference instead of overwriting the UUID mapping. Reference resolution checks identify the requested record; they do not decide whether an action proceeds.
When a record reaches max_hops, it is not extended further. The task may continue; a new linked record can provide additional recording depth or record fresh human-approved context. Supersession adds a record and does not invalidate its predecessor.
import { generateKeyPair, issueToken, issueSupersedingToken } from "@helixar_ai/hdp";
const { privateKey } = await generateKeyPair();
const original = await issueToken({
sessionId: "sess-stream-1",
principal: { id: "user-42", id_type: "opaque" },
scope: {
intent: "Analyze the sales report",
data_classification: "confidential",
network_egress: false,
persistence: false,
max_hops: 2,
},
signingKey: privateKey,
keyId: "issuer-key-v1",
});
const successor = await issueSupersedingToken({
original,
scope: {
...original.scope,
intent: "Continue the approved sales report analysis",
max_hops: 3,
},
signingKey: privateKey,
keyId: "issuer-key-v1",
expiresInMs: 60 * 60 * 1000,
});
console.log({
token_id: successor.header.token_id,
parentTokenId: successor.header.parent_token_id,
chainLength: successor.chain.length,
});The new record has a new token_id, issued_at, and expires_at, inherits the original session, principal, and scope unless overridden, and starts with an empty chain. The SDK currently uses a 24-hour fallback when expiresInMs is omitted; HDP defines no protocol default. A hop at or after expires_at is retained and reported by audit as recorded after the authorization period ended.
HDP v0.1 records one principal per token. Each principal's approval is a separately signed record. The parent link and shared session_id identify linked records; trusted application context is required to label the relationship as joint approval. Without that context, the relationship is unknown.
import {
generateKeyPair,
issueToken,
issueSupersedingToken,
verifyPrincipalChain,
} from "@helixar_ai/hdp";
const aliceKeys = await generateKeyPair();
const bobKeys = await generateKeyPair();
const scope = {
intent: "Review the quarterly report",
data_classification: "confidential" as const,
network_egress: false,
persistence: false,
};
const t1 = await issueToken({
sessionId: "sess-joint-approval",
principal: { id: "alice", id_type: "opaque" },
scope,
signingKey: aliceKeys.privateKey,
keyId: "alice-key-v1",
});
const t2 = await issueSupersedingToken({
original: t1,
principal: { id: "bob", id_type: "opaque" },
signingKey: bobKeys.privateKey,
keyId: "bob-key-v1",
});
const entries = [
{ token: t1, publicKey: aliceKeys.publicKey },
{ token: t2, publicKey: bobKeys.publicKey },
];
const withoutContext = await verifyPrincipalChain(entries);
console.log({ token_id: t2.header.token_id, relationship: withoutContext.relationship });
const jointApproval = await verifyPrincipalChain(entries, {
relationshipContext: { type: "joint_approval", authenticated: true },
});
console.log({
token_id: t2.header.token_id,
recordsValid: jointApproval.valid,
relationship: jointApproval.relationship,
});verifyPrincipalChain() checks each token's integrity, parent link, and shared session. Its valid field reports those audit checks; it is not an action decision. Expiry, principal equality, and session equality alone do not establish joint approval. CoAuthorizationRequest is a type-only v0.2 preview; simultaneous threshold signing is not implemented in v0.1.
import {
buildAuditSafe,
generateKeyPair,
issueToken,
redactPii,
stripPrincipal,
} from "@helixar_ai/hdp";
const { privateKey } = await generateKeyPair();
const token = await issueToken({
sessionId: "sess-privacy-1",
principal: { id: "user-42", id_type: "opaque" },
scope: {
intent: "Prepare a report",
data_classification: "internal",
network_egress: false,
persistence: false,
},
signingKey: privateKey,
keyId: "issuer-key-v1",
});
const safeForTransmission = stripPrincipal(token); // remove all principal PII
const anonymized = redactPii(token); // principal.id → '[REDACTED]'
const auditEntry = buildAuditSafe(token); // token_id + intent + chain summaryverifyToken() performs input validation as API step 0, then checks the five integrity steps in order:
- Version:
hdpis recognized and equalsheader.version. - Root signature: Ed25519 signature over the canonical issuance record.
- Hop structure: sequential
seq, valid priorparent_hop, and nondecreasing timestamps. - Hop signatures: every recorded hop signature verifies in chain order.
- Recorded depth: when
scope.max_hopsis defined, the chain does not exceed it.
Verification stops at the first failure and returns its failedStep and error. expires_at, session matching, and Proof-of-Humanity checks do not change integrity; auditToken() reports recording period, optional session comparison, linked-record relationship, and optional Proof-of-Humanity separately. A failed verification or audit finding does not decide whether an action proceeds. See audit semantics.
The Intent Provenance Protocol (draft-haberkamp-ipp-01) solves the same problem with different trade-offs. The critical difference: IPP requires agents to poll a central revocation registry every 5 seconds. If the registry is unreachable, agents cannot safely act. Every IPP token is also cryptographically anchored to ipp.khsovereign.com/keys/founding_public.pem — making fully self-sovereign deployment impossible.
HDP integrity verification is fully offline and requires only the trusted issuer public key. It does not use session context, current time, or verifier-local token-revocation state. No central registry, central endpoint, or third-party trust anchor is required for integrity verification.
→ Full technical comparison: COMPARISON.md
HDP stops at provenance. It does not enforce.
HDP records an issuer's statement about human delegation context and the activity the issuer subsequently recorded. It does not:
- Prevent an agent from exceeding its declared scope at runtime
- Enforce
authorized_toolsordata_classificationconstraints at the model layer - Implement separate application access controls or revocation policy when the service requires them
- Provide a central authority
- Prove that an action occurred or that a named delegate consented
- Prove that the supplied chain is the only or final branch
Applications that need runtime enforcement should use their own access control mechanism. HDP tokens remain audit input and do not gate those actions.
HDP v0.1 has been audited against spec §12's 10 threat scenarios. See docs/security/audit-report-v0.1.md.
Test coverage includes: input validation, token forgery, chain tampering, prompt injection, recorded-depth checks, and offline integrity verification.
Packages use independent tag prefixes.
The v* tag publishes @helixar_ai/hdp, @helixar_ai/hdp-mcp, hdp-validate, and @helixar_ai/hdp-autogen:
git tag v0.2.0 && git push origin v0.2.0Pipeline: test-node → vet-node (ReleaseGuard) → publish-hdp + publish-hdp-mcp + publish-hdp-cli + publish-hdp-autogen-ts
Publishes only @helixar_ai/hdp-autogen (TypeScript AutoGen middleware):
git tag node/hdp-autogen/v0.2.0 && git push origin node/hdp-autogen/v0.2.0Pipeline: test-hdp-autogen-ts → vet-hdp-autogen-ts (ReleaseGuard) → publish-hdp-autogen-ts-standalone
git tag python/v0.2.0 && git push origin python/v0.2.0Pipeline: test-python → vet-hdp-crewai (ReleaseGuard) → publish-hdp-crewai
git tag python/hdp-grok/v0.2.0 && git push origin python/hdp-grok/v0.2.0Pipeline: test-hdp-grok → vet-hdp-grok (ReleaseGuard) → publish-hdp-grok
git tag python/hdp-autogen/v0.2.0 && git push origin python/hdp-autogen/v0.2.0Pipeline: test-hdp-autogen → vet-hdp-autogen (ReleaseGuard) → publish-hdp-autogen
git tag python/hdp-agent-framework/v0.2.0 && git push origin python/hdp-agent-framework/v0.2.0Pipeline: test-hdp-agent-framework → vet-hdp-agent-framework (ReleaseGuard) → publish-hdp-agent-framework
git tag python/hdp-langchain/v0.2.0 && git push origin python/hdp-langchain/v0.2.0Pipeline: test-hdp-langchain → vet-hdp-langchain (ReleaseGuard) → publish-hdp-langchain
git tag python/llama-index-callbacks-hdp/v0.2.0 && git push origin python/llama-index-callbacks-hdp/v0.2.0Pipeline: test-llama-index-callbacks-hdp → vet-llama-index-callbacks-hdp (ReleaseGuard) → publish-llama-index-callbacks-hdp
git tag python/hdp-llamaindex/v0.2.0 && git push origin python/hdp-llamaindex/v0.2.0Pipeline: test-hdp-llamaindex → vet-hdp-llamaindex (ReleaseGuard) → publish-hdp-llamaindex
Every artifact is scanned by ReleaseGuard before it reaches PyPI or npm — checking for secrets, unexpected files, license compliance, and generating a CycloneDX SBOM. The exact vetted artifact is what gets published. If ReleaseGuard fails, the publish job never runs.
# Vet locally before tagging
cd packages/hdp-grok && python -m build && releaseguard check ./dist
cd packages/hdp-crewai && python -m build && releaseguard check ./dist
cd packages/hdp-autogen && python -m build && releaseguard check ./dist
cd packages/hdp-agent-framework && python -m build && releaseguard check ./dist
cd packages/hdp-langchain && python -m build && releaseguard check ./dist
cd packages/llama-index-callbacks-hdp && python -m build && releaseguard check ./dist
cd packages/hdp-autogen-ts && npm run build && releaseguard check ./distFull protocol specification: https://helixar.ai/about/labs/hdp/
For research publications, cite:
@misc{dalugoda2026hdp,
title = {{HDP}: A Lightweight Cryptographic Protocol for Human Delegation
Provenance in Agentic {AI} Systems},
author = {Dalugoda, Asiri},
year = {2026},
month = apr,
eprint = {2604.04522},
archivePrefix = {arXiv},
primaryClass = {cs.CR},
url = {https://arxiv.org/abs/2604.04522},
}Apache License 2.0 — Helixar Limited