Harness integration

Your agents run in your harness — a custom loop, an agent framework, Claude Code, anything. Eniyan governs them wherever that is: the SDK and the MCP server wrap your existing loop with identity verification, live scope decisions, JIT credential windows, and self-reported run telemetry. Eniyan never hosts or observes the loop itself.

The telemetry contract: metadata only

A run is one execution of your agent’s loop, reported by the agent itself — the attestation model extended to per-step granularity. Runs and steps carry metadata only: tool names, scopes, token counts, and outcomes. Tool arguments, tool results, prompts, and model output are never accepted; long labels are truncated server-side rather than stored. Run history is retained for 180 days; runs left open are closed as abandoned after 24 hours.

The runs API

Three machine endpoints under your org API key drive the lifecycle:

  • POST /v1/runs — start (agent id, harness label, optional model and JIT task link).
  • POST /v1/runs/{run_id}/steps — append steps, singly or batched (kinds: model_call · tool_call · grant · policy_decision · note). Step order is server-assigned; token counts accumulate onto the run.
  • POST /v1/runs/{run_id}/finish — complete or fail the run (finishing twice returns RUN_ALREADY_FINISHED).

A step may reference the access grant it consumed — the grant record gains the run breadcrumb, tying per-action authorization to the work it authorized. agent.run_started and agent.run_finished webhooks fire on the lifecycle, and runs appear in the dashboard under Agents → Runs.

Python SDK: governed_run

pip install eniyan and wrap your loop:

from eniyan import EniyanClient, governed_run

client = EniyanClient(
    base_url="https://api.eniyantrust.com",
    api_key=..., credential_token=..., agent_id=...,
)

with governed_run(client, harness="my-loop", jit=True,
                  scopes=["crm:read"]) as run:
    fetch = run.tool("fetch_accounts", scope="crm:read")(fetch_accounts)
    accounts = fetch()
    run.model_call("claude-sonnet-5", input_tokens=1200, output_tokens=300)
  • Per-tool scope gating — each run.tool call checks the agent’s live permitted set (roles, live delegations, approved exceptions — whatever your org policy composes). Under block enforcement a refusal raises EniyanScopeRefused before the tool executes; advisory mode proceeds and flags the violation server-side.
  • Crash-safe accounting — on exit, success or exception, buffered steps flush, the run finishes (failed on a raise), and the JIT window closes and attests. The cycle is the same one Eniyan’s own internal review agent runs in production.

The MCP server

For MCP-capable harnesses (Claude Code, the Claude Agent SDK, and others), install pip install "eniyan[mcp]" and register eniyan-mcp — governance becomes a config block, not code:

{
  "mcpServers": {
    "eniyan": {
      "command": "eniyan-mcp",
      "env": {
        "ENIYAN_API_URL": "https://api.eniyantrust.com",
        "ENIYAN_API_KEY": "...",
        "ENIYAN_CREDENTIAL_TOKEN": "...",
        "ENIYAN_AGENT_ID": "..."
      }
    }
  }
}

The server exposes scope checks, JIT window open/complete/attest, run recording, and the scope-exception request as tools. Refusals come back as results with guidance (not errors), so the model can read why it was refused and route the request to a human — a refused scope points at the Agent RBAC exception workflow, where an approver decides in the dashboard.

Governed local files (eniyan-fs)

eniyan-fs is a second MCP server in the same package: a governed gate for the agent’s access to files on YOUR machine. Agents read, write, list, and search only inside the roots you configure (ENIYAN_FS_ROOTS="projects=/abs/path"), enforced locally with realpath containment (symlinks and .. can’t escape) and a built-in denylist for key material (.ssh, .env, keys). On top of the local boundary, every access is policy-checked against Eniyan — org policy, protected folders, audit, and the kill switch: revoke the agent’s credential in the dashboard and the next access is refused within the decision-cache window (default 60 seconds, configurable to per-access).

  • Metadata only, always: the cloud sees root aliases and path hashes with an operation label — never file contents, never full paths.
  • Fail closed: writes and deletes refuse when Eniyan is unreachable; deletion is additionally double-gated (an org scope AND an explicit local opt-in).
  • Pair it with Govern your local agents in 10 minutes for the end-to-end setup.

