Skip to main content

Audit Log

Every capability call — successful, denied, or errored — is recorded in an AES-256-GCM encrypted in-memory buffer. The audit log is your source of truth for what agents did and when.

Reading the Log

// All entries (decrypted in-memory, no I/O)
const entries = chain.getAuditLog();

// Summary stats including auth overhead
const stats = chain.getStats();
// {
// agentId, hostId, agentName, hostname,
// totalCalls, successfulCalls, deniedCalls, errorCalls,
// registeredAt,
// authOverhead: { avgMs, maxMs }
// }

Audit Entry Fields

FieldTypeDescription
idstringUnique entry ID
agentIdstringAgent that made the call
agentNamestringHuman-readable agent name
capabilitystringCapability requested
argsobjectSanitized call arguments
result"success" | "denied" | "error"Outcome
denialReasonstring?Set when result === "denied"
jtistringJWT ID (replay protection key)
timestampnumberUnix ms
durationMsnumberExecution time in ms
authOverheadMsnumberTime spent in build + verify

Argument Sanitization

Sensitive fields are automatically redacted before logging:

Keys matching: key, secret, token, password, auth, credential, bearer
→ replaced with "[REDACTED]"

This happens automatically — you don't need to configure it.

Draining

The buffer is capped at 1000 entries (O(1) appends, oldest evicted). Drain it to an external system periodically or on shutdown:

// Drain using the configured exporter
await chain.drain();

// Or override with an ad-hoc exporter
await chain.drain(new ConsoleAuditExporter());

Exporters

Two built-in exporters:

ConsoleAuditExporter

Logs entries to the console. Good for development:

import { ConsoleAuditExporter } from 'agents-chain';

const chain = await AppChain.create({
// ...
auditExporter: new ConsoleAuditExporter(),
});

HttpAuditExporter

Posts entries to an HTTP endpoint:

import { HttpAuditExporter } from 'agents-chain';

const chain = await AppChain.create({
// ...
auditExporter: new HttpAuditExporter({
endpoint: 'https://audit.mycompany.com/ingest',
apiKey: process.env.AUDIT_API_KEY,
}),
});

Custom Exporter

Implement the AuditExporter interface:

const myExporter = {
async export(entries) {
for (const entry of entries) {
await myDatabase.insert('audit_log', entry);
}
},
};

Internals

The buffer is never encrypted per-append. Encryption happens only when flush() or drain() is called. Reads are always in-memory.