AERDocumentation

Guide

Recording from your own code

When the drop-in collector cannot see what your agent does, or you want precise control over what is recorded, emit events yourself with the SDK. Same wire protocol, same signed record, same bodies-off contract.

Install

npm install @adastracomputing/aer-sdk-ts

The Python SDK is stdlib only and needs Python 3.10 or newer. It is installed from the client repository rather than PyPI, so there is one source of truth rather than a copy that drifts from the code under test. Drop the tag to track the default branch instead of a release.

pip install "git+https://github.com/Ad-Astra-Computing/aer.git@sdk-py-v0.1.0#subdirectory=packages/sdk-py"

Record a run

import { createAerClient } from '@adastracomputing/aer-sdk-ts';

// Session ids and the ingest token come from POST /v1/sessions
// (tenant API key), or from your orchestrator.
const client = createAerClient({
  baseUrl: 'https://api.aer.run',
  sessionId: sess.agent_session_id,
  ingestToken: sess.ingest_token,
});

await client.emit('tool.started', { tool: 'web_search' });
await client.emit('tool.completed', { tool: 'web_search', ok: true });
await client.emit('llm.completed', {
  model: 'claude-sonnet-4-5', input_tokens: 812, output_tokens: 102, ok: true,
});

const result = await client.complete();
// result.aer_id, result.canonical_hash, result.signing_key_id

The same run in Python:

from aer_sdk import AerClient, create_session

session = create_session(
    base_url="https://api.aer.run",
    tenant_api_key=os.environ["AER_API_KEY"],
    tenant_id=..., agent_id=..., agent_version="1.0.0", environment_id=...,
)

with AerClient(
    base_url="https://api.aer.run",
    session_id=session["agent_session_id"],
    ingest_token=session["ingest_token"],
) as client:
    client.emit("tool.started", {"tool": "web_search"})
    client.emit("tool.completed", {"tool": "web_search", "ok": True})
    client.emit("llm.completed", {
        "model": "claude-sonnet-4-5", "input_tokens": 812, "output_tokens": 102, "ok": True,
    })
    result = client.complete()
    # result["aer_id"], result["canonical_hash"], result["signing_key_id"]

Events buffer in memory and post in batches (500 ms flush by default; size-triggered when a batch fills). Server errors retry with exponential backoff; client errors fail fast and the batch requeues for the next flush, so a transient outage never silently drops events. complete() flushes everything and seals the signed record. abort() is the crash path: the session terminates and no record is generated.

What to emit

The full event taxonomy with required and common fields is documented at aer.run/events. The rule for every payload: metadata only. Model names, token counts, tool names, hosts and paths belong in a record; prompts, completions, tool arguments and response bodies never do. The server enforces this at ingest: unknown payload keys are stripped before storage (the response reports aggregate counts when that happens) and out-of-bounds values reject the event.

Severity hints

await client.emit('guardrail.triggered', { rule: 'egress-denied' }, { severity_hint: 'high' });

severity_hint (info, low, medium, high, critical) feeds the findings pipeline and the console feeds; it never changes what is captured.

Sessions from scratch

POST /v1/sessions      (tenant API key)
→ { agent_session_id, ingest_token }

# then emit events with the ingest token and complete when done

The ingest token is scoped to one session and expires with it; your tenant API key never needs to reach the agent process. Go and Rust follow once the wire protocol stabilises past v1, and any language can speak the raw HTTP API (see the API reference).