Squad Squad

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.

FeatureFSStorageProviderInMemoryStorageProviderSQLiteStorageProvider
PersistenceDiskNone (ephemeral)Single .db file
SetupNoneNone1 import + init
SpeedDisk I/O latencyInstant (memory)Query overhead
PortabilityWindows/Linux/MacYesYes
Suitable forDevelopment, productionTestsPortable 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

GoalProvider
Local developmentFSStorageProvider
Unit testing agentsInMemoryStorageProvider
Small team, portable DBSQLiteStorageProvider
Scale across multiple machinesCustom provider (your database, blob store, or message queue)
Azure Blob StorageUse 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

  1. 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.