Lightweight identity, authentication, and audit layer for AI agent SDKs.
Wrap your OpenAI or Anthropic client with agents-chain to get:
- Ed25519 key-pair identity per agent instance
- JWT-based capability tokens — every API call is signed and verified
- Encrypted in-memory audit log — AES-256-GCM, queryable at runtime
- Zero network calls — everything runs locally, no external service required
npm install agents-chain
# or
pnpm add agents-chainRequires Node.js 18+
import { AgentsChain } from 'agents-chain';
import OpenAI from 'openai';
const chain = await AgentsChain.create({
agentName: 'summarizer',
hostname: 'my-app',
capabilities: ['chat.completion'],
});
const ai = chain.openai(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }));
const response = await ai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: 'Summarize the water cycle in one sentence.' }],
});
console.log(response.choices[0].message.content);import { AgentsChain } from 'agents-chain';
import Anthropic from '@anthropic-ai/sdk';
const chain = await AgentsChain.create({
agentName: 'classifier',
hostname: 'my-app',
capabilities: ['message'],
});
const ai = chain.anthropic(new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }));
const response = await ai.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 256,
messages: [{ role: 'user', content: 'Classify this text as positive or negative: "I love it!"' }],
});
console.log(response.content[0]);const chain = await AgentsChain.create({
agentName: string; // Human-readable name for this agent
hostname: string; // Used to build the agentId: "<hostname>-agent-<32hex>"
capabilities: string[]; // List of capability strings this agent is allowed to use
encryptionKey?: string; // Optional 64-char hex (32-byte) AES-256-GCM key.
// Omit to generate a random key per session.
// Provide to persist and reload audit logs across restarts.
});Every API call through a wrapped client is recorded in an encrypted in-memory log.
// Get all entries (decrypted)
const entries = chain.getAuditLog();
// Full snapshot with metadata
const snapshot = chain.exportAudit();
// { agentId, entries, exportedAt }
// Summary counts
const stats = chain.getStats();
// { agentId, agentName, hostname, totalCalls, successfulCalls, deniedCalls, errorCalls, registeredAt }Each AuditEntry contains:
| Field | Type | Description |
|---|---|---|
id |
string |
Unique entry ID |
agentId |
string |
The agent that made the call |
capability |
string |
Capability used |
result |
"success" | "denied" | "error" |
Outcome |
timestamp |
number |
Unix ms |
meta |
Record<string, unknown> |
Provider-specific metadata |
agents-chain maps SDK method paths to short capability strings. You must use these exact strings in the capabilities array when calling AgentsChain.create() — otherwise the call will be blocked with a ChainAuthError.
| SDK method | Capability string |
|---|---|
ai.chat.completions.create() |
"chat.completion" |
ai.embeddings.create() |
"embedding" |
ai.images.generate() |
"image.generation" |
ai.audio.transcriptions.create() |
"audio.transcription" |
ai.audio.speech.create() |
"audio.speech" |
ai.moderations.create() |
"moderation" |
ai.responses.create() |
"response" |
| SDK method | Capability string |
|---|---|
ai.messages.create() |
"message" |
ai.messages.stream() |
"message.stream" |
ai.messages.countTokens() |
"message.count_tokens" |
ai.completions.create() |
"completion" |
ai.beta.messages.create() |
"message.beta" |
Any SDK method not in these tables passes through without interception.
Low-level utilities are exported if you need direct access:
import {
generateKeyPair,
exportPublicKeyJwk,
exportPrivateKeyJwk,
importPublicKeyJwk,
computeJwkThumbprint,
signJwt,
verifyJwtSignature,
decodeJwtUnsafe,
generateId,
generateAgentId,
base64UrlEncode,
base64UrlDecode,
} from 'agents-chain';MIT — brianmwangidev