Skip to main content
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" }
});
input
CreateAgentInput
required
Agent creation data.
returns
Promise<Agent>
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");
}
id
string
required
Agent ID (e.g. "ag_abc123").
returns
Promise<Agent | null>
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);
apiKeyHash
string
required
SHA-256 hex hash of the API key.
returns
Promise<Agent | null>
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");
}
publicKey
string
required
Base64-encoded Ed25519 public key.
returns
Promise<Agent | null>
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
});
id
string
required
Agent ID.
input
UpdateAgentInput
required
Fields to update. All fields are optional.
returns
Promise<Agent>
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");
}
id
string
required
Agent ID.
returns
Promise<boolean>
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()
});
challenge
ChallengeData
required
Challenge data to store.
returns
Promise<void>
No return value.

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");
}
agentId
string
required
Agent ID.
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");
agentId
string
required
Agent ID whose challenge to delete.
returns
Promise<void>
No return value.

cleanExpiredChallenges

Remove all expired challenges. Should be called periodically for cleanup.
const cleaned = await store.cleanExpiredChallenges();
console.log(`Cleaned ${cleaned} expired challenges`);
returns
Promise<number>
Number of challenges cleaned up.

Lifecycle

close

Close any open connections and clean up resources. Called during server shutdown.
await store.close();
returns
Promise<void>
No return value.

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);

Build docs developers (and LLMs) love