Skip to content
EctoSpace edited this page Nov 13, 2026 · 1 revision

SDKs

Two typed REST clients talk to a running IronVise server (ironvise serve or the desktop app's backend). Both are Apache-2.0, unlike the server (BUSL-1.1), so you can embed them freely.

Python TypeScript / JavaScript
Package ironvise on PyPI ironvise on npm
Install pip install "ironvise>=0.8.0" npm install ironvise@^0.8.0
Import ironvise_sdk ironvise
Client LedgerClient (async) IronViseClient
Framework adapters LangChain, AutoGen none (by design)
Runs on Python with httpx Node 18+, Deno, Bun, browsers with fetch
Full reference sdk/python/README.md sdk/typescript/README.md

Use 0.8.0 or newer: earlier releases predate verify_intent / verifyIntent, which lets an agent ask permission before acting instead of only recording.

Tokens

The server issues bearer tokens with one of three roles:

Role Can
admin Everything, including tokens, webhooks, policies, config and approval decisions
agent Create sessions, append events, ask verify-intent
auditor Read only: every GET endpoint and the event stream; refused on anything that writes

Create agent and auditor tokens on the desktop app's Access screen, or with create_token / createToken using an admin token. The hooks and proxies read the token from ECTOLEDGER_TOKEN and the server address from ECTOLEDGER_URL.

Python

pip install "ironvise>=0.8.0"
pip install "ironvise[langchain]>=0.8.0"   # with the LangChain adapter
pip install "ironvise[autogen]>=0.8.0"     # with the AutoGen adapter

Record a run

import asyncio
from ironvise_sdk import LedgerClient

async def main():
    async with LedgerClient("http://localhost:3000", bearer_token="your_token") as client:
        session = await client.create_session(goal="audit-run-42", record=True)
        await client.append_event(session.session_id, {"observation": "read config"})
        await client.seal_session(session.session_id)

        result = await client.verify_session(session.session_id)
        print(result.ok, result.signatures)

asyncio.run(main())

record=True opens a recorder session that needs no model on the server and stays open until you seal it. Without it, the server runs its own audit loop.

verify_chain returns True only when the chain is intact and every signature verified; verify_session gives the two verdicts separately.

Ask before acting

verdict = await client.verify_intent(sid, "run_command", {"command": cmd}, justification="...")
verdict.raise_if_denied()
subprocess.run(cmd, shell=True)

Runs the same policy, tripwire and guard checks as the agent loop. Nothing is executed or recorded by asking.

LangChain

from ironvise_sdk.langchain import LedgerCallbackHandler

handler = LedgerCallbackHandler(session_id="your-session-uuid", base_url="http://localhost:3000")
agent.invoke({"input": "..."}, config={"callbacks": [handler]})

Fail-closed: a recording failure raises RecordingFailed and stops the tool call. Pass fail_open=True to accept gaps deliberately.

AutoGen (autogen-agentchat 0.4 or newer)

from ironvise_sdk.autogen import LedgerHook

with LedgerHook(session_id="your-session-uuid") as hook:
    agent = AssistantAgent("auditor", model_client=model_client,
                           tools=hook.wrap_tools([read_file, run_scan], agent_name="auditor"))
    await agent.run(task="...")

Each call is recorded before it runs, so a recording failure prevents the action. AutoGen then continues with an error in the agent's context; it is not a halt.

Errors and retries

  • Non-2xx responses raise LedgerSdkError with status_code and body. The message ends with the server's explanation.
  • Connection errors and timeouts reach you as the httpx exception.
  • retries (default 2) applies to GET requests only, on connection errors and 502/503/504. POST, PUT, DELETE and streams are never retried.

TypeScript

npm install ironvise@^0.8.0
import { IronViseClient, assertAllowed } from "ironvise";

const client = new IronViseClient({ baseUrl: "http://localhost:3000", bearerToken: "my-api-token" });

const session = await client.createSession({ goal: "audit-run-42", record: true });
await client.appendEvent(session.id, { event_type: "model_invocation", payload: { model: "gpt-4o" } });

const verdict = await client.verifyIntent(session.id, {
  action: "run_command",
  params: { command: "ls" },
  justification: "...",
});
assertAllowed(verdict);

await client.sealSession(session.id);

Live events:

for await (const evt of client.streamEvents({ session_id: "abc" })) {
  console.log(evt.event_type, evt.payload);
}

Errors and retries

  • Non-2xx responses throw IronViseApiError with .status and .body.
  • Misuse the client can detect (an id of "", "." or "..") throws IronViseUsageError before any request.
  • Network failures and timeouts propagate as TypeError / AbortError.
  • timeoutMs (default 30000) and retries (default 2, GET only) behave like the Python client.

Events you append are unsigned

Events appended through either SDK are hash-chained but not signed: only events the server creates carry an Ed25519 signature. verify_session / verifySession report signed and unsigned counts. See Concepts and Architecture.

Verifying webhooks

Each webhook receiver gets its own key from ironvise webhook-key --url <url>. Both SDKs ship a verifier that refuses deliveries signed for another receiver, timestamps more than 300 seconds off, and replays.

from ironvise_sdk import MemoryReplayCache, verify_webhook

cache = MemoryReplayCache()  # one per process
verdict = verify_webhook(receiver_key=os.environ["WEBHOOK_KEY"], receiver_url="https://siem.example/ironvise",
                         headers=request.headers, body=raw_body, cache=cache)
import { MemoryReplayCache, verifyWebhook } from "ironvise";

const verdict = verifyWebhook({ receiverKey: process.env.WEBHOOK_KEY!, receiverUrl: "https://siem.example/ironvise",
                                headers: req.headers, body: rawBody, cache });

verdict.reason is missing_headers, stale, bad_signature or replayed. Pass the raw body exactly as received. The TypeScript verifier uses node:crypto (Node, Deno, Bun).

Upgrading from the EctoLedger SDKs

  • Python: python -m pip uninstall ectoledger-sdk, then install ironvise and change imports from ectoledger_sdk to ironvise_sdk. This does not touch ledger data or keys.
  • TypeScript: the ectoledger-sdk package and EctoLedgerClient aliases are gone. Use IronViseClient, IronViseApiError and IronViseUsageError.

See Migrating from EctoLedger.

Clone this wiki locally