In-Memory Storage
The in-memory storage package (@helix-agents/store-memory) provides simple Map-based implementations for development and testing. Data exists only in process memory and is lost when the process exits.
When to Use
Good fit:
- Local development
- Unit tests
- Prototyping
- Single-process deployments where durability isn't needed
Not ideal for:
- Production requiring durability
- Multi-process deployments
- Long-running agents that may outlive the process
Installation
bash
npm install @helix-agents/store-memoryOr use the SDK which includes it:
bash
npm install @helix-agents/sdkInMemoryStateStore
Basic Usage
typescript
import { InMemoryStateStore } from '@helix-agents/store-memory';
const stateStore = new InMemoryStateStore();
// Save state
await stateStore.save({
runId: 'run-123',
agentType: 'researcher',
streamId: 'run-123',
messages: [],
customState: { count: 0 },
stepCount: 0,
status: 'running',
subAgentRefs: [],
aborted: false,
});
// Load state
const state = await stateStore.load('run-123');
console.log(state?.status); // 'running'
// Delete state
await stateStore.delete('run-123');Atomic Operations
typescript
// Append messages (thread-safe within single process)
await stateStore.appendMessages('run-123', [{ role: 'user', content: 'Hello' }]);
// Merge custom state changes
await stateStore.mergeCustomState('run-123', {
values: { count: 5, items: ['new item'] },
arrayReplacements: new Set(), // Arrays to replace, not append
warnings: [],
});
// Update status
await stateStore.updateStatus('run-123', 'completed');
// Increment step count
const newCount = await stateStore.incrementStepCount('run-123');
console.log(newCount); // 1Message Queries
typescript
// Get paginated messages
const result = await stateStore.getMessages('run-123', {
offset: 0,
limit: 10,
includeThinking: false, // Strip thinking content
});
console.log(result.messages); // Message[]
console.log(result.total); // Total count
console.log(result.hasMore); // More pages available?
// Get message count
const count = await stateStore.getMessageCount('run-123');Testing Utilities
The in-memory store includes utilities not in the interface:
typescript
// Get all stored run IDs
const runIds = stateStore.getAllRunIds();
// Clear all state (useful between tests)
stateStore.clear();
// Get count of stored states
console.log(stateStore.size);InMemoryStreamManager
Basic Usage
typescript
import { InMemoryStreamManager } from '@helix-agents/store-memory';
const streamManager = new InMemoryStreamManager();
// Create writer
const writer = await streamManager.createWriter('stream-123', 'run-123', 'researcher');
// Write chunks
await writer.write({
type: 'text_delta',
agentId: 'run-123',
agentType: 'researcher',
timestamp: Date.now(),
delta: 'Hello',
});
// Close writer (NOT the stream)
await writer.close();
// End the stream
await streamManager.endStream('stream-123');Reading Streams
typescript
// Create reader
const reader = await streamManager.createReader('stream-123');
if (reader) {
try {
for await (const chunk of reader) {
console.log(chunk.type, chunk);
}
} finally {
await reader.close();
}
}Stream Lifecycle
typescript
// Stream is created implicitly by first writer
const writer = await streamManager.createWriter('stream-123', 'run-123', 'agent');
// Write events...
await writer.write({
/* chunk */
});
await writer.close();
// Option 1: End stream successfully
await streamManager.endStream('stream-123', { result: 'done' });
// Option 2: Fail stream
await streamManager.failStream('stream-123', 'Something went wrong');Testing Utilities
typescript
// Clear all streams
streamManager.clear();
// Check stream existence
const exists = streamManager.hasStream('stream-123');
// Get all stream IDs
const streamIds = streamManager.getAllStreamIds();
// Get stream status
const status = streamManager.getStreamStatus('stream-123');
// 'active' | 'ended' | 'failed' | undefinedComplete Example
typescript
import { JSAgentExecutor } from '@helix-agents/runtime-js';
import { InMemoryStateStore, InMemoryStreamManager } from '@helix-agents/store-memory';
import { VercelAIAdapter } from '@helix-agents/llm-vercel';
// Create stores
const stateStore = new InMemoryStateStore();
const streamManager = new InMemoryStreamManager();
// Create executor
const executor = new JSAgentExecutor(stateStore, streamManager, new VercelAIAdapter());
// Execute agent
const handle = await executor.execute(MyAgent, 'Research AI');
// Stream results
const stream = await handle.stream();
if (stream) {
for await (const chunk of stream) {
if (chunk.type === 'text_delta') {
process.stdout.write(chunk.delta);
}
}
}
// Get final result
const result = await handle.result();
console.log(result.output);
// Cleanup (optional - for testing)
stateStore.clear();
streamManager.clear();Unit Testing Pattern
typescript
import { describe, it, beforeEach, expect } from 'vitest';
import { InMemoryStateStore, InMemoryStreamManager } from '@helix-agents/store-memory';
describe('MyAgent', () => {
let stateStore: InMemoryStateStore;
let streamManager: InMemoryStreamManager;
beforeEach(() => {
// Fresh stores for each test
stateStore = new InMemoryStateStore();
streamManager = new InMemoryStreamManager();
});
it('should complete successfully', async () => {
const executor = new JSAgentExecutor(stateStore, streamManager, mockLLMAdapter);
const handle = await executor.execute(MyAgent, 'Test input');
const result = await handle.result();
expect(result.status).toBe('completed');
expect(result.output).toBeDefined();
});
it('should track state changes', async () => {
// ... run agent ...
// Verify state was saved
const runIds = stateStore.getAllRunIds();
expect(runIds).toHaveLength(1);
const state = await stateStore.load(runIds[0]);
expect(state?.status).toBe('completed');
});
});Limitations
No Persistence
Data exists only in memory:
typescript
const stateStore = new InMemoryStateStore();
await stateStore.save(state);
// Process restart - all data lost
// After restart:
const loaded = await stateStore.load(state.runId); // nullSingle Process Only
Cannot share state across processes:
Process 1: stateStore.save(state)
Process 2: stateStore.load(runId) // null - different Map instanceNo Real-Time Streaming Across Processes
Streams are local to the process:
Process 1: streamManager.createWriter(...)
Process 2: streamManager.createReader(...) // Different stream instanceMemory Usage
Large numbers of agents or long conversations consume memory:
typescript
// Each run accumulates messages
// No automatic cleanup
// Eventually: OutOfMemoryError
// Solution: Clean up after completion
await handle.result();
await stateStore.delete(handle.runId);Migration to Production
When moving to production, swap to Redis stores:
typescript
// Development
const stateStore =
process.env.NODE_ENV === 'production' ? new RedisStateStore(redis) : new InMemoryStateStore();
const streamManager =
process.env.NODE_ENV === 'production'
? new RedisStreamManager(redis)
: new InMemoryStreamManager();The interface is identical - your code doesn't change.
Next Steps
- Redis Storage - Production-ready persistence
- Cloudflare Storage - Edge deployment
- JavaScript Runtime - Runtime that uses these stores