A virtual filesystem for AI agents: how Mintlify built ChromaFs

A virtual filesystem for AI agents: how Mintlify built ChromaFs
46 s → 100 msto create a session, at the 90th percentile
$70,000+a year in sandboxes, with 5-minute sessions
$0of extra compute per conversation with ChromaFs
30,000+conversations a day on their assistant
Your agent doesn't need a disk: Mintlify's virtual filesystem, explained
I also cover it in this video of about ten minutes, on my channel Articulator EN.

When RAG falls shortlink

The Mintlify assistant ran on RAG, retrieval augmented generation: it searched for the chunks of text most similar to the question and answered with them. The article opens with a line that sums it up: RAG is great, until it isn't.

It fails in two cases. When the answer is spread across several pages. And when the user needs the exact syntax of something, and that chunk does not show up among the top search results (the famous top-K). In both, the assistant got stuck.

Three pages with a piece of the answer on each, and a results list where the exact chunk falls outside the top K: the assistant, stuck.
The two cases where RAG gets stuck.

Docs as a repositorylink

They wanted the assistant to navigate the docs the way an agent navigates a code repository. And agents already have a favorite interface for that: the file system. According to the article, agents are converging on it because grep, cat, ls and find are all an agent needs.

The idea is simple: each docs page is a file and each section is a folder. That way the agent can search for exact strings, read whole pages and walk the structure on its own. All they needed was a file system that mirrored the live documentation site.

A documentation site on the left and its folder tree on the right, joined by arrows: a file system that mirrors the site.
Each page, a file; each section, a folder.

The container bottlenecklink

The obvious move is to give the agent a real file system. That's what almost every harness does: it spins up an isolated environment, a sandbox, and clones the repository into it. Mintlify already does that for its background agents, where latency doesn't matter. But this assistant lives on the web, with a user watching the loading spinner go round.

A crane lowering a container in a port at night, labeled sandbox, isolated environment, and a terminal running git clone.
A sandbox per conversation: it has to be spun up and the repo cloned into it.

Creating each session, GitHub clone included, took about 46 seconds at the 90th percentile. In other words, nine out of ten sessions took less, but the rest took even longer: almost a minute staring at a spinner, just to start reading the docs.

A docs site with the assistant loading and a stopwatch showing 46 seconds labeled P90.
Creating a session: 46 s at the 90th percentile.

Then there's the bill. The article runs the numbers: 850,000 conversations a month, each with its own micro virtual machine. Even a minimal one (1 vCPU, 2 GiB of memory and a 5-minute lifetime), at Daytona's per-second pricing ($0.0504/h per vCPU and $0.0162/h per GiB) comes to about $0.0069 per conversation: over $70,000 a year. With longer sessions, double that: about $0.0137 per conversation, the figure in their comparison table.

The math: Daytona pricing, $0.0069 per conversation and $70,380 a year, with a times-two stamp.
The article's math, step by step.

They admit themselves that it's a naive estimate: in production there would be pre-warmed machines and shared containers. But the point stands. To read static documentation, one machine per conversation is slow and expensive. They needed something instant and cheap, and that meant rethinking the file system itself.

Faking a shell: ChromaFslink

Here's the key: the agent doesn't need a real file system, the illusion of one is enough. And Mintlify already had all its documentation chunked, indexed and stored in Chroma, the database behind its search.

A movie set at night: the lit facade of a building, labeled the illusion of one.
In the video I tell it with a movie set: a facade in front, just some props behind.

That's how ChromaFs was born: a virtual file system that intercepts the Unix commands the agent types and translates them into queries on that same database. The agent thinks it's reading files; in reality, it's asking Chroma.

The agent's terminal runs cat on a page; ChromaFs intercepts it and sends a query to Chroma.
The agent thinks it reads files; it's really asking Chroma.

The result: creating a session went from about 46 seconds to about 100 milliseconds, around 460 times faster (that math is mine). And since it reuses a database they were already paying for, each new conversation adds no compute cost. Side by side:

