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:

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.

  1. 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.
  2. Save your API key (shown once) as LYHNA_API_KEY.
  3. 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 mapping mcp/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_session cookie (from signup or POST /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"}]}
    Canonical actions may work directly; raw MCP tool names may require a runtime mapping. Any tool not yet mapped is held, not executed — that's the gate; map it from the dashboard's Runtime mappings screen.
  4. Start the governed proxy over a scratch directory — both blocks run as written.
    macOS / Linux (mkdir workdir first):
    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 &
    Windows (PowerShell) — unix sockets are POSIX-only, so the control channel is a loopback TCP port here:
    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/mcp
    Set the env vars in the SAME shell that starts the proxy — don't splice the JSON env var through nested quoting (Start-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 8790 to each ctl/export-pack), or set $env:LYHNA_PROXY_CONTROL_PORT there first — flags win over environment.
    If a default port is taken (8765 agent-facing, or your chosen control port): pick free ones with LYHNA_PROXY_HTTP_PORT and LYHNA_PROXY_CONTROL_PORT. Wait for the LYHNA_MCP_READY block — 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 as lyhna-proxy-start.sh:
    #!/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.pid
    The proxy is launched as a direct node process (installed once, then run from node_modules) so the recorded PID is the proxy itself — behind an npx wrapper, $! records the wrapper and a signal to it can orphan the real proxy unsealed. Windows (PowerShell):
    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"]'
    $p = Start-Process npx -ArgumentList '-y','@lyhna/mcp' -PassThru -NoNewWindow -RedirectStandardOutput lyhna-proxy.log
    $p.Id | Set-Content lyhna-proxy.pid
    The LYHNA_MCP_READY block lands in lyhna-proxy.log — read the resolved addresses from there. Run the bash launcher with source (. ./lyhna-proxy-start.sh) so its exports persist in your shell and the later ctl/export-pack commands work without flags — executing it as ./lyhna-proxy-start.sh keeps the env in a child shell that exits with the script. Passing --host 127.0.0.1 --port 8790 on 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).
  5. 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"]
        }
      }
    }
    npx -y @lyhna/mcp ctl --file open.json
    The response carries the sealed scope_ref — the lane this loop is allowed to run in (goal_hash is 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:
    {
      "mcpServers": {
        "lyhna-governed": { "type": "http", "url": "http://127.0.0.1:8765/mcp/s1" }
      }
    }
    Every tool call now binds through the hosted gate before it executes. With the preset applied, the agent's write_file calls resolve to write_text_file and 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.
  6. 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
  7. 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.

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.