Skip to content
3 min read · 690 words

Storage batteries

These batteries persist artifact bytes only — not conversation state

The storage batteries implement the spool reader/writer pattern for SpooledArtifact persistence. They are NOT a replacement for the 25 TurnRunnerConfig storage callbacks. They do not persist Messages, Memories, or ToolCalls. Session state still requires all 25 callbacks. See Bring Your Own Storage.

They store artifact bytes. Nothing else.

When a tool handler returns a string or Uint8Array, the bundled OpenAI/WebLLM adapters currently spool it to in-memory storage. To use these storage batteries instead, build or wire your own executor path using these stores.

If you are running in production, bytes must survive across requests, server restarts, or browser sessions. The storage batteries provide the persistence layer for these artifact bytes.

They do not persist Message records, Memory records, ToolCall metadata, or any of the other primitives in the 25-callback surface. Those are yours to implement. See Bring Your Own Storage.

typescript
import { InMemorySpoolStore } from '@nhtio/adk/batteries/storage/in_memory'

const store = new InMemorySpoolStore()
typescript
import { Disk } from 'flydrive'
import { FSDriver } from 'flydrive/drivers/fs'
import { FlydriveSpoolStore } from '@nhtio/adk/batteries/storage/flydrive'

const disk = new Disk(new FSDriver({ location: '/var/app/artifacts', visibility: 'public' }))
const store = new FlydriveSpoolStore(disk)
typescript
import { OpfsSpoolStore, type OpfsDirectoryHandle } from '@nhtio/adk/batteries/storage/opfs'

const store = new OpfsSpoolStore({
  directory: async () => {
    const root = await navigator.storage.getDirectory()
    return (await root.getDirectoryHandle('agent-artifacts', { create: true })) as OpfsDirectoryHandle
  },
  keyPrefix: 'agent-runs-'
})

In-Memory (@nhtio/adk/batteries/storage/in_memory)

Environment-neutral. Works in Node, browsers, edge runtimes, and workers. Backed by a Map<string, string>.

Binary Data Corrupts Here

InMemorySpoolStore is strictly text-oriented. It forces a UTF-8 decode on whatever you feed it. If you attempt to store arbitrary binary data, it will corrupt. If your tools return binary payloads, use the Flydrive or OPFS batteries.

typescript
import { InMemorySpoolStore } from '@nhtio/adk/batteries/storage/in_memory'

const store = new InMemorySpoolStore()

// Write artifact bytes and get a reader
const reader = store.write(callId, artifactBytes)

// Read previously written bytes
const existingReader = store.read(callId)  // returns InMemorySpoolReader | undefined

write() stores the bytes (converting Uint8Array to a UTF-8 string) and returns an InMemorySpoolReader bound to the stored content. read() returns a reader for a previously stored call ID, or undefined if not found.

Each call to write() or read() returns a fresh reader instance.

Use this for:

  • Unit and integration tests
  • Prototypes and quick CLI spikes
  • Ephemeral environments where artifact loss on process exit is irrelevant

Do not use this for:

  • Production deployments requiring binary data integrity
  • Production deployments where artifacts must survive restarts or scale across multiple server instances

Flydrive (@nhtio/adk/batteries/storage/flydrive)

Node/server runtime; not browser. Requires flydrive as a peer dependency. Backed by a flydrive Disk — which can point at the local filesystem or remote object storage backends (S3, GCS, etc.) via any supported flydrive driver.

Install the peer dependency first:

bash
npm install flydrive
typescript
import { Disk } from 'flydrive'
import { FSDriver } from 'flydrive/drivers/fs'
import { FlydriveSpoolStore } from '@nhtio/adk/batteries/storage/flydrive'

const disk = new Disk(new FSDriver({ location: '/var/app/artifacts', visibility: 'public' }))
const store = new FlydriveSpoolStore(disk)

// Write artifact bytes to the backing store
const reader = await store.write(callId, artifactBytes)

// Read previously written bytes
const existingReader = await store.read(callId)  // FlydriveSpoolReader | undefined

FlydriveSpoolStore is stateless — it owns no in-memory cache. Multiple store instances sharing the same Disk are safe.

FlydriveSpoolReader supports line access, byte-length reporting, line counts, and full decoded string reads. Large artifacts use streaming mode for line/index access, but readAll()/asString() loads the full decoded string into memory.

Use this for:

  • Server-side production deployments
  • Multi-process setups where artifacts need to be shared via a cloud storage driver

OPFS (@nhtio/adk/batteries/storage/opfs)

Browser only. Uses the Origin Private File System — a sandboxed filesystem API available in modern browsers. No network required. Data persists in the browser's origin-private storage.

typescript
import { OpfsSpoolStore, type OpfsDirectoryHandle } from '@nhtio/adk/batteries/storage/opfs'

const store = new OpfsSpoolStore({
  directory: async () => {
    const root = await navigator.storage.getDirectory()
    return (await root.getDirectoryHandle('agent-artifacts', { create: true })) as OpfsDirectoryHandle
  },
  keyPrefix: 'agent-runs-'
})

// Write artifact bytes
const reader = await store.write(callId, artifactBytes)

// Read previously written bytes
const existingReader = await store.read(callId) // OpfsSpoolReader | undefined

Correct API Usage

The constructor accepts the directory callback options configuration. Passing a directory handle to write() or read() is not supported; those methods do not accept it.

OpfsSpoolStore works on the main thread and in Web Worker threads, using different internal code paths for each context. The public API is identical in both.

Use this for:

  • Browser-embedded agents that need artifact persistence across page reloads
  • Progressive web apps or browser extension-based agents

Choosing the Right Battery

These recommendations apply only for spool-byte persistence in production. For the 25-callback storage adapter, go to Bring Your Own Storage.

EnvironmentBattery
Tests and prototypesInMemorySpoolStore
Node server (development)InMemorySpoolStore
Node server (production)FlydriveSpoolStore
BrowserOpfsSpoolStore
Edge / serverlessInMemorySpoolStore (if artifact loss is acceptable)

If your production setup needs artifact bytes to survive across requests or processes, use FlydriveSpoolStore. If your agent runs in the browser, use OpfsSpoolStore. If bytes are ephemeral and you are okay losing them on restart, InMemorySpoolStore is valid for production too — but only if you are not processing binary data.