Governed web access (eniyan-web)

eniyan-web is the web sibling of eniyan-fs: agents reach ONLY the websites allowlisted on the agent’s dashboard page, with per-site abilities mapped to request effect — read (GET), write (POST), edit (PUT/PATCH/DELETE) — plus a downloads switch. The allowlist lives in the cloud and syncs to the running gate within a minute; enforcement is server-side on every grant, always hard, and the same credential revocation is the kill switch. Two engines share the permission model: the HTTP fetch engine (the personal-account default) and a real-browser engine for sign-in flows, posting, and purchasing (the enterprise default; personal accounts can switch it on per agent — it needs pip install "eniyan[browser]").

  • Injection screening, locally: page content is screened on your machine before the agent sees it — hidden instructions, smuggled characters, and exfiltration-shaped content are blocked or stripped (risk-based by default: block when the agent can write, edit, or download; strip-and-warn when read-only), and everything delivered arrives wrapped as untrusted data. Page content never reaches Eniyan; a flagged page produces only a metadata event (domain + signal codes) in the audit log.
  • Metadata only, always: the cloud sees domains and URL hashes with a method label — never page content, never full URLs (query strings never leave the machine, even in readable-audit mode).
  • Fail closed: writes, edits, and downloads refuse when Eniyan is unreachable; every redirect hop is re-checked against the allowlist; private and internal addresses are unreachable by construction; downloads are double-gated (the dashboard switch AND an explicit local opt-in).

Shopping (browser engine)

Four tools run the purchase ceremony: request_purchase (amount + the store the gate is actually on), check_purchase (poll the approval), fill_payment (one-shot: consumes the approval and injects the locally-vaulted card — values never enter the model’s context), and report_purchase (the executed amount, or failed/abandoned which releases the budget). The site ability is web:pay (per-site, dashboard-managed); after a fill, the checkout submit itself is gated as web:pay — cloud-validated per access, never cached, carrying the purchase reference — until the outcome is reported. A gate without shopping limits refuses with web_payments_disabled.

Web transaction execution (0.5.0)

Three more tools execute an org’s existing transaction authority at web checkouts and venues when the org opts in: authorize_transaction (dispatches on the ref — an approved purchase, a consumer delegation, or a trading mandate), check_transaction (consumer step-up polling: relay the consumer’s challenge link through your platform, respect retry_after), and report_transaction (executed amounts flip the server-side hold; failed/abandoned under a minted authority makes no server call — the unused token just expires). Fill-less is first-class: the authorization gates the submit itself, so merchant-saved cards, wallet buttons (Apple/Google Pay on the site), and payment methods on file all work with no card anywhere near Eniyan or the model. The locally-vaulted card is only for stores where the card must be typed — the vault is cross-platform (macOS Keychain built in; Windows/Linux via the eniyan[card] extra). Gates run on desktops; phone-native wallet sheets are out of scope. The minted token never enters tool results or model context, and trading refusals are final — mandate math only, either surface. With the relevant org toggle off, authorize_transaction refuses with web_consumer_execution_disabled or web_trading_execution_disabled — the guidance in the result points the operator at Settings → Consumer Delegation or the Trading page; an unknown ref prefix refuses as web_unknown_authority.

The control room

Agents → Runs shows the org-wide run stream with status, harness, token totals, and expandable step timelines; each agent’s detail page carries its own recent runs beside its roles and delegations. Subscribe to the run webhooks to mirror the stream into your own observability stack.

Related: Agent RBAC (the authority model runs are governed by), Security model (what Eniyan deliberately does not do).