Skip to content
4 min read · 731 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 27 TurnRunnerConfig storage callbacks. They do not persist Messages, Memories, or ToolCalls. Session state still requires all 27 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 spool it under the tool call's id and hand the model a SpooledArtifact. By default this goes to a fresh per-dispatch InMemorySpoolStore; pass the spoolStore option to back it with a durable store instead:

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

const adapter = new OpenAIChatCompletionsAdapter({
  model: 'gpt-5.5',
  spoolStore: new OpfsSpoolStore({ directory: async () => navigator.storage.getDirectory() }),
})

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 27-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. Stores bytes byte-faithfully in a Map<string, Uint8Array>.

Binary is stored faithfully — but it lives in process memory

InMemorySpoolStore stores raw bytes intact (binary payloads round-trip without corruption); InMemorySpoolReader decodes on demand for line/text reads. The catch is durability, not fidelity: everything lives in process memory and is lost on exit. For artifacts that must survive restarts, 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 = await store.write(callId, artifactBytes)

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

write() accepts a string (UTF-8-encoded), a Uint8Array (stored verbatim), or a ReadableStream<Uint8Array> (drained to a buffer — the stream form resolves asynchronously, so await the result), and returns an InMemorySpoolReader bound to the stored bytes. 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 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 27-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 — it stores binary faithfully; the only caveat is durability.