Sandbox
  • Boot time (p90): ~46 s
  • Cost: ~$0.0137 per conversation
  • Search: scans the disk file by file
  • Infrastructure: Daytona or similar
ChromaFs
  • Boot time (p90): ~100 ms
  • Cost: ~$0, reuses the database
  • Search: a metadata query
  • Infrastructure: the database they already had
The article's table: boot time, cost per conversation, search and infrastructure, sandbox versus ChromaFs.
The table from the article.

just-bash and the layerslink

Under the hood, ChromaFs is built on just-bash, from Vercel Labs: a TypeScript reimplementation of bash that understands grep, cat, ls, find and cd. It handles all the hard parts: parsing the commands, the pipes and the flags.

And the best part: it lets you plug in your own file system through an interface, IFileSystem. ChromaFs implements it, and every call to the disk becomes a query to Chroma. On top, a normal shell; underneath, a database. In the code the article shows, readFile asks Chroma for all the chunks of the page and joins them, and writeFile, appendFile, mkdir and rm throw a read-only error.

just-bash on top, the IFileSystem interface and ChromaFs plugged in below, connected to the database: a normal shell on top and a database underneath.
On top, a normal shell; underneath, a database.

The article's diagram sums it up in four layers. At the top, the agent types bash commands. Below that, just-bash interprets them. Then ChromaFs translates them. And at the bottom sits the data: Chroma, a cache in Redis and files in S3.

The layer diagram: agent, shell, ChromaFs with the grep optimizer and the path tree, and the data layer with Redis, Chroma and S3.
The article's diagram, redrawn.

The folder treelink

ChromaFs has to know which files exist before the agent types a single command. So they store the entire tree as a single compressed JSON document, __path_tree__, inside the Chroma collection itself. Each path carries two more facts: whether it's public (isPublic) and which groups it belongs to (groups).

At startup, the server fetches it, decompresses it and builds two structures in memory: a set (Set<string>) with the path of every file and a map (Map<string, string[]>) from each folder to its children. From there, ls, cd and find resolve in local memory, with no network calls. And since the tree is cached, later sessions for the same site don't even have to ask Chroma for it.

The tree fetched from Chroma becomes two in-memory structures: a set with every path and a map from folder to children.
A set with the paths and a map with each folder's children.

Access controllink

Before building the tree, ChromaFs prunes the paths using the user's session token, and applies the same filter to every query that follows. If you don't have access to a file, that file doesn't exist: it vanishes from the tree, and the agent can't read it or even mention it, because as far as it knows that path was never there.

The folder tree without the restricted paths, and a terminal where cat on an internal page returns No such file or directory.
No access, no file.

In a real sandbox, this would mean managing Linux user groups, chmod permissions or a different container image for each customer tier. In ChromaFs it's a few lines of filtering before the tree is built.

Pages in chunks, caching and read-onlylink

Pages are stored in Chroma as chunks, because that's how they're indexed for search. When the agent runs cat on a page, ChromaFs fetches all its chunks, sorts them by number (chunk_index) and joins them. It also caches the result, so several grep runs in a row over the same pages never hit the database twice.

A page's chunks come out of Chroma out of order, get sorted and joined into the whole page.
Fetch the chunks, sort them, join them.

Not everything has to live in Chroma. Some customers have huge OpenAPI specs stored in their own S3. For those there are lazy pointers: the agent sees v2.json when it lists /api-specs/, but the content is only fetched when it runs cat.

The v2.json file in the tree, with a dashed link to the customer's S3: it's only downloaded on cat.
A lazy pointer to S3.

And one last rule: every write returns EROFS, the read-only file system error. The agent explores freely but can never change the documentation. So everything stays stateless: there's nothing to clean up when the session ends, and no agent can spoil what another one sees.

A terminal where echo, rm and mkdir return EROFS, next to a shield labeled read only.
Reading and searching, yes; writing, no.

Optimizing greplink

