Documentation menu

Docs

Core & Custom Backends

@nanocodana/core is the engine the adapters wrap: the agent loop, the tools, skills, approval gating, and MCP wiring. It imports no Node builtins and touches no DOM, so it runs wherever JavaScript does — a serverless function, an edge runtime, a container, a remote sandbox, or embedded in your own app — over whatever storage you hand it.

npm install @nanocodana/core ai
embed.ts
import { NanoCodana } from '@nanocodana/core'

const agent = new NanoCodana({
  model,
  fs: myFileSystem,      // any IFileSystem
  // or: sandbox: mySandbox
})

// Same streaming surface as the adapters.
const result = await agent.stream({ messages })

Which package do I want?#

One question decides it — where do the files live?

Files live…Use
on a real diskNodeAgent— laptop, server, or a serverless function's own disk (clone → work → push).
in a browser tabBrowserAgent — in-memory or IndexedDB.
anywhere elsecore — a database, blob storage, a remote sandbox: you hand the agent its filesystem.

The easy way: hydrate + onFilesChange#

You usually don't need to implement a filesystem at all. Load the project from your store into the built-in in-memory FS via initialFiles, let the agent work at memory speed, and persist every change back with the onFilesChange hook. This is exactly how Sharables works — swap its IndexedDB for your database and the pattern is unchanged:

db-backed-agent.ts
const project = await db.loadFiles(projectId) // [{ path, content }, ...]

const agent = new NanoCodana({
  model,
  initialFiles: project,
  onFilesChange: (changes) => {
    // Fires for every file the agent creates, edits, or deletes.
    for (const { path, content } of changes) {
      content === undefined ? db.deleteFile(projectId, path) : db.saveFile(projectId, path, content)
    }
  },
})

Too big to hydrate? Load files lazily#

When a project is too large to pull in full, give a file's content a function instead of a string. List the paths cheaply up front; each file's bytes are fetched on first read — sync or async — and cached. A write replaces the provider before it ever runs.

lazy-hydration.ts
const paths = await db.listPaths(projectId) // cheap: names only, no content

const agent = new NanoCodana({
  model,
  initialFiles: paths.map((path) => ({
    path,
    content: () => db.readFile(projectId, path), // fetched on first read
  })),
  onFilesChange: persist, // writes/deletes still flow back to your store
})
Anything that scans the whole tree — Glob, Grep, or listing a directory — materializes every lazy file it visits. For search-heavy work over a remote store, hydrate eagerly instead.

Runnable example: apps/serverless — a function that hydrates a project from a database per invocation and persists the agent's edits back.

Ready-made filesystems from just-bash#

fs takes any IFileSystem, and just-bash ships four backends that satisfy it. Most of the time you don't implement anything — you install it, pick one, and pass it:

BackendWhat it is
InMemoryFsPure in-memory (core's default). Fast, ephemeral, supports the lazy providers above.
ReadWriteFsDirect read-write access to a real directory (what NodeAgent uses). Point it at a workspace, never at trusted code.
OverlayFsCopy-on-write over a real directory: reads come from disk, writes stay in memory. Let an agent explore a real repo without ever touching it.
MountableFsA unified namespace combining backends at mount points — e.g. read-only knowledge at /mnt, a read-write workspace at /home.
overlay-sandbox.ts
import { OverlayFs } from 'just-bash'

// Reads fall through to the real repo; writes are captured in memory —
// the agent works over a live copy it can't corrupt.
const fs = new OverlayFs({ root: '/path/to/repo' })
const agent = new NanoCodana({ model, fs })
just-bash is a direct dependency of yours, not a transitive one — @nanocodana/core vendors a Node-free build of the shell and doesn't depend on the package at runtime. Run npm install just-bash to use these backends.

Note that OverlayFs, ReadWriteFs and the Sandbox need node:fs, so they are Node-only. In a browser or on an edge runtime, use InMemoryFs (core's default) or your own IFileSystem.

The full contract: IFileSystem#

If none of those fit — reads and writes should go straight to your backend with no in-memory copy — implement IFileSystem yourself (exported as a type from @nanocodana/core) and pass it as fs. It's a POSIX-flavored async contract:

GroupMethods
contentreadFile · readFileBuffer · writeFile · appendFile
structuremkdir · readdir · readdirWithFileTypes? · rm · cp · mv
metadatastat · lstat · exists · chmod · utimes
linkssymlink · link · readlink · realpath
pathsresolvePath · getAllPaths

Reference implementations to crib from: core's MemoryFileSystem (in-memory), and just-bash's ReadWriteFs(real disk — what the Node adapter uses). Your editor's jump-to-definition on IFileSystem shows every signature with JSDoc.

Or bring a sandbox#

Pass sandbox instead of fs when you have an execution environment — the agent routes both file operations and shell commands through it. This is the integration point for remote sandboxes and microVMs.

What you inherit for free#

Everything documented in the Concepts section works identically on core: streaming, approval gating, skills, image generation, MCP, and per-call overrides.

Size#

A browser app loads 182 kB gzipped up front, with the shell deferred to its own chunk — +338 kB on the first Bash call, and never fetched if the agent doesn't shell out. Measured on a real app.

On disk, node_modules holds 59 MB — essentially all AI SDK, and none of it ships: your bundler takes only what you import. The shell is vendored in as one pre-built file, so just-bash and its ~78 MB of runtimes never enter your tree at all. To drop the shell from the build rather than defer it, import @nanocodana/core/no-bash — on a Cloudflare Worker that's 854 KiB → 448 KiB gzipped. virtualBash: false won't do it: no bundler can eliminate a reachable dynamic import on a runtime flag.

Rule of thumb: reach for an adapter first. Drop to core only when the filesystem or the execution environment is genuinely yours.