Persistent Dir Reference
Proposed callback shapes for developer-backed persistent directories.
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 let the app choose the storage logic while Little Harness owns the harness directory, session isolation, diffing, and commit timing.
Diff-Based Shape
The recommended default is diff-based.
const userMemory = {
harnessDir: "/persistent/user",
load: async ({ extraBody }) => {
return appStorage.loadFolder(`users/${extraBody.userId}/memory`);
},
store: async ({ extraBody, changes }) => {
return appStorage.applyChanges({
prefix: `users/${extraBody.userId}/memory`,
changes,
});
},
};Load Result
load can return a map of file paths to content.
{
"preferences.md": "# Preferences\nPrefer concise briefs.\n",
"accounts/acme.md": "Known risk: renewal date moved twice.\n"
}A future version may include metadata such as content type, version, etag, or modified time.
Store Input
store receives changes computed at the commit point.
{
created: {
"accounts/contoso.md": "New notes..."
},
updated: {
"preferences.md": "Updated notes..."
},
deleted: ["old-note.md"]
}Commit Modes
commit: "after-turn" // default
commit: "manual"
commit: "read-only"after-turn should be the default because it is predictable across chat, API, and job-style usage.
manual skips the automatic commit and can be committed later from
result.commitManual() or finished.commitManual() for the completed turn.
read-only exposes files for inspection but rejects writes immediately; it is
not a copy-on-write mode.
Per-Turn Write Check
canWrite: ({ extraBody }) => extraBody?.role === "editor"canWrite runs each turn, before the directory loads. When it returns false (or a
promise of false), the directory behaves as read-only for that turn: writes fail at
once, store is not called, and the commit result lists the directory as skipped.
Without it, every turn may write. localDir({ canWrite }) passes it through.
Conflict Handling
The alpha API does not lock the conflict API yet. A likely shape is for load to return a version cursor and store to receive it back.
load: async () => ({
files,
cursor: "etag-or-version",
});
store: async ({ changes, cursor }) => {
await storage.applyChanges({ changes, ifMatch: cursor });
};