The Simplest Agent-to-Agent Communication: Just Use a Filesystem

Last modification on

Every few months, a new agent communication protocol appears. But for most agent workloads, the simplest answer is something we've had since the 1970s: a filesystem.

Files as messages

One agent writes a file, another agent watches for it.

Agent A writes: /inbox/task-001.json
Agent B sees:  CREATE /inbox/task-001.json
Agent B reads: {"action":"summarize","url":"https://..."}
Agent B writes: /outbox/result-001.json

No SDKs. No protocol negotiation. The debugging story is unbeatable:

  • ls shows queue depth
  • cat shows payloads
  • rm clears broken messages

This is not a new idea. Maildir has worked this way for decades.

The missing piece: durable event streams

Traditional filesystems give you inotify-style notifications, but they're local-only, lossy under load, and don't work across machines.

What you want is a filesystem with a real event stream β€” durable, resumable, remote.

fs9: a queryable filesystem with watch

db9 includes fs9, a cloud-backed filesystem namespace. It supports fs watch: a cursor-based event stream over file changes.

db9 fs watch myapp:/inbox/
00:00:00.000 CREATE /inbox/task-001.json
00:00:01.234 MODIFY /inbox/task-001.json
00:00:05.678 DELETE /inbox/temp.txt

Key properties:

  • Durable: events are stored as a stream. If your agent crashes, it resumes from its last cursor.
  • Remote: two agents on different machines coordinate without VPN gymnastics.
  • Queryable: files can be queried from SQL.

Example: dispatcher + worker

A dispatcher writes tasks:

SELECT fs9_write(
  '/tasks/pending/t-' || gen_random_uuid() || '.json',
  '{"url":"https://example.com/article","requested_by":"user-42"}'
);

A worker watches and claims tasks via atomic move:

db9 fs watch myapp:/tasks/pending/ --json | while read -r event; do
  path=$(echo "$event" | jq -r '.path')
  type=$(echo "$event" | jq -r '.event_type')

  [ "$type" = "CREATE" ] || continue

  processing_path=$(echo "$path" | sed 's|/pending/|/processing/|')

  # Claim by atomic move
  if ! db9 sql myapp -q "SELECT fs9_move('$path', '$processing_path')" --output raw >/dev/null 2>&1; then
    continue
  fi

  # Process the task...
  task=$(db9 sql myapp -q "SELECT fs9_read('$processing_path')" --output raw)
  # your agent logic here
done

SQL escape hatch

Because fs9 is queryable from SQL, you can run ops on your "queue" directly:

-- How many tasks are pending?
SELECT count(*) FROM extensions.fs9('/tasks/pending/');

-- What's the oldest unprocessed task?
SELECT path, mtime
FROM extensions.fs9('/tasks/pending/')
ORDER BY mtime ASC
LIMIT 1;

Common patterns

  • Request/Response: /requests/{id}.json β†’ /responses/{id}.json
  • Fan-out: many workers watch the same directory, claim via atomic move
  • Event log: append-only writes to /events/YYYY-MM-DD/, consumers keep cursors
  • Config reload: watch /config/settings.json, reload on modify

Why this works for agents

Most agents don't need ultra-low latency or massive throughput. They need:

  • Simplicity β€” fewer moving parts
  • Inspectability β€” see the state with ls and cat
  • Persistence β€” if an agent dies, the message is still there
  • Flexibility β€” JSON today, CSV tomorrow, model checkpoints next week

A filesystem is the lowest-common-denominator abstraction every toolchain already understands. Adding durability, remote access, and SQL queryability makes it surprisingly capable.

Try it

db9 create --name my-agents

If you can ls your queue and cat your messages, you're in good shape.