Querying
Cross-session query APIs enable you to build admin dashboards, usage analytics, multi-tenant management, and monitoring tools. List sessions by user or agent type, query usage metrics across conversations, and browse memory entities — all with consistent pagination and filtering.
Shared Pagination Contract
All query methods return paginated results with a consistent structure:
interface PaginatedResult {
total: number; // Total count across all pages
offset: number; // Current offset
limit: number; // Requested page size
hasMore: boolean; // Whether more results exist
}The limit parameter defaults to 50 and has a maximum of 200. Use offset for offset-based pagination:
// First page
const page1 = await stateStore.listSessions({ limit: 50, offset: 0 });
// Next page
const page2 = await stateStore.listSessions({ limit: 50, offset: 50 });
// Check if more pages exist
if (page1.hasMore) {
// Load more...
}Listing Sessions
The listSessions() method on SessionStateStore provides powerful filtering for browsing conversations. Use it to build admin dashboards, analytics views, or debug tools.
Basic Usage
import { RedisStateStore } from '@helix-agents/store-redis';
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
const stateStore = new RedisStateStore(redis);
// List all sessions
const result = await stateStore.listSessions();
console.log(`Found ${result.total} sessions`);
for (const session of result.sessions) {
console.log(`${session.sessionId}: ${session.agentType} - ${session.status}`);
}Filtering Options
interface ListSessionsOptions {
userId?: string; // Filter by user
agentType?: string; // Filter by agent type
status?: SessionStatus | SessionStatus[]; // Filter by status
parentSessionId?: string | null; // Children of parent, or null for root sessions
tags?: string[]; // Must have all specified tags (AND logic)
metadata?: Record<string, string>; // Must match all key-value pairs (AND logic)
dateRange?: {
from?: number; // Inclusive lower bound (epoch ms)
to?: number; // Inclusive upper bound (epoch ms)
field?: 'createdAt' | 'updatedAt'; // Which timestamp to filter (default: createdAt)
};
orderBy?: {
field: 'createdAt' | 'updatedAt';
direction: 'asc' | 'desc';
};
offset?: number; // Pagination offset (default: 0)
limit?: number; // Page size (default: 50, max: 200)
includeMessageCount?: boolean; // Include message count (default: true)
}Example Queries
// Find active sessions for a specific user
const userSessions = await stateStore.listSessions({
userId: 'user-123',
status: 'active',
});
// Find failed sessions in the last hour
const recentFailures = await stateStore.listSessions({
status: 'failed',
dateRange: {
from: Date.now() - 3600000,
field: 'updatedAt',
},
});
// Find root sessions (no parent) for a specific agent type
const rootResearchers = await stateStore.listSessions({
agentType: 'researcher',
parentSessionId: null,
});
// Find sessions with specific tags
const taggedSessions = await stateStore.listSessions({
tags: ['production', 'high-priority'],
});
// Find sessions with custom metadata
const customerSessions = await stateStore.listSessions({
metadata: {
environment: 'production',
customerId: 'cust-456',
},
});
// Sort by most recently updated
const recent = await stateStore.listSessions({
orderBy: { field: 'updatedAt', direction: 'desc' },
limit: 20,
});SessionSummary Fields
Each summary includes lightweight metadata without the full state:
| Field | Type | Description |
|---|---|---|
sessionId | string | Session identifier |
agentType | string | Agent type name |
status | SessionStatus | Current session status |
userId | string? | User ID if set |
tags | string[]? | Tags if set |
metadata | Record? | Metadata key-value pairs if set |
parentSessionId | string? | Parent session ID if this is a sub-agent |
stepCount | number | Total LLM call count |
resumeCount | number | Number of resume operations |
messageCount | number | Total message count |
createdAt | number | Creation timestamp (epoch ms) |
updatedAt | number | Last update timestamp (epoch ms) |
To get the full state including customState and output, call loadState(sessionId).
Querying Usage
The queryUsage() method on UsageStore enables cross-session usage queries for cost tracking, analytics, and monitoring.
Basic Usage
import { RedisUsageStore } from '@helix-agents/store-redis';
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
const usageStore = new RedisUsageStore(redis);
// Query all usage entries
const result = await usageStore.queryUsage({});
console.log(`Found ${result.total} usage entries`);
for (const entry of result.entries) {
console.log(`${entry.kind} - ${entry.timestamp}`);
}Filtering Options
interface QueryUsageOptions {
sessionId?: string | string[]; // Filter by session(s)
agentType?: string; // Filter by agent type
userId?: string; // Filter by user
kinds?: Array<'tokens' | 'tool' | 'subagent' | 'custom'>; // Filter by entry kind
dateRange?: {
from?: number; // Inclusive lower bound (epoch ms)
to?: number; // Inclusive upper bound (epoch ms)
};
orderBy?: {
field: 'timestamp' | 'sessionId';
direction: 'asc' | 'desc';
};
offset?: number; // Pagination offset (default: 0)
limit?: number; // Page size (default: 50, max: 200)
}Example Queries
// Get token usage for a specific user
const userTokens = await usageStore.queryUsage({
userId: 'user-123',
kinds: ['tokens'],
});
// Get failed tool calls across all sessions
const failedTools = await usageStore.queryUsage({
kinds: ['tool'],
});
const failures = failedTools.entries.filter(e => 'success' in e && !e.success);
// Get usage for multiple specific sessions
const sessions = await usageStore.queryUsage({
sessionId: ['session-1', 'session-2', 'session-3'],
});
// Get usage in a date range
const lastWeek = await usageStore.queryUsage({
dateRange: {
from: Date.now() - 7 * 24 * 60 * 60 * 1000,
to: Date.now(),
},
orderBy: { field: 'timestamp', direction: 'desc' },
});
// Get custom metrics for a specific agent type
const apiCalls = await usageStore.queryUsage({
agentType: 'researcher',
kinds: ['custom'],
});Cost Tracking Example
Aggregate token usage across sessions for cost reporting:
async function calculateMonthlyCost(userId: string): Promise<number> {
const startOfMonth = new Date();
startOfMonth.setDate(1);
startOfMonth.setHours(0, 0, 0, 0);
const result = await usageStore.queryUsage({
userId,
kinds: ['tokens'],
dateRange: { from: startOfMonth.getTime() },
});
const PRICING: Record<string, { input: number; output: number }> = {
'gpt-4o': { input: 0.0025, output: 0.01 },
'gpt-4o-mini': { input: 0.00015, output: 0.0006 },
};
let totalCost = 0;
for (const entry of result.entries) {
if (entry.kind === 'tokens') {
const pricing = PRICING[entry.model];
if (pricing) {
totalCost += ((entry.tokens.prompt ?? 0) / 1000) * pricing.input;
totalCost += ((entry.tokens.completion ?? 0) / 1000) * pricing.output;
}
}
}
return totalCost;
}Listing Memory Entities
The listEntities() method on MemoryStore returns distinct entity/source pairs with memory counts. Use this to browse users or contexts that have stored memories.
Basic Usage
import { RedisMemoryStore } from '@helix-agents/memory-redis';
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
const memoryStore = new RedisMemoryStore({ redis, dimensions: 1536 });
await memoryStore.initialize();
// List all entities
const result = await memoryStore.listEntities();
console.log(`Found ${result.total} entity/source pairs`);
for (const entity of result.entities) {
console.log(`${entity.entityId} (${entity.sourceName}): ${entity.memoryCount} memories`);
}Filtering Options
interface ListEntitiesOptions {
sourceName?: string; // Filter by source name
metadataFilter?: Record<string, unknown>; // Filter entities with matching metadata
orderBy?: {
field: 'entityId' | 'memoryCount';
direction: 'asc' | 'desc';
};
offset?: number; // Pagination offset (default: 0)
limit?: number; // Page size (default: 50, max: 200)
}EntitySummary Fields
Each summary provides aggregate information about an entity:
| Field | Type | Description |
|---|---|---|
entityId | string | Entity identifier |
sourceName | string | Memory source name |
memoryCount | number | Total memory count |
oldestMemoryAt | string | Oldest memory timestamp (ISO) |
newestMemoryAt | string | Newest memory timestamp (ISO) |
Example Queries
// List entities for a specific source
const userPrefs = await memoryStore.listEntities({
sourceName: 'user-prefs',
});
// Sort by memory count
const mostActive = await memoryStore.listEntities({
orderBy: { field: 'memoryCount', direction: 'desc' },
limit: 10,
});
// Filter by metadata
const approvedEntities = await memoryStore.listEntities({
metadataFilter: { approved: true },
});Store Compatibility
Not all stores support all query methods. Durable Object stores throw errors for cross-session queries because DOs are scoped to a single session.
| Store | listSessions | queryUsage | listEntities |
|---|---|---|---|
InMemoryStateStore | ✓ | - | - |
InMemoryUsageStore | - | ✓ | - |
InMemoryMemoryStore | - | - | ✓ |
RedisStateStore | ✓ | - | - |
RedisUsageStore | - | ✓ | - |
RedisMemoryStore | - | - | ✓ |
PostgresStateStore | ✓ | - | - |
PostgresUsageStore | - | ✓ | - |
D1StateStore | ✓ | - | - |
D1UsageStore | - | ✓ | - |
CloudflareMemoryStore | - | - | ✓ |
DOStateStore | ✗ | - | - |
DOUsageStore | - | ✗ | - |
Attempting to call a query method on a DO store will throw an error explaining that the method is not supported in the DO environment.
Contract Tests
The framework provides contract test suites to validate store implementations:
import { memoryStoreContractTests, usageStoreContractTests } from '@helix-agents/core/testing';
// Test your memory store implementation
memoryStoreContractTests({
name: 'MyMemoryStore',
createStore: async () => ({ store: new MyMemoryStore() }),
clearStore: async (store) => await store.clear(),
});
// Test your usage store implementation
usageStoreContractTests({
name: 'MyUsageStore',
createStore: async () => ({ store: new MyUsageStore() }),
clearStore: async (store) => await store.clear(),
});These suites include tests for listEntities() and queryUsage() to ensure consistent behavior across implementations.
Next Steps
- Usage Tracking - Learn about usage entry types and recording metrics
- Memory - Understand memory sources and retrieval
- State Management - Learn about session state and custom state