The thing I keep running into with cloud agents is that they're really good at answering questions and really bad at knowing when to ask themselves one.
Most agents today are reactive. You call them, they respond, they go dormant. This is perfectly fine for a coding assistant sitting at your keyboard. When I'm feeding my daughter with one hand, and flipping pancakes with the other, I want my agent monitoring my stack (not the pancakes) so I don't have to be running back to my computer every 10 minutes accepting permission prompts or telling the agent to look at my honeycomb traces.
When I wake up at three in the morning and have trouble going back to sleep the thing that I can't get out of my head is how to make my agents proactive. (Do I have an obsession problem? Maybeee π€«) How can the agent know something happened in the outside world without me having to tell it?
MCP - Mo' Context Problems
MCP has established itself as the industry standard and it can be fantastic when used correctly. But I've found that when you stack many MCP tools, the agent starts every task carrying a catalog it may mostly never use. Anthropic has written about tool definitions consuming tens of thousands of tokens before any real work begins and how they try to counteract this. Composio has written about the same problem on their blog and their efforts to improve the situation.
That's the context bloat problem. There's a related but different one: MCP tools let the agent pull information. They're not how the outside world pushes a signal to the agent. For that you need webhooks, and webhooks are non trivial.
To make a single cloud agent proactive against Linear you need a public endpoint, HMAC verification, a queue, deduplication logic, retry handling, and registration with Linear's API. Then repeat for GitHub, Jira, and Slack, each with a different signature format and retry behavior. A team can spend four to six weeks building this before they write a line of actual agent logic.
Clock π, Listener π», Inbox π₯
The way we think about making agents proactive is three primitives:
- Clock wakes the agent up on a schedule -> we map this to relaycron
- Listener wakes the agent up when something in the world changes -> we map this to relayfile
- Inbox wakes the agent up when someone has something to say -> we map this to relaycast
So, allow me to introduce relayfile. You connect Linear, GitHub, Notion, Slack β whichever providers you care about β once. From then on, the agent code is just:
import { RelayFileClient, RelayFileSync } from '@relayfile/sdk';
const client = new RelayFileClient({
token: process.env.RELAYFILE_TOKEN!,
});
const sync = new RelayFileSync({
client,
workspaceId: 'ws_acme_ops', // already wired to Linear, GitHub, Notion, ...
});
sync.on('event', async (event) => {
// event.path tells you which provider's record changed:
// e.g. '/linear/issues/AGE-16__...json' or '/github/repos/.../pulls/412.json'
await agent.handle(event);
});
sync.start();No endpoint to host. No signature verification. No queue. When a Linear issue moves to blocked or a GitHub PR gets reviewed, the agent gets a normalized filesystem event pointing at the canonical file:
{
eventId: 'evt_01HQ8K7M2YV3R0XW9F4ZB6T2QA',
type: 'file.updated',
path: '/linear/issues/AGE-16__87389837-62b1-4e1a-a237-59218bab2974.json',
revision: 'rev_42',
provider: 'linear',
origin: 'provider_sync',
timestamp: '2026-05-13T14:32:01Z',
}The event tells the agent what changed and where. It then reads the file at path to see the current state β and, if it cares about the diff, the prior revision is one client.readFile away. The rest? Forget about it (said like a New Yorker)
The workspace is also just files
The change events are useful on their own but the full workspace is also navigable. Every connected provider is mounted at a well-known path and the agent can explore, search, and write back using tools it already knows.
$ ls $MOUNT
LAYOUT.md notion/ linear/ slack/ github/ confluence/LAYOUT.md at the root (and at each provider root) describes the shape of the workspace. That's where the agent should start.
Notion pages live under notion/pages/. Canonical records are keyed by UUID with a by-title/ subtree for human-readable lookup:
# Find a page by title
$ jq -r '.id' $MOUNT/notion/pages/by-title/just-give-the-agent-files__c24642bb.json
3566800c-1c90-8035-a491-cdc3c24642bb
# Read the body
$ cat $MOUNT/notion/pages/3566800c-1c90-8035-a491-cdc3c24642bb/content.mdLinear issues use <identifier>__<uuid> β ticket number first so the listing is immediately scannable. Mirage converged on a similar filename shape in their mount, and their broader "mount everything as one tree" framing is the closest thing in this space to what we're doing:
$ ls $MOUNT/linear/issues/
AGE-9__2bb2c00f-ee93-4c73-a793-df5b725d9a1a.json
AGE-10__8c313d70-9800-4539-820f-96a481c09ce0.json
AGE-16__87389837-62b1-4e1a-a237-59218bab2974.json
$ jq '.payload | {title, state_name, assignee_name}' \
$MOUNT/linear/issues/AGE-16__87389837-62b1-4e1a-a237-59218bab2974.jsonWritebacks are files too
Reads are useful. Writes are where it gets interesting. The agent doesn't call a provider API to take action β it writes a file. Relayfile handles the provider auth, payload validation, retries, and audit trail on the other side.
To create a GitHub issue, the agent drops a file:
$ cat > $MOUNT/github/repos/AgentWorkforce/relayfile-adapters/issues/relayfile-writeback-test-20260513T121247Z.json <<EOF
{
"title": "Relayfile writeback test 20260513T121247Z",
"body": "Created by Relayfile writeback validation for rw_517d60b6."
}
EOFThat file write created an actual GitHub issue. The agent didn't touch the GitHub API. It wrote a file. Same pattern works for posting Linear comments, creating Confluence pages, sending Slack messages β whatever the provider supports, the agent interacts with through the filesystem.
Permissions when multiple agents share a workspace
When multiple agents are cookin' in the same workspace, Relayfile + Relayauth scopes access to paths so each agent gets exactly what it needs:
scopes: [
'relayfile:fs:read:/notion/*',
'relayfile:fs:read:/slack/channels/C0ADE9B71CN__gtm-prospects/*',
'relayfile:fs:write:/linear/issues/*',
];Scopes are plane:resource:action:path β * is allowed as a trailing path segment but not in the middle, so you grant a writable subtree rather than carving out per-resource holes. Each agent gets its own scoped token that can be revoked independently.
With multiple agents, the second thing that matters is how fast their writes propagate to each other. Most "filesystem for agents" projects assume one agent at a time. Relayfile assumes many β and writes are pushed to every other agent in sub-second time, not on a polling loop.
When agent A writes a file, agent B sees the new contents on the next read. No commit, no merge, no message bus. That's the difference between agents that use a filesystem and agents that coordinate through one.
A few patterns this unlocks without writing any coordination code:
- An implementer agent drops a draft at
linear/issues/AGE-16/comments/draft-1.json; a reviewer agent with a scope on that path gets afile.createdevent the moment it lands. - An orchestrator agent watches its workers' progress by tailing their writes β no status protocol, no heartbeat β the files are the status.
- A human and an agent share the same mount; the human edits a Notion doc, the agent gets the same change event a webhook would have delivered, and it doesn't matter which of them did it.
I can just think of one word to describe this, DOPE.
Under the hood
All the auth, refresh tokens, pagination, retries, and webhook payloads are handled by Nango under the hood. Nango is excellent integration infrastructure β I'd say that even if I weren't a former employee (I was the first engineer hired there, full disclosure). Relayfile sits on top and turns that layer into the file interface.
It also works with Composio, Pipedream, and other providers. Whichever integration layer you're already using, Relayfile controls what gets promoted into the workspace: which records become files, which paths are mounted, which writes go through.
We actually ran the numbers
I want to keep it π― here because I've seen too many benchmark posts where the author is clearly trying to win a preordained argument. So here's what we actually found.
We ran a head-to-head on one realistic task β "what did I work on yesterday across GitHub, Linear, and Notion?" β against three Claude models. Same prompt, same date, same accounts. MCP arm used the GitHub, Linear, and Notion MCP servers. Relayfile arm used a mounted filesystem.
We ran it three times. Started naive and then iterated.
Round 1 β live mount, no index directories:
| Model | MCP cost | Relayfile cost | MCP turns | RF turns |
|---|---|---|---|---|
| Opus | $0.32 | $0.80 | 11 | 23 |
| Sonnet | $0.19 | $0.38 | 8 | 22 |
| Haiku | $0.16 | $0.19 | 9 | 21 |
Womp Womp. Relayfile lost pretty badly on Opus and Sonnet. The agent was spending roughly 9 of its 23 turns reconstructing what should have been a single directory listing. This was our naive first pass β we were testing the hypothesis that just giving the agent files makes it more performant, no special structure. Frankly, I was just so excited at having shipped this I thought just files was enough. Back to the drawing board...
Round 2 β by-* index directories added:
| Model | MCP cost | Relayfile cost | MCP turns | RF turns |
|---|---|---|---|---|
| Opus | $0.35 | $0.47 | 11 | 19 |
| Sonnet | $0.16 | $0.31 | 10 | 29 |
| Haiku | $0.12 | $0.13 | 6 | 13 |
Better. Relayfile dropped 34% total cost run-over-run. But Sonnet actually used more turns in round 2 β the index directories were there but the agent still had to figure out which ones to reach for.
Round 3 β digest + .skills/ added:
Between rounds 2 and 3 we added two things: a digests/yesterday.md that Relayfile pre-computes deterministically from the raw provider data β the agent gets it for free, nothing to generate β and a .skills/activity-summary.md file that tells the agent to check the digest first before doing any exploration.
| Model | MCP cost | Relayfile cost | MCP turns | RF turns |
|---|---|---|---|---|
| Opus | $0.44 | $0.25 | 9 | 4 |
| Sonnet | $0.27 | $0.07 | 18 | 4 |
| Haiku | failedΒΉ | $0.06 | β | 4 |
ΒΉ MCP Haiku asked for clarification instead of using its tools. Relayfile Haiku completed the task.
Relayfile Sonnet went from $0.31/29 turns to $0.07/4 turns. The agent read the digest, confirmed it covered the date, and reported back. Four tool calls. The digest is one file read that replaces 25 individual provider queries.
MCP Sonnet got more expensive between rounds 2 and 3 because there was more data in the repos by the time we ran it β more PRs, more issues, more to page through. That's the thing about API-based retrieval: costs move with data volume. The filesystem costs move with how well your indexes are structured.
A few things I noticed looking at the actual outputs:
MCP returns structured filtered results in a single round-trip, which is genuinely faster on wall-clock. If you're building something where latency per query matters, that's a real advantage.
Where relayfile pulled ahead was completeness and stability. Relayfile Sonnet found "Khaliq's To Dos" and several other Notion pages that MCP Opus missed entirely β because ls notion/pages/by-edited/2026-05-12/ is exhaustive. And MCP Haiku failing to act in round 3 is the kind of thing that happens with API-dependent tools: the model second-guessed itself without the filesystem signal telling it exactly where to look.
The trajectory is also just different. Every structural improvement to the mount β better indexes, digests, skill files β makes every future run cheaper. With MCP you're mostly stuck at whatever the API returns.
And there's a thing coming next that I'm pretty excited about: letting your engineers write custom digest logic that ships with your workspace. Today the digest sections are produced by the first-party adapters we maintain (Linear, GitHub, Notion), etc. The natural next step is letting a customer drop in a TypeScript/Python function β "roll up our OKR Notion database by status", "summarize Salesforce opps that crossed Stage 4 last week", "list every PR that touched the billing surface" and then its agents files are highly optimized for their use cases. Determinism + Agents FTW.
Try it yourself
The shortest path is to hand your agent the skill and let it cook:
npx prpm install @agent-relay/setting-up-relayfileThat installs the setting-up-relayfile skill into the agent's context, and it knows how to call relayfile setup, pick a provider, run the OAuth flow, verify the mount, and hand off RELAYFILE_LOCAL_DIR to whatever runs next β without you threading any of it through the prompt.
If you'd rather drive it yourself, the CLI does the same work:
# Install and connect your first provider
npx relayfile setup \
--provider notion \
--local-dir ~/relayfile-mountOr, in code, via the SDK:
import { RelayfileSetup } from '@relayfile/sdk';
const setup = await RelayfileSetup.login();
const workspace = await setup.createWorkspace({
name: 'acme-ops',
scopes: ['relayfile:fs:read:/notion/*', 'relayfile:fs:write:/linear/issues/*'],
});
const notion = await workspace.connectNotion();
console.log(`Connect Notion: ${notion.connectLink}`);
await workspace.waitForNotion({ connectionId: notion.connectionId });That logs you into Relayfile Cloud, creates a workspace, mints a Nango OAuth URL for Notion auth, runs the initial sync, and starts the local mount daemon.
Add more providers once the first one is working:
npx relayfile integration connect linear
npx relayfile integration connect github
npx relayfile integration connect slackThen point the agent at the mount:
export RELAYFILE_LOCAL_DIR=~/relayfile-mountThe agent reads files directly with ls, cat, grep, find β same as any project directory. Writes propagate to the provider within about 30 seconds. If you want to see the entire layout on the web you can run:
npx relayfile observerWhich will open up a hosted web view of the filesystem.
Just Files
Seeing files and reasonable paths just makes sense. Engineers who've spent years wiring up webhooks look at the workspace layout for about thirty seconds and just start typing commands. They already know ls and rg and cat.
There's obviously more to it than that (the permissions model, the change events, the audit trail) and not every agent workflow should run through a filesystem. MCP is useful. There are times where calling a specific function directly is the right call.
But for the scenario where an agent needs to wake up when something changes, read context from a few places, and act without being asked, I've seen files work really well. The workspace is already shared. Once the change event lands, everything else is just the same tools the agent already knows how to use.
Just files.