dyolo-kya — Know Your Agent
Cryptographic chain-of-custody for recursive AI agent delegation.
The Problem
When a human authorizes an AI agent — which may delegate to another agent, which may delegate again — the authorization chain breaks down at the first hop. There is no irrefutable proof that an action traces back to a human authorization, no guarantee that the delegated scope is a strict subset of the parent's scope, and no replay or revocation enforcement across hops.
This is the Recursive Delegation Gap. dyolo-kya closes it with a
cryptographic chain-of-custody protocol using Ed25519 signatures and Blake3
Merkle trees over intent hashes.
What is new in v2.0.0
| Gap from v1 | v2.0 fix |
|---|---|
| Rust-only — no SDK for other languages | REST gateway + Go SDK + Python SDK + TypeScript SDK |
| No ops tooling | dyolo-kya-cli: keygen, issue, revoke, inspect, verify, decode |
| No identity bridge to existing IAM | dyolo-kya-identity: JWT/OIDC binding + YAML policy-as-code |
| Redis-only storage | dyolo-kya-pg: PostgreSQL adapter with multi-tenant namespacing |
| No typed extension fields | CertExtensions committed into cert signatures |
| No AI framework examples | LangChain, OpenAI Assistants, AutoGen, Go SDK |
| Global rate limiting | Per-client-IP bucketing with governor::keyed |
| Two-step nonce protocol (TOCTOU) | Atomic try_consume — single-roundtrip at DB level |
| No batch cert issuance | POST /v1/cert/issue-batch + CertBundle type |
| No discovery document | GET /.well-known/kya-configuration (OIDC-style) |
| No batch authorization | POST /v1/authorize/batch + authorizeBatch in all SDKs |
| No policy-as-code | PolicyDocument YAML/JSON compiled into DelegationPolicy |
Architecture
┌───────────────────────────────────────────────────────────────────────┐
│ Human (Ed25519 root) │
│ │ signs DelegationCert (scope: trade.equity, max_depth: 8) │
│ ▼ │
│ Orchestrator agent (Ed25519) │
│ │ narrows scope → signs sub-cert (scope: trade.equity/NYSE) │
│ ▼ │
│ Tool agent (Ed25519) │
│ │ calls execute_trade("AAPL", 10) │
│ ▼ │
│ dyolo-kya-gateway POST /v1/authorize │
│ verifies: signatures → scope ⊆ parent → expiry → nonce → revoked │
│ returns: AuthorizeResponse { authorized: true, chain_depth: 2 } │
└───────────────────────────────────────────────────────────────────────┘
Five-minute start (Docker + Python)
# 1. Run the gateway
# 2. Install the Python SDK
# 3. Issue a cert and authorize an action
SDK quick-reference
Go
import "github.com/dyologician/dyolo-kya/sdk/go/kya"
client := kya.NewClient("http://localhost:8080")
cert, err := client.IssueCert(ctx, kya.IssueCertRequest)
result, err := client.Authorize(ctx, kya.AuthorizeRequest)
// Batch: authorize multiple intents atomically
batch, err := client.AuthorizeBatch(ctx, kya.BatchAuthorizeRequest)
Python
=
=
=
# Batch
=
assert
TypeScript / Node.js
import { KyaClient } from "dyolo-kya";
import { buildLangChainKyaBatchTool } from "dyolo-kya/integrations";
const kya = new KyaClient("http://localhost:8080");
const cert = await kya.issueCert({ delegatePkHex: "...", intents: [{ name: "trade.equity" }] });
// Single-intent
const res = await kya.authorize({ chain, intentName: "trade.equity", executorPkHex: "..." });
// Multi-intent batch (atomic single round-trip)
const batch = await kya.authorizeBatch({
chain,
executorPkHex: "...",
intents: [{ name: "query.portfolio" }, { name: "trade.equity" }],
});
if (!batch.allAuthorized) throw new Error("Batch denied");
// LangChain multi-intent tool guard
const tool = buildLangChainKyaBatchTool({
name: "portfolio_rebalance",
description: "Query and rebalance a portfolio",
intentNames: ["query.portfolio", "trade.equity"],
client: kya,
resolveContext: (input) => ({ chain: agentChain, executorPkHex: agentPk }),
run: async (input, auth) => `Rebalanced (${auth.authorizedCount} intents authorized)`,
});
Rust (native, no gateway)
use ;
let human = generate;
let agent = generate;
let intent = new.unwrap;
let tree = build.unwrap;
let cert = new
.sign;
let mut chain = new;
chain.push;
// Plain authorize
let action = chain.authorize.unwrap;
// Authorize with policy enforcement + audit log
let policy = new.add;
let action = chain.authorize_with_options.unwrap;
assert_eq!;
Policy-as-code (YAML)
Store your policies in source control and load them at startup:
# policies/fintech-trading.yaml
name: fintech-trading
max_chain_depth: 4
max_ttl_secs: 3600
forbid_sub_delegation: true
capabilities:
- "trade.equity"
- "query.portfolio"
required_extensions:
- "dyolo.cost_center"
use PolicyDocument;
let doc = from_yaml?;
let policy = doc.into_policy; // → DelegationPolicy
Batch cert issuance
Issue multiple delegation certificates in a single authenticated request:
{
{ }
{ }
}
Returns a CertBundle alongside the individual issuance results.
Discovery document
Returns an OIDC-style JSON document with the gateway's signing public key,
all endpoint URLs, supported algorithms, and protocol version. Enterprise
clients can bootstrap trust by fetching this document and pinning the
gateway_signing_pk_hex field.
CLI
# PostgreSQL migration (one-time setup for dyolo-kya-pg)
# Print the raw DDL for manual or CI-managed application
|
# Generate shell completions
Gateway REST API
| Method | Path | Description |
|---|---|---|
GET |
/health |
Liveness + signing public key |
GET |
/.well-known/kya-configuration |
OIDC-style discovery document |
POST |
/v1/cert/issue |
Issue a single cert |
POST |
/v1/cert/issue-batch |
Issue multiple certs (CertBundle) |
POST |
/v1/cert/revoke |
Revoke a cert by fingerprint |
POST |
/v1/cert/revoke-batch |
Revoke multiple certs in one call |
GET |
/v1/cert/:fp |
Inspect revocation status |
POST |
/v1/authorize |
Authorize a single agent intent |
POST |
/v1/authorize/batch |
Authorize multiple intents atomically |
POST |
/v1/token/verify |
Verify a VerifiedToken MAC |
Environment variables
| Variable | Default | Description |
|---|---|---|
DYOLO_SIGNING_KEY_HEX |
ephemeral | 32-byte Ed25519 signing key (hex). Set in production. |
DYOLO_MAC_KEY_HEX |
ephemeral | 32-byte BLAKE3 MAC key (hex). Set in production. |
DYOLO_RATE_LIMIT_RPS |
500 |
Per-IP request rate limit (requests/second). |
GATEWAY_ADDR |
0.0.0.0:8080 |
Bind address |
RUST_LOG |
info |
Log filter |
Framework integrations
| Framework | Language | Reference |
|---|---|---|
| LangChain | Python | docs/integrations/langchain.md |
| OpenAI Assistants | Python | docs/integrations/openai-agents.md |
| AutoGen | Python | examples/integrations/autogen_example.py |
| OpenAI Agents SDK | TypeScript | examples/integrations/openai_agents_example.ts |
| LangChain.js (batch) | TypeScript | buildLangChainKyaBatchTool in dyolo-kya/integrations |
| OpenAI SDK (batch) | TypeScript | buildOpenAIKyaBatchFunction in dyolo-kya/integrations |
| AutoGen (batch) | TypeScript | withKyaBatchGuard in dyolo-kya/integrations |
Crate / package layout
| Package | Description |
|---|---|
dyolo-kya (Rust) |
Core library — certs, chains, intents, identity, policy, audit |
dyolo-kya-gateway |
Axum REST sidecar with per-IP rate limiting |
dyolo-kya-cli |
Operations CLI |
dyolo-kya-redis |
Redis AsyncRevocationStore + AsyncNonceStore (atomic SET NX PX) |
dyolo-kya-pg |
PostgreSQL adapter (atomic INSERT ON CONFLICT DO NOTHING) |
dyolo-kya-identity |
JWT/OIDC binding + YAML policy-as-code (PolicyDocument) |
dyolo-kya (npm) |
TypeScript/Node.js SDK |
dyolo-kya (PyPI) |
Python SDK |
dyolo-kya (Go module) |
Go SDK |
How dyolo-kya compares to JWT delegation and SPIFFE
See docs/vs-jwt-spiffe.md.
SPIFFE answers "which workload is this?", JWT answers "which user triggered this?",
and dyolo-kya answers "did the user authorize this exact action through this exact
delegation chain, and is the scope still valid?"
Security
Primitives: ed25519-dalek 2 (signatures), blake3 1 (hashing + MAC),
subtle 2 (constant-time comparisons). Nonce stores use single-operation
atomic commits — SET NX PX for Redis, INSERT ON CONFLICT DO NOTHING for
Postgres — eliminating the check-then-set TOCTOU race.
Report vulnerabilities to the address in SECURITY.md.
License
MIT OR Apache-2.0. MD_EOF echo "done"