Lyhna — Documentation
What Lyhna Does
Lyhna is the attestation layer for autonomous execution.
Each call creates an attestation — a signed record that becomes part of the tenant’s judgment corpus.
Over time, that corpus becomes the Institutional Judgment Layer.
Before any consequential action executes, your system calls bind(). Lyhna evaluates authority, applies policy, and returns a signed receipt.
If no receipt exists, the action does not execute.
Every bind() call produces one of three outcomes:
- APPROVED → proceed
- ESCALATED → wait for higher authority, then retry
- REFUSED → stop
Every bind() call returns a cryptographically signed receipt. Only an APPROVED receipt licenses execution. ESCALATED and REFUSED also return receipts, but execution must wait or stop.
The bind() Pattern
Using the SDK
The SDK reads LYHNA_API_KEY from your environment automatically. No init() call is required.
import { bind } from '@lyhna/bind';
const receipt = await bind({
action_type: 'test_ping',
action_payload: {},
intent: 'verify_install',
intent_version: '1.0',
});
if (receipt?.receipt_id && receipt?.signature) {
console.log('Lyhna install verified.');
console.log('Outcome:', receipt.outcome);
console.log('Receipt:', receipt.receipt_id);
}
if (receipt.outcome === 'ESCALATED') {
console.log('Fresh tenant: ESCALATED is expected until authority rules are configured.');
}
A fresh tenant may return ESCALATED for test_ping until authority rules are configured. That still proves install success: the SDK reached Lyhna, bind() executed, and a signed receipt was returned.
Direct API Call
const res = await fetch('https://api.lyhna.com/v1/bind', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.LYHNA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
action_type: 'test_ping',
action_payload: {},
intent: 'verify_install',
intent_version: '1.0',
}),
})
const { receipt, request_id, elapsed_ms } = await res.json()
console.log(receipt.outcome);
console.log(receipt.receipt_id);
if (receipt.outcome === 'APPROVED') {
console.log('Lyhna install verified.');
} else if (receipt.outcome === 'ESCALATED') {
console.log('Escalated:', receipt.escalate_to);
} else {
console.log('Refused:', receipt.reason);
}
Three Outcomes
| Outcome | Meaning | Your Action |
|---|---|---|
| APPROVED | Authority verified. Action licensed. | Proceed |
| ESCALATED | Authority insufficient. Higher tier required. | Wait → retry |
| REFUSED | Not authorized under policy. | Stop |
APPROVED is silent. ESCALATED and REFUSED produce actionable signals.
Tier Model
Lyhna operates on four authority tiers.
| Tier | Name | Default Behavior | Examples |
|---|---|---|---|
| 0 | Routine | auto_approve |
Read data, status checks |
| 1 | Operational | standard_review |
Updates, notifications |
| 2 | Consequential | standard_review |
Payments, pricing, customer-impacting changes |
| 3 | Executive | standard_review |
Deploy, delete, bulk ops |
Configuration Model
Lyhna is configured entirely server-side.
authority_rules — Maps action_type → tier. "What kind of action is this?"
tier_policies — Maps tier → behavior. "How strict is this tier?"
Changes apply immediately on the next bind() call. No redeploy. No SDK change.
Receipts
Every bind() call produces a signed receipt containing: receipt_id, action_type, intent_version, authority_tier, outcome, registry_version, constraints evaluated, timestamp, nonce (replay protection), canonical_hash, signature (Ed25519).
Receipts are append-only, immutable, and verifiable offline. Any party can verify a receipt independently.
Loops
A single bind() call proves one action. Autonomous agents now work in loops — they pursue a goal across many actions, then attempt to declare the goal complete. Lyhna chains those actions into one verifiable session.
Each consequential bind() inside a session carries loop context: a loop id, a link to the prior receipt, and a hash of the session goal. The first link's prior reference is null; each subsequent action links to the receipt before it, forming a continuous chain.
The session is sealed by a terminal loop_close receipt. It records the loop id, the number of actions in the session (action_count), the session outcome, and the reason the loop closed (termination_reason), and it links back to the final action receipt — turning an entire multi-step run into a single offline-verifiable object instead of a log reconstructed afterward.
| Receipt | Proves |
|---|---|
| Action receipt | An individual step, at the moment it tried to become real |
| Continuation receipt | Resolution of a held action by a distinct declared authority — a person, another agent, or a policy service; never the acting agent itself |
Loop receipt (loop_close) |
The autonomous session closed with authority intact |
The loop is closed by the runtime boundary, not by the agent. In a real deployment the MCP adapter (see the SDKs page) provides this boundary: it stamps loop context on every governed tool call and emits the terminal loop_close when the session ends. The agent never closes its own loop — self-declared completion is exactly what the chain is built to prevent.
Verification
Receipts are verified by recomputing the canonical hash, comparing to the stored hash, and verifying the Ed25519 signature. No network required. Enforcement must be independently verifiable.
What Lyhna Sees
Lyhna does not ingest your data. It sees: action_type, intent, intent_version, payload_hash (SHA-256). It does NOT see raw payloads, customer data, financial data, messages, or documents. The hash proves integrity. The receipt proves authority.
Six Invariants
These are not configurable.
| Invariant | Meaning |
|---|---|
| Fail-closed | No APPROVED receipt = no execution |
| Deterministic | No probabilistic decisions |
| Append-only | No mutation of receipts |
| Sovereign | Tenant-isolated |
| Verifiable | Offline verification always possible |
| Pre-execution | Authorization happens before execution |
Quick Start — a governed loop, end to end
Which package? @lyhna/bind proves one governed action; @lyhna/mcp wraps an agent loop and exports the capsule trio. This quickstart uses @lyhna/mcp.
The deliverable is the capsule trio from your own loop: proof-card.md (for people), HANDOFF.md (for the next agent), memory-injection.json (for machines) — plus receipts.json, verifiable by anyone with no Lyhna account. Every command below runs as written; the worked example governs a filesystem-writing agent.
- Sign up at lyhna.com/signup. Use any email you control — there is no email verification (deliberately, so an agent can complete this funnel); the API key is the credential.
- Save your API key (shown once) as
LYHNA_API_KEY. - Apply the “Filesystem write preset” — a required step before your first governed run. It creates exactly two things and confirms both: an authority rule
write_text_file → tier_0(auto-approve) and a runtime mappingmcp/write_file → write_text_file. Two equivalent ways, both explicit declared acts — nothing is ever auto-applied:- Human path: press the button on the dashboard (Authority rules card). A fresh tenant's first dashboard visit opens a one-time onboarding wizard — the quickstart needs nothing from it: press Skip all → (top right) to reach the dashboard, or complete it; either way the preset button is on the dashboard behind it.
- Agent path: the button makes exactly these two calls — make them yourself, authenticated with your
lyhna_sessioncookie (from signup orPOST /v1/auth/login):POST /v1/tenant/authority-rules {"rules":[{"action_type":"write_text_file","effective_tier":"tier_0","is_enabled":true}]} POST /v1/tenant/runtime-mappings {"mappings":[{"runtime_family":"mcp","raw_action_key":"write_file","resolver_type":"canonical","resolved_action_type":"write_text_file"}]}
- Start the governed proxy over a scratch directory — both blocks run as written.
macOS / Linux (mkdir workdirfirst):
Windows (PowerShell) — unix sockets are POSIX-only, so the control channel is a loopback TCP port here:export LYHNA_API_KEY=<your key> export LYHNA_PROXY_CONTROL_SOCKET=/tmp/lyhna-control.sock export LYHNA_PROXY_UPSTREAM_COMMAND=npx export LYHNA_PROXY_UPSTREAM_ARGS_JSON='["-y","@modelcontextprotocol/server-filesystem","./workdir"]' npx -y @lyhna/mcp &
Set the env vars in the SAME shell that starts the proxy — don't splice the JSON env var through nested quoting (New-Item -ItemType Directory -Force ./workdir | Out-Null $env:LYHNA_API_KEY = '<your key>' $env:LYHNA_PROXY_CONTROL_PORT = '8790' $env:LYHNA_PROXY_UPSTREAM_COMMAND = 'npx' $env:LYHNA_PROXY_UPSTREAM_ARGS_JSON = '["-y","@modelcontextprotocol/server-filesystem","./workdir"]' npx -y @lyhna/mcpStart-Process,cmd /c): the inner quotes get stripped and the proxy refuses to start. The PowerShell block runs the proxy in the foreground: run the supervisor commands of the later steps from a SECOND window and give them the control target explicitly (append--host 127.0.0.1 --port 8790to eachctl/export-pack), or set$env:LYHNA_PROXY_CONTROL_PORTthere first — flags win over environment.
If a default port is taken (8765agent-facing, or your chosen control port): pick free ones withLYHNA_PROXY_HTTP_PORTandLYHNA_PROXY_CONTROL_PORT. Wait for theLYHNA_MCP_READYblock — it prints the RESOLVED agent-facing MCP URL (http://127.0.0.1:8765/mcp/<session>with the defaults) and the supervisor control address; always use those. The agent only ever gets its session URL — it has no verb to open, close, or read a loop; you (the supervisor) drive the control channel.
Scripted launch (single-session agents): the two-window flow above is human-shaped. An agent driving one session can run the proxy from a launcher script that constructs the JSON inside its own shell (consistent with the quoting warning — env set in the parent, child inherits) and records the PID for a clean stop. macOS/Linux, save aslyhna-proxy-start.sh:
The proxy is launched as a direct#!/usr/bin/env bash mkdir -p workdir npm install --no-save --no-audit --no-fund @lyhna/mcp export LYHNA_API_KEY=<your key> export LYHNA_PROXY_CONTROL_PORT=8790 export LYHNA_PROXY_UPSTREAM_COMMAND=npx export LYHNA_PROXY_UPSTREAM_ARGS_JSON='["-y","@modelcontextprotocol/server-filesystem","./workdir"]' node node_modules/@lyhna/mcp/dist/src/bin/cli.js > lyhna-proxy.log 2>&1 & echo $! > lyhna-proxy.pidnodeprocess (installed once, then run fromnode_modules) so the recorded PID is the proxy itself — behind annpxwrapper,$!records the wrapper and a signal to it can orphan the real proxy unsealed. Windows (PowerShell):
TheNew-Item -ItemType Directory -Force ./workdir | Out-Null $env:LYHNA_API_KEY = '<your key>' $env:LYHNA_PROXY_CONTROL_PORT = '8790' $env:LYHNA_PROXY_UPSTREAM_COMMAND = 'npx' $env:LYHNA_PROXY_UPSTREAM_ARGS_JSON = '["-y","@modelcontextprotocol/server-filesystem","./workdir"]' $p = Start-Process npx -ArgumentList '-y','@lyhna/mcp' -PassThru -NoNewWindow -RedirectStandardOutput lyhna-proxy.log $p.Id | Set-Content lyhna-proxy.pidLYHNA_MCP_READYblock lands inlyhna-proxy.log— read the resolved addresses from there. Run the bash launcher withsource(. ./lyhna-proxy-start.sh) so its exports persist in your shell and the laterctl/export-packcommands work without flags — executing it as./lyhna-proxy-start.shkeeps the env in a child shell that exits with the script. Passing--host 127.0.0.1 --port 8790on each supervisor command works from any shell, sourced or not (flags win over environment). The PowerShell block runs inline in your session, so its env persists either way.
Stopping the proxy: close your loops first (the close verb is what seals — step 6), then terminate the listener. macOS/Linux:kill "$(cat lyhna-proxy.pid)"— the PID is the proxy process itself, and SIGTERM also seals any still-open loops and shuts down the spawned upstream. Windows:taskkill /PID $(Get-Content lyhna-proxy.pid) /T— terminates the proxy and its child process tree (a hard stop does not seal; close first). - Open the loop with a sealed scope, then run your agent. The full open payload, inline — save as
open.json(adjust the lane to your task) and send it:{ "cmd": "open", "session_id": "s1", "loop_id": "loop-quickstart-1", "goal": "describe the loop's goal", "scope_class_map": { "read_file": "read", "read_text_file": "read", "list_directory": "read", "directory_tree": "read", "search_files": "read", "get_file_info": "read", "list_allowed_directories": "read", "write_file": "write", "edit_file": "write", "create_directory": "write", "move_file": "write" }, "scope_capsule": { "structural": { "capsule_type": "scope_capsule", "capsule_version": "scope-capsule/v1", "loop_id": "loop-quickstart-1", "goal_hash": "", "privacy_mode": "verified_context", "allowed_action_classes": ["read", "write"] }, "sidecar": { "goal_summary": "describe the loop's goal", "planned_steps": ["read the relevant files", "write the change"] } } }
The response carries the sealednpx -y @lyhna/mcp ctl --file open.jsonscope_ref— the lane this loop is allowed to run in (goal_hashis filled at open). Now point your agent at its session URL — any MCP-capable client works (the session URL is standard Streamable HTTP); for Claude Code, a project.mcp.json:
Every tool call now binds through the hosted gate before it executes. With the preset applied, the agent's{ "mcpServers": { "lyhna-governed": { "type": "http", "url": "http://127.0.0.1:8765/mcp/s1" } } }write_filecalls resolve towrite_text_fileand approve; refusals come back as verdicts (⛔ REFUSED — nothing was executed), not errors — a refusal is the product working. Out-of-scope steps are refused pre-execution and attested. - Close and export — supervisor verbs the agent can never call. Commands take their JSON from a file (shell-quoting-safe, including PowerShell):
# close.json: {"cmd":"close","session_id":"s1","outcome":"COMPLETED","reason":"done"} npx -y @lyhna/mcp ctl --file close.json npx -y @lyhna/mcp export-pack --loop loop-quickstart-1 --out ./proof-pack npx -y @lyhna/mcp handoff ./proof-pack # paste-ready next-agent prompt - Verify on your machine — trust nothing:
npx -y lyhna-verify --chain ./proof-pack/receipts.json
Direct SDK (advanced)
Integrating bind() directly into your own runtime instead of wrapping an MCP server? That path proves one governed action per call and ends at a signed receipt, not the capsule trio — see SDKs → Direct SDK.
For AI Agents
If you are an AI agent: do not decide authority yourself. Call bind() and obey the returned outcome.
- APPROVED → proceed
- ESCALATED → wait for higher authority
- REFUSED → stop
Pricing
| Tier | Volume | Rate |
|---|---|---|
| Free | 0–50K events | $0 |
| Growth | 50K–500K | Available during onboarding |
| Scale | 500K+ | Available during onboarding |
| Enterprise | Custom | Contact — BYOK, VPC, SLA, and attested corpus requirements |
Support
Dashboard: lyhna.com | API: /v1/bind
The model thinks. Lyhna binds.