The typed primitives that make every agent run inspectable, evaluable, and provable.
Every instrumented agent run becomes a typed graph. A handful of primitives carry everything you need to debug, evaluate, and guardrail:
Eyes captures these primitives. DNA stores them as a typed substrate. Cortex answers questions over them — every answer cited to the node that proves it.
A run is the unit of work the SDK captures. Wrap any handler withruns.start and every action inside becomes a node attached to the run.
import { Invariance } from '@invariance/sdk';
const inv = Invariance.init({ apiKey: process.env.INVARIANCE_API_KEY! });
await inv.runs.start({ name: 'discharge-summary' }, async (run) => {
await run.log('intake', { patientId: 'p-123' });
await run.tool('chart.read', { patientId: 'p-123' }, async () => ({ /* … */ }));
await run.tool('policy.check', { policy: 'discharge' }, async () => ({ ok: true }));
});Every action inside a run produces a node. Each node has a type (tool_call, llm_step, log, …), an input/output payload, timing, and a parent pointer that builds the run's tree.
interface Node {
id: string; // ULID
runId: string;
parentId: string | null;
agentId: string;
actionType: string; // 'tool_call' | 'llm_step' | 'log' | ...
input: unknown;
output?: unknown;
startedAt: number;
endedAt?: number;
anomalyScore?: number; // 0..1, set by monitors
}A session groups the receipts produced by a run into an ordered, hash-chained sequence. Sessions are how you verify a run wasn't tampered with after the fact.
const session = inv.session(); // lazy
const session = await inv.createSession(); // eager
await session.record('tool_call', {
input: { tool: 'chart.read', patientId: 'p-123' },
output: { /* … */ },
});
await session.close();Sessions have three statuses:
A monitor is a rule that runs against runs or nodes. When the rule fires it emits a finding — a typed signal with severity, the node(s) that produced it, and an optional review queue entry.
Evals use the same primitive: pass/fail of an eval case is derived from the findings the run produces.
Under every session is a hash-chained, Ed25519-signed sequence of receipts. Each receipt commits to a SHA-256 hash of its action data plus the previous receipt's hash. The first receipt anchors with "0".
Receipt 1: hash = SHA256(data₁ + "0")
Receipt 2: hash = SHA256(data₂ + hash₁)
Receipt 3: hash = SHA256(data₃ + hash₂)Every receipt hash is signed with the agent's Ed25519 private key via @noble/ed25519. Keys are produced by Invariance.generateKeypair() — a 32-byte private key plus its 32-byte public key.
import { Invariance } from '@invariance/sdk';
const { privateKey, publicKey } = Invariance.generateKeypair();
// privateKey: 64-char hex (32 bytes)
// publicKey: 64-char hex (32 bytes)verifyChain() walks the receipts in order, recomputes each hash from the canonical action data plus the previous hash, and verifies the Ed25519 signature against the agent's public key. A single failure anywhere marks the entire session as tampered.
import { verifyChain } from '@invariance/sdk';
const result = await verifyChain(receipts, publicKey);
if (result.valid) {
console.log('Chain intact — all hashes and signatures verified');
} else {
console.error('Tamper detected at receipt:', result.brokenAt);
console.error('Reason:', result.reason);
}