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 { agent, localDirectorySource, withFilesystemSources } from '@fabric-harness/sdk';
const sandbox = withFilesystemSources('empty', [{
mountAt: '/workspace/kb',
source: localDirectorySource('./knowledge-base', { include: (p) => p.endsWith('.md') }),
}]);
export default agent({
run: async ({ init, input }) => {
const agent = await init({ sandbox, runtime: 'stateless' });
const session = await agent.session();
const message = String((payload as any)?.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.