Storage Provider
⚠️ Experimental — Squad is alpha software. APIs, commands, and behavior may change between releases.
Try this to switch storage backends:
Use SQLite for persistent team state
Try this to see where session data is stored:
Where is my squad data stored?
Try this to build a cloud storage backend:
Create a StorageProvider for Azure Blob Storage
All of Squad’s data — sessions, decisions, agent memories, event logs — flows through a pluggable storage interface. Pick the provider that matches your deployment: filesystem, database, or cloud.
What is StorageProvider?
StorageProvider is Squad’s I/O contract. Every read, write, delete, and directory operation goes through this interface. This decoupling means:
- Local development uses the filesystem. Sessions and state live in
.squad/. - Testing uses in-memory storage. No disk I/O, no test pollution.
- Production can use SQLite, cloud storage, or a database.
- Multi-team deployments can route different squads to different backends.
The interface is minimal — just 12 core async methods1:
read(filePath: string): Promise<string | undefined>
write(filePath: string, data: string): Promise<void>
append(filePath: string, data: string): Promise<void>
exists(filePath: string): Promise<boolean>
list(dirPath: string): Promise<string[]>
delete(filePath: string): Promise<void>
deleteDir(dirPath: string): Promise<void>
isDirectory(targetPath: string): Promise<boolean>
mkdir(dirPath: string, options?: { recursive?: boolean }): Promise<void>
rename(oldPath: string, newPath: string): Promise<void>
copy(srcPath: string, destPath: string): Promise<void>
stat(targetPath: string): Promise<StorageStats | undefined>
Built-in Providers
FSStorageProvider
What it is: Node.js filesystem wrapper. Standard, portable, no setup.
When to use it:
- Local development
- Single-machine deployments
- Monorepo setups where squad data is part of the project
How it works: Maps all Squad paths to disk directories. Create parent directories on write. Returns undefined on ENOENT instead of throwing.
InMemoryStorageProvider
What it is: HashMap-backed, zero I/O.
When to use it:
- Unit tests for agent logic
- Ephemeral sessions that don’t need persistence
- CI environments where
.squad/is discarded
How it works: All paths stored in memory as POSIX strings. Fast, isolated, perfect for test fixtures.
SQLiteStorageProvider
What it is: SQLite-backed provider using sql.js (WASM). Single .db file, cross-platform.
When to use it:
- Small to medium teams
- Need a portable database file
- Windows/Linux/Mac without platform-specific binaries
How it works: Stores file content in a files(path, content, updated_at) table. sql.js runs entirely in WASM — no native compilation, no dependencies.
| Feature | FSStorageProvider | InMemoryStorageProvider | SQLiteStorageProvider |
|---|---|---|---|
| Persistence | Disk | None (ephemeral) | Single .db file |
| Setup | None | None | 1 import + init |
| Speed | Disk I/O latency | Instant (memory) | Query overhead |
| Portability | Windows/Linux/Mac | Yes | Yes |
| Suitable for | Development, production | Tests | Portable deployments |
Create a Custom Provider
Implement the StorageProvider interface to plug in any backend. Here’s a skeleton:
import type { StorageProvider, StorageStats } from '@bradygaster/squad-sdk';
export class MyCustomStorageProvider implements StorageProvider {
async read(filePath: string): Promise<string | undefined> {
// Fetch from your backend (S3, Cosmos, etc.)
// Return undefined if not found
}
async write(filePath: string, data: string): Promise<void> {
// Write to your backend
// Create parent directories as needed
}
async append(filePath: string, data: string): Promise<void> {
// Append to a file, creating it if missing
}
async exists(filePath: string): Promise<boolean> {
// Check if path exists
}
async list(dirPath: string): Promise<string[]> {
// Return entry names in directory
// Return empty array if directory doesn't exist
}
async delete(filePath: string): Promise<void> {
// Delete a file (no-op if missing)
}
async deleteDir(dirPath: string): Promise<void> {
// Recursively delete directory (no-op if missing)
}
async isDirectory(targetPath: string): Promise<boolean> {
// Return true if path is a directory
}
async mkdir(dirPath: string, options?: { recursive?: boolean }): Promise<void> {
// Create directory
}
async rename(oldPath: string, newPath: string): Promise<void> {
// Move/rename file or directory
}
async copy(srcPath: string, destPath: string): Promise<void> {
// Copy file, creating parent dirs for destination
}
async stat(targetPath: string): Promise<StorageStats | undefined> {
// Return file metadata: size, mtime, isDirectory
// Return undefined if path doesn't exist
}
// Sync variants (deprecated, but still required for Wave 1 compat)
readSync(filePath: string): string | undefined { /* ... */ }
writeSync(filePath: string, data: string): void { /* ... */ }
appendSync(filePath: string, data: string): void { /* ... */ }
existsSync(filePath: string): boolean { /* ... */ }
listSync(dirPath: string): string[] { /* ... */ }
deleteSync(filePath: string): void { /* ... */ }
deleteDirSync(dirPath: string): void { /* ... */ }
isDirectorySync(targetPath: string): boolean { /* ... */ }
mkdirSync(dirPath: string, options?: { recursive?: boolean }): void { /* ... */ }
renameSync(oldPath: string, newPath: string): void { /* ... */ }
copySync(srcPath: string, destPath: string): void { /* ... */ }
statSync(targetPath: string): StorageStats | undefined { /* ... */ }
}
Pass it to the runtime:
import { SquadClient } from '@bradygaster/squad-sdk';
const client = new SquadClient({
storageProvider: new MyCustomStorageProvider(),
teamRoot: '.squad',
});
See storage-provider-azure and storage-provider-sqlite samples for complete, production-ready implementations.
Choose the Right Provider
| Goal | Provider |
|---|---|
| Local development | FSStorageProvider |
| Unit testing agents | InMemoryStorageProvider |
| Small team, portable DB | SQLiteStorageProvider |
| Scale across multiple machines | Custom provider (your database, blob store, or message queue) |
| Azure Blob Storage | Use storage-provider-azure sample as reference |
| DynamoDB, Firestore, etc. | Implement StorageProvider — the interface maps cleanly |
Sample Projects
- storage-provider-sqlite — Complete SQLite implementation using sql.js
- storage-provider-azure — Azure Blob Storage backend with connection pooling
Both live in /samples and demonstrate patterns for production providers.
Footnotes
-
The full interface also includes 12 deprecated synchronous variants (
readSync,writeSync,appendSync,existsSync,listSync,deleteSync,deleteDirSync,isDirectorySync,mkdirSync,renameSync,copySync,statSync) — 24 methods total. The sync methods exist for backward compatibility and will be removed in Wave 2. New code should use the async methods exclusively. ↩