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).