The @agentdoor/core storage module provides a unified AgentStore interface with multiple backend implementations for persisting agent data, challenges, and authentication state.
AgentStore Interface
All storage backends implement this interface:
import type { AgentStore } from "@agentdoor/core/storage/interface" ;
Agent CRUD Operations
createAgent
Create a new agent record.
const agent = await store . createAgent ({
id: "ag_abc123" ,
publicKey: "base64-encoded-key" ,
x402Wallet: "0x..." ,
scopesGranted: [ "weather.read" ],
apiKeyHash: "sha256-hash" ,
rateLimit: { requests: 1000 , window: "1h" },
metadata: { framework: "langchain" , version: "0.1.0" }
});
Agent creation data. Show CreateAgentInput properties
Unique agent ID (generated by generateAgentId()).
Base64-encoded Ed25519 public key.
Optional x402 wallet address.
Array of granted scope IDs.
SHA-256 hash of the API key (hex encoded).
Rate limit configuration for this agent.
metadata
Record<string, string>
required
Agent metadata (framework, version, name, etc.).
The created Agent record with all fields populated.
Throws: DuplicateAgentError if public key or wallet already exists.
getAgent
Get an agent by its ID.
const agent = await store . getAgent ( "ag_abc123" );
if ( ! agent ) {
console . log ( "Agent not found" );
}
Agent ID (e.g. "ag_abc123").
The Agent record, or null if not found.
getAgentByApiKeyHash
Look up an agent by the hash of their API key. Used for authenticating requests with Bearer API keys.
import { hashApiKey } from "@agentdoor/core" ;
const apiKeyHash = hashApiKey ( apiKeyFromRequest );
const agent = await store . getAgentByApiKeyHash ( apiKeyHash );
SHA-256 hex hash of the API key.
The Agent record, or null if not found.
getAgentByPublicKey
Look up an agent by their public key. Used to check for duplicate registrations.
const existing = await store . getAgentByPublicKey ( publicKey );
if ( existing ) {
throw new Error ( "Agent already registered" );
}
Base64-encoded Ed25519 public key.
The Agent record, or null if not found.
updateAgent
Update an existing agent record.
const updated = await store . updateAgent ( "ag_abc123" , {
scopesGranted: [ "weather.read" , "weather.write" ],
reputation: 75 ,
lastAuthAt: new Date (),
incrementRequests: 1
});
Fields to update. All fields are optional. Show UpdateAgentInput properties
Update reputation score (0-100).
Update metadata (merged with existing).
Update agent status ("active", "suspended", or "banned").
Update last auth timestamp.
Increment total request count by this amount.
Increment total x402 paid amount by this amount.
The updated Agent record.
Throws: AgentNotFoundError if agent doesn’t exist.
deleteAgent
Delete an agent record.
const deleted = await store . deleteAgent ( "ag_abc123" );
if ( deleted ) {
console . log ( "Agent deleted" );
}
true if the agent was deleted, false if not found.
Challenge Management
createChallenge
Store a registration challenge.
await store . createChallenge ({
agentId: "ag_abc123" ,
nonce: "random_nonce" ,
message: "agentdoor:register:ag_abc123:1234567890:random_nonce" ,
expiresAt: new Date ( Date . now () + 5 * 60 * 1000 ), // 5 minutes
createdAt: new Date ()
});
getChallenge
Retrieve a challenge by agent ID. Returns the most recent challenge for the agent.
const challenge = await store . getChallenge ( "ag_abc123" );
if ( ! challenge ) {
throw new Error ( "No challenge found" );
}
returns
Promise<ChallengeData | null>
The ChallengeData, or null if not found or expired.
Note: Expired challenges are automatically deleted when accessed.
deleteChallenge
Delete a challenge after successful verification or expiry.
await store . deleteChallenge ( "ag_abc123" );
Agent ID whose challenge to delete.
cleanExpiredChallenges
Remove all expired challenges. Should be called periodically for cleanup.
const cleaned = await store . cleanExpiredChallenges ();
console . log ( `Cleaned ${ cleaned } expired challenges` );
Number of challenges cleaned up.
Lifecycle
close
Close any open connections and clean up resources. Called during server shutdown.
Storage Implementations
MemoryStore
In-memory Map-based implementation for development and testing.
import { MemoryStore } from "@agentdoor/core/storage/memory" ;
const store = new MemoryStore ();
Suitable for:
Development environments
Testing
Prototyping
Serverless environments with short-lived processes
NOT suitable for:
Production deployments (data lost on restart)
Multi-process / clustered environments (no shared state)
Additional methods:
store . agentCount ; // Get total number of registered agents
store . challengeCount ; // Get total number of pending challenges
store . getAllAgents (); // Get all agents (for debugging)
SQLiteStore
SQLite database implementation using better-sqlite3.
import { SQLiteStore } from "@agentdoor/core/advanced/storage" ;
const store = new SQLiteStore ( "./agentdoor.db" );
// or in-memory:
const store = new SQLiteStore ( ":memory:" );
Installation:
npm install better-sqlite3
npm install -D @types/better-sqlite3
Suitable for:
Single-process production deployments
Edge/embedded environments
Local-first applications
NOT suitable for:
Multi-process / clustered deployments (use Postgres instead)
Serverless environments with ephemeral storage
Features:
WAL mode enabled for better concurrent read performance
Foreign keys enforced
Automatic indexes on public_key, api_key_hash, x402_wallet
PostgresStore
PostgreSQL database implementation using pg.
import { PostgresStore } from "@agentdoor/core/storage/postgres" ;
const store = new PostgresStore (
"postgresql://user:pass@localhost/agentdoor"
);
// Or with an existing pool:
import { Pool } from "pg" ;
const pool = new Pool ({ connectionString: process . env . DATABASE_URL });
const store = new PostgresStore ( pool );
// Initialize tables (call once):
await store . initialize ();
Installation:
npm install pg
npm install -D @types/pg
Suitable for:
Multi-process / clustered production deployments
Serverless environments with connection pooling (Neon, Supabase)
High-availability setups
Features:
JSONB columns for scopes and metadata
Automatic indexes on all lookup fields
ON CONFLICT handling for challenge upserts
Connection pooling support
Important: Call await store.initialize() once before using the store to create tables.
Complete Example
import { SQLiteStore } from "@agentdoor/core/advanced/storage" ;
import {
generateAgentId ,
generateApiKey ,
hashApiKey ,
generateKeypair
} from "@agentdoor/core" ;
// Initialize store
const store = new SQLiteStore ( "./agentdoor.db" );
// Create a new agent
const { publicKey , secretKey } = generateKeypair ();
const agentId = generateAgentId ();
const apiKey = generateApiKey ( "live" );
const apiKeyHash = hashApiKey ( apiKey );
try {
const agent = await store . createAgent ({
id: agentId ,
publicKey ,
scopesGranted: [ "weather.read" ],
apiKeyHash ,
rateLimit: { requests: 1000 , window: "1h" },
metadata: { framework: "langchain" , version: "0.1.0" }
});
console . log ( "Agent created:" , agent . id );
// Store a challenge
await store . createChallenge ({
agentId: agent . id ,
nonce: "random_nonce" ,
message: `agentdoor:register: ${ agent . id } : ${ Date . now () } :random_nonce` ,
expiresAt: new Date ( Date . now () + 5 * 60 * 1000 ),
createdAt: new Date ()
});
// Retrieve the challenge
const challenge = await store . getChallenge ( agent . id );
console . log ( "Challenge:" , challenge ?. message );
// Update agent after successful auth
await store . updateAgent ( agent . id , {
lastAuthAt: new Date (),
incrementRequests: 1
});
// Look up by API key
const foundAgent = await store . getAgentByApiKeyHash ( apiKeyHash );
console . log ( "Found agent:" , foundAgent ?. id );
} catch ( error ) {
if ( error . name === "DuplicateAgentError" ) {
console . error ( "Agent already exists" );
} else {
throw error ;
}
} finally {
await store . close ();
}
Periodic Cleanup
For production deployments, set up periodic cleanup of expired challenges:
// Clean up expired challenges every 5 minutes
setInterval ( async () => {
const cleaned = await store . cleanExpiredChallenges ();
if ( cleaned > 0 ) {
console . log ( `Cleaned ${ cleaned } expired challenges` );
}
}, 5 * 60 * 1000 );