cat and ls are easy to virtualize. The hard one is grep: a grep -r that read every file one by one over the network would be painfully slow. So they intercept it: they parse its flags with an argument parser (yargs-parser) and turn them into a Chroma query, $contains for fixed strings or $regex for patterns. Then it works in two passes:

  1. Coarse filter. Chroma points out which files might contain the match.
  2. Prefetch. The chunks of those files are pulled all at once (bulkPrefetch) into a Redis cache.
  3. Fine filter. They rewrite the grep to target only those files and hand it back to just-bash, which runs it in memory.
Chroma drops the files into a funnel, three come out, and their chunks go all at once into a Redis cache.
Coarse filter and prefetch.

The result: huge recursive searches that finish in milliseconds.

Hands panning for gold in a pan, labeled fine filter, in memory.
The fine filter, like the gold left in the pan.

In shortlink

Today ChromaFs powers the documentation assistant for hundreds of thousands of users, across more than 30,000 conversations a day. By swapping sandboxes for a virtual file system on top of their existing database, Mintlify got three things: instant sessions, zero marginal compute cost and built-in role-based access control, without any new infrastructure.

Three badges: instant sessions, zero marginal cost and role-based access control, and below, no new infrastructure.
What they gained.
In one sentence

The agent doesn't need a real disk: the illusion of one is enough.

What else would you turn into folders for an agent to explore?

Sourceslink

Continue reading

Last posts -->

Have you seen these projects?

Gymnasia

Gymnasia Gymnasia
Expo
React Native
TypeScript
OpenAI
Anthropic

Fitness app with two agents that run entirely on the device, with no backend, so the user's data never leaves the phone. A BYOK conversational coach with local tools and a remote system prompt with offline fallback, plus a food estimator that pulls macronutrients out of a photo of the plate, with barcode scanning against OpenFoodFacts.

LangGraph Deep Researcher

LangGraph Deep Researcher LangGraph Deep Researcher
Python
LangGraph
FastAPI
React
TypeScript
Docker

Multi-agent research system built with LangGraph. A supervisor breaks your question down into topics and launches search sub-agents in parallel; each one compresses its findings before handing them to a writer agent that produces the final sourced markdown report. Live streaming over WebSockets, a configurable model per role and bring-your-own API keys that are never persisted server-side.

Tau

Tau Tau
Python
LangChain

Multi-agent tutoring system for secondary school students, with one agent per subject and course material written and validated by a team of teachers. It was used with real students at a private school in Spain and at a secondary school in Colombia.

View all projects -->
>_ Available for projects

Do you have an AI project?

Let's talk.

maximofn@gmail.com

Machine Learning and AI specialist. I develop solutions with generative AI, intelligent agents and custom models.

Do you want to watch any talk?

Last talks -->

Do you want to improve with these tips?

Last tips -->

Use this locally

Hugging Face spaces allow us to run models with very simple demos, but what if the demo breaks? Or if the user deletes it? That's why I've created docker containers with some interesting spaces, to be able to use them locally, whatever happens. In fact, if you click on any project view button, it may take you to a space that doesn't work.

Flow edit

Flow edit Flow edit

FLUX.1-RealismLora

FLUX.1-RealismLora FLUX.1-RealismLora
View all containers -->
>_ Available for projects

Do you have an AI project?

Let's talk.

maximofn@gmail.com

Machine Learning and AI specialist. I develop solutions with generative AI, intelligent agents and custom models.

Do you want to train your model with these datasets?

short-jokes-dataset

HuggingFace

Dataset with jokes in English

Use: Fine-tuning text generation models for humor

231K rows 2 columns 45 MB
View on HuggingFace →

opus100

HuggingFace

Dataset with translations from English to Spanish

Use: Training English-Spanish translation models

1M rows 2 columns 210 MB
View on HuggingFace →

netflix_titles

HuggingFace

Dataset with Netflix movies and series

Use: Netflix catalog analysis and recommendation systems

8.8K rows 12 columns 3.5 MB
View on HuggingFace →
View more datasets -->