Files over tools: how we built our agent with a virtual filesystem and bash

cjbell3 pts0 comments

Files over tools: how we built the Knock Agent using a virtual file system and bash | Knock<br>Log inLSign upS

Files over tools: how we built the Knock Agent using a virtual file system and bash<br>Last updated:09 Jul 2026<br>|12 min read<br>|Summarize:ChatGPTClaudePerplexityGoogle GeminiGitHub CopilotGrok

In March 2026, we shipped the Knock Agent: an AI agent for managing all your customer messaging resources in Knock. It can create and update workflows, templates, and audiences, help segment users, and answer questions about how your customer messaging is performing. The agent can be invoked from the Knock dashboard, a connected Slack workspace, the API, or our MCP server.

In this post, we'll take a look behind the scenes at how we architected and built our agent using bash, a virtual file system, and our management API.

Defining the vision for our agent

Our vision was to create an agent that could manage everything that can be accessed from the Knock dashboard. We wanted the agent to create messaging that uses your company's design system, matches the tone of voice, and understands how your data is modeled.

To bring this vision to life, we needed to create a rich agent harness that could access the full breadth of data in your Knock account and use that context while creating your messaging and answering any questions.

We built our first prototype to operate exclusively on workflows, which is a deep domain with plenty of nuance around how it works. We used a tool-per-type pattern, where each tool exposed a primitive from our management API, such as adding a delay step to a workflow, or building an email template using our visual blocks language. The tool description encoded the idiosyncrasies of working with that tool and gave examples, while the tool input described the field types and what was required.

While this worked and led to good results, we realized that this approach would not scale: we'd be exposing a tool per resource in the management API, which would bloat the context window of the agent without us implementing a more sophisticated tool routing layer or introducing a scripting language for the agent to use.

Fewer tools, more files

We found a blog from the team at Vercel, "How to build agents with filesystems and bash", where they describe building an agent for d0, their internal data tool. They explain how the filesystem is a great way for an agent to discover the context it needs, and how you can map a domain to a filesystem to power this context layer.

At Knock, we've long had a CLI which enables teams to pull down their Knock resources, such as messaging templates and components, and operate on them locally via the filesystem.

The idea here was simple: instead of exposing tools that mirror our management API, we give the agent a filesystem with the contents of your account, and the ability to use bash to script on top of that filesystem. That way, the agent can explore the filesystem to gather context, using bash to efficiently write scripts as needed.

When the agent wants to make an edit to a workflow or template, it modifies a file in place in the filesystem and calls back to Knock to persist that change.

This pattern is much closer to Claude Code and other coding agents than other in-product assistants, and it fits the philosophy we've already established with our CLI and local-first approach to maintaining your customer messaging.

A bash for agents, in Elixir

Over the 2025 holidays, Malte from Vercel released just-bash. The idea was simple: let agents have a unix filesystem and a bash environment, without booting a Linux image. In practice, that meant creating a virtual bash implementation in TypeScript with a virtual filesystem that an agent could call.

Knock is a company firmly rooted in the Elixir ecosystem. We wanted a virtualized bash interpreter of our own. Our options were to 1) forgo the work we had done on our agent and rebuild it in TypeScript, or 2) port a version of the library to Elixir.

Fortunately, a library like just-bash is a reproducible target for an agent. It has a well-defined surface area and a thorough test suite covering the commands it supports (jq, ls, cat, etc.) — fixtures our Elixir port reuses verbatim.

In one of the all-time nerd snipes, we convinced agent-pilled friend of Knock, Ivar Vong, to give it a go. Many tokens later, we had a complete bash interpreter and virtual filesystem in Elixir, which he also called just-bash, with an extensive test suite and a robust security model.

Why not a sandbox?

The obvious question: why not give the agent a full computer via a sandbox rather than a virtual filesystem? After all, there are a number of vendors we could have picked to power it.

We believe a full sandbox is—at this stage—overkill for our simple needs. Starting a sandbox comes at a performance cost compared to the virtual, in-memory option, and introduces harder synchronization problems for content and changes made outside our app which we...

agent bash knock filesystem virtual tool

Related Articles