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
| Field | Type | Description |
|---|---|---|
id | string | Unique entry ID |
agentId | string | Agent that made the call |
agentName | string | Human-readable agent name |
capability | string | Capability requested |
args | object | Sanitized call arguments |
result | "success" | "denied" | "error" | Outcome |
denialReason | string? | Set when result === "denied" |
jti | string | JWT ID (replay protection key) |
timestamp | number | Unix ms |
durationMs | number | Execution time in ms |
authOverheadMs | number | Time 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.