Storage Adapters
The shipped local and customDir adapters, plus a proposed object-store shape.
Little Harness: published on npm as little-harness@0.2.0-alpha.0 (2026-10-05). Sections labeled UNRELEASED describe source-only changes. Release status
Persistent Dirs are developer-backed. First-party adapters make common stores easy without forcing Little Harness to own every app's data model.
What ships in alpha
localHost, localDir, projectDir, and customDir are exported today. The
s3Dir object-store shape below is proposed — it is not exported yet; use
customDir(...) with your own callbacks to back an object store in the meantime.
Shipped: Local Adapter
localDir() is the shipped filesystem adapter. It maps a harnessDir to a local folder and
is exposed through Little Harness rather than leaking arbitrary host paths to the
model.
import { localDir, projectDir } from "@little-stack/harness";
localDir({
harnessDir: "/persistent/dev",
sourceDir: "persistent/dev-memory",
});Project folders require explicit intent.
localDir({
harnessDir: "/persistent/project-docs",
sourceDir: projectDir("./docs"),
commit: "read-only",
});See the localHost() reference for the
full localDir / projectDir signatures.
Shipped: customDir (app databases and object stores)
customDir() is the shipped, typed helper for backing a harnessDir with your
own storage. It is a thin wrapper over a PersistentDir entry — the same object
you could place directly in persistentDirs — so you get types and intent without
Little Harness owning your data model.
The simplest shape provides load / store (load the whole directory, commit
changes at the commit boundary):
import { customDir } from "@little-stack/harness";
customDir({
harnessDir: "/persistent/user",
load: async ({ extraBody }) => db.files.loadFolder(extraBody.userId),
store: async ({ extraBody, changes }) =>
db.files.applyChanges(extraBody.userId, changes),
});You can also express the store as per-path list / read / write / delete
callbacks instead of load / store:
customDir({
harnessDir: "/persistent/docs",
list: ({ extraBody, path }) => docs.list(extraBody.userId, path),
read: ({ extraBody, path }) => docs.read(extraBody.userId, path),
write: ({ extraBody, path, content }) =>
docs.write(extraBody.userId, path, content),
delete: ({ extraBody, path }) => docs.delete(extraBody.userId, path),
});Note this shape is not lazy yet: at turn start the runtime calls list once
at the root (path: "") and then reads every file entry that single call
returned, before execution. It does not recurse into directory entries —
your root list must return a flattened list of every file (with nested paths
like guides/setup.md) or nested files are silently skipped. Truly lazy loading
(fetching files only when touched) is future work, so keep large stores out of
hot dirs for now. A per-path store still preserves the commit model: it does not
turn every shell write into an immediate backing-store mutation unless the
developer explicitly chooses that behavior.
Proposed: Object Store Adapter
A convenience adapter mapping a harnessDir to a bucket and prefix. This shape is
illustrative — s3Dir is not exported yet; back an object store with customDir
today.
// Proposed shape — not exported yet.
s3Dir({
harnessDir: "/persistent/user",
bucket: "app-agent-memory",
prefix: ({ extraBody }) => `users/${extraBody.userId}/`,
});Equivalent adapters could exist for Cloudflare R2 and Google Cloud Storage.