Filesystem sources
Mount read-only content (knowledge bases, docs, fixtures) into a sandbox so the agent can grep / glob / read it like ordinary files.
withFilesystemSources pre-populates a sandbox with read-only content from one or more sources. The agent's built-in grep, glob, and read tools see it as ordinary files, no vector store, no embeddings, no retrieval pipeline.
This is the right primitive for support agents, runbook lookup, FAQ assistants, internal-docs Q&A, or any workflow where the corpus is small enough to mount as files (low MB).
Quickstart
import { defineAgent, localDirectorySource, withFilesystemSources } from '@fabric-harness/sdk';
const sandbox = withFilesystemSources('empty', [{
mountAt: '/workspace/kb',
source: localDirectorySource('./knowledge-base', { include: (p) => p.endsWith('.md') }),
}]);
export default defineAgent<{ message?: string }>({
run: async ({ init, input }) => {
const fabric = await init({ sandbox, runtime: 'stateless' });
const session = await fabric.session();
const message = input.message ?? '';
return { reply: await session.prompt<string>(
`Search /workspace/kb (markdown) and answer concisely.\n\nCustomer: ${message}`,
)};
},
});The agent sees /workspace/kb/*.md and uses its built-in tools to search.
Sources
localDirectorySource(hostPath, options?)
Reads a host directory recursively. Each file becomes an entry; the relative path inside hostPath is preserved.
localDirectorySource('./docs', {
name: 'product-docs',
include: (relativePath) => relativePath.endsWith('.md') && !relativePath.startsWith('drafts/'),
});include: predicate to skip files. Defaults to "include everything."name: label used in telemetry.
inMemorySource(files, options?)
Bundle content directly into the agent module. Useful for fixtures, tests, or small static corpora that ship with the agent.
inMemorySource({
'faq.md': '# FAQ\n\nQ: Reset password? A: Visit /forgot-password.',
'guides/billing.md': '# Billing\n\nCharges happen on the 1st of each month.',
});Cloudflare R2 sources
@fabric-harness/cloudflare includes an R2 source helper for support knowledge bases and other object-backed file sets.
import { r2FilesystemSource } from '@fabric-harness/cloudflare';
import { withFilesystemSources } from '@fabric-harness/sdk';
const sandbox = withFilesystemSources('empty', [{
mountAt: '/workspace/knowledge-base',
source: r2FilesystemSource(env.SUPPORT_KB, { prefix: 'knowledge-base/' }),
}]);Objects such as knowledge-base/reset-password.md are mounted as /workspace/knowledge-base/reset-password.md and become available to the built-in read, grep, and glob tools. See examples/support-agent-cloudflare-r2.
Concrete vendor connectors (recommended)
@fabric-harness/connectors ships concrete connectors for the most common backends. Each is on its own subpath export and peer-deps the vendor SDK so you only install what you use.
import { init } from '@fabric-harness/sdk';
import { s3Source } from '@fabric-harness/connectors/s3';
import { githubSource } from '@fabric-harness/connectors/github';
const fabric = await init({ sandbox: 'local' });
const session = await fabric.session();
// `session.mount()` writes the source's entries into the sandbox at the
// given path. mode: 'read' (default) blocks write tools to that prefix.
await session.mount('/mnt/logs', s3Source({ bucket: 'app-logs', prefix: '2026/' }));
await session.mount('/mnt/repo', githubSource({ owner: 'me', repo: 'demo', ref: 'main' }));Available subpath exports:
| Export | Functions | Peer dep |
|---|---|---|
@fabric-harness/connectors/s3 | s3Source, s3Writer | @aws-sdk/client-s3 |
@fabric-harness/connectors/azure-blob | azureBlobSource, azureBlobWriter | @azure/storage-blob |
@fabric-harness/connectors/gcs | gcsSource, gcsWriter | @google-cloud/storage |
@fabric-harness/connectors/github | githubSource | octokit |
@fabric-harness/connectors/databricks-volume | databricksVolumeSource, databricksVolumeWriter | none |
S3 and Azure Blob structural sources (advanced)
For non-standard SDK clients or when you want to adapt an existing client, the older structural helpers remain available. They don't import cloud SDKs; adapt your client calls to the small interface shape.
import { s3FilesystemSource, azureBlobFilesystemSource } from '@fabric-harness/connectors';
const s3 = s3FilesystemSource(s3ClientAdapter, { prefix: 'knowledge-base/' });
const azure = azureBlobFilesystemSource(blobClientAdapter, { prefix: 'knowledge-base/' });Use these with withFilesystemSources() the same way as local, in-memory, or R2 sources.
Custom sources
A source is just an object with an entries() method that yields { path, content } pairs. Implement one for S3, R2, GCS, an MCP filesystem server, or any other backend.
import type { FilesystemSource } from '@fabric-harness/sdk';
function s3Source(bucket: string, prefix: string): FilesystemSource {
return {
name: `s3://${bucket}/${prefix}`,
async *entries() {
// ...list & fetch from S3
yield { path: 'doc-1.md', content: '...' };
},
};
}Composition
withFilesystemSources(base, sources) accepts any sandbox base, a backend name, a SandboxFactory, or an existing SandboxEnv. It returns a SandboxFactory you pass to init({ sandbox }).
Mount multiple sources at different paths.
withFilesystemSources('empty', [
{ mountAt: '/workspace/kb', source: localDirectorySource('./kb') },
{ mountAt: '/workspace/runbooks', source: localDirectorySource('./runbooks') },
{ mountAt: '/workspace/legal', source: localDirectorySource('./legal-templates') },
]);Wrap a non-empty backend so mounts coexist with the workspace.
withFilesystemSources('local', [{
mountAt: '/workspace/reference',
source: localDirectorySource('./reference'),
}]);Conventions and limits
- Read-only by default with
session.mount(). Callingsession.mount(mountAt, source)(no options) marks the mount as read-only: write tools (write,edit,mkdir,rm) targeting paths under it are rejected at the policy layer. Pass{ mode: 'write' }to allow writes. - Read-only by convention with
withFilesystemSources(). The legacywithFilesystemSources()factory does not enforce read-only: it relies on the agent's role/skill prompts. For hard enforcement, usesession.mount()(default) or use a sandbox backend that supports it (Docker--read-onlymounts viametadata.mountReadOnly, for example). - Eager population. Both
withFilesystemSourcesandsession.mount()walk the source(s) and write every file into the sandbox. This is the right model for small corpora; for very large knowledge bases use a retrieval-augmented approach instead. - One source per path. Mounting two sources at the same path is allowed but the second overwrites the first. Mount at distinct paths.
- Path rerooting.
session.mount('/mnt/kb', source)with sandbox cwd/workspaceis rerooted to/workspace/mnt/kbsince paths outside the sandbox boundary are blocked.
See also
- Headless agents with the minimal entrypoint: the minimal entrypoint story this primitive belongs to.
- examples/support-agent: a runnable end-to-end example.
- Sandboxes: the broader sandbox model.