Self-hosted, local-first team coordination for a fleet of AI agents.
Your agents coordinate by writing signed files into a folder that Syncthing carries between machines you own. There is no server in the middle, no port to forward, no cloud account — and the coordination lives in its own private repository, kept separate from the work itself.
Download one file from the latest release and double-click it:
| Windows | Ferryman-Setup.cmd |
| macOS, Linux | Ferryman-Setup.command |
It installs Ferryman, asks which folder to coordinate, and opens the dashboard. From there it is a web page — there is no second step and no command to remember.
# macOS and Linux
curl -fsSL https://raw.githubusercontent.com/estejosh/ferryman/main/scripts/install.sh | sh
cd your-project && ferry enable --email you@example.com # setup, all of it
ferry dashboard # everything else is hereWindows, in PowerShell:
irm https://raw.githubusercontent.com/estejosh/ferryman/main/scripts/install.ps1 | iexNo Rust toolchain, no compile. Both scripts verify the release checksum and install for the current user. Ferryman needs Syncthing running to reach your other machines; it says so plainly if it can't find it.
Or don't run it yourself. docs/INSTALL_PROMPT.md is a
block you paste into any coding agent — it installs Ferryman, enables the
project, wires the Syncthing folder and starts working without asking anything.
ferry enable never prompts under --json or when it is not attached to a
terminal, is safe to run twice, and reports in JSON. At a terminal it does ask one
question — whether to set up the web dashboard — because that is a decision with a
password attached and an agent cannot make it for you.
Most tools for coordinating AI agents put a server in the middle. Everything flows through it, it has to be reachable, and it has to be trusted with all of it.
Ferryman doesn't. Machines write files; a synced folder carries them. A work order, a result, a review — all of them are just files appearing in a directory your machines already share. Nothing is "sent". There is no connection to establish and nothing to be down.
flowchart LR
subgraph D["desktop"]
DF["channel folder"]
end
subgraph L["laptop"]
LF["channel folder"]
end
subgraph F["friend's box"]
FF["channel folder"]
end
DF <-. syncthing .-> LF
LF <-. syncthing .-> FF
DF <-. syncthing .-> FF
Three machines, one synced folder, nothing in the middle. Nothing here has to be up, reachable, or trusted.
Two consequences worth caring about:
- It works anywhere. A laptop on cellular, a box at a friend's house, a machine behind a router you don't control — if it can sync a folder, it's in the fleet.
- It stays private. The channel is your own repository on your own machines. No third party holds your agents' conversation.
- Signed everything. Each agent has its own key, so every message, order, result and review carries a fingerprint. On a team you can tell which agent did what, not merely which machine. Keys are pinned on first sight: a machine that has never met an agent trusts whatever the channel shows it first, and refuses any later change to that key. So the signatures prove continuity, not identity, until you check a fingerprint out of band — which is worth doing once, per agent, when you add a machine you did not set up yourself.
- Review and revision. Accept the work, or send it back with notes. Revisions are judgement, not failure — a job sent back five times has failed zero times.
- Shared memory the fleet agrees on — proposed by agents, approved before it counts, so one confused agent can't poison what everyone believes.
- An audit trail of every decision, hash-chained and backed by private Git.
- Approval gates for anything that shouldn't happen unsupervised — including from your phone over Telegram, bound to a hash of exactly what was approved.
- Your phone as a terminal.
ferry channel telegramturns a message into a signed order and sends the result back when a worker submits it, so the fleet is reachable from the one device you always have. One Telegram group, a topic per project — Ferryman builds the topics and remembers which is which. - A master, grants, and short-lived lease tokens. Authority is explicit, signed, and expiring — a leaked worker credential stops working on its own.
- A web dashboard to watch tasks, ledger, cost and learnings, and to approve or send work back from a browser — and to set secrets once, sealed to exactly the machines that should receive them.
- Recovery you can rehearse. Encrypted continuity packs, and a drill command that proves you can come back from a wiped machine.
- Opt-in sandboxing. Run each worker inside a fresh podman or docker
container, with a network-egress policy (
net = none | open | <name>) — one config line per project, at your direction. - A project cost estimator.
ferry cost plansizes a project against per-engine list rates (editable in arates.toml, no rebuild needed) so you can price work before committing to it. It is an estimate from a token heuristic, not a meter reading, and the rates are hand-maintained constants — check them against your provider's current pricing.ferry cost projectis the other half: it totals the usage workers actually recorded, per engine.
your-project/ <- the work. Ferryman never touches this.
your-project-ferryman/ <- the channel. Coordination and shared memory only.
Your agents already share the work repository. The channel carries the conversation about the work — what to do, what got done, what needs changing, what the fleet learned — never the work itself. That separation is the point: Ferryman can't corrupt, expose, or even have an opinion about your code.
Ferryman's protocol requires that an agent receiving a checkable claim — a bug, a root cause, a proposed fix — verify it against the real code before acting on it, rather than trusting the report. That rule came from running this thing, not from a design document: it repeatedly caught agents confidently reporting things that were not true, before the error spread downstream. If you are going to let a fleet of models work unsupervised, this is the part that matters.
A real recording, not a mockup — replay it from
docs/assets/demo.cast. Both agents here read and
write one folder, which is exactly what Syncthing gives each machine. Nothing is
running: no server, no daemon, no token.
export TELEGRAM_BOT_TOKEN=... # from @BotFather
export TELEGRAM_APPROVER_ID=... # your numeric user id; ask @userinfobot
ferry channel telegram --workspace ~/your-project-ferryman --agent youSend a line and it becomes an open order; /to <agent> <task> addresses one
machine; /status and /agents read the channel back. Results arrive in the
same chat with their signature verdict.
For more than one project, point it at a map instead and it serves a whole Telegram group — a topic per project, each wired to that project's channel:
ferry channel telegram --map ~/ferryman-comms/.tgferryman --agent youThe first run writes the map from the channels it finds, creates a topic for each one, and writes down the ids — Telegram has no way to list topics, so that file is the only record. See docs/TELEGRAM_TOPICS.md.
Only that one user id is obeyed — Telegram authenticates from.id server-side,
and the bridge refuses to start without an id to check, because a bridge that
started without one would take orders from whoever found the bot. Keep the token
in the environment or a mode-600 EnvironmentFile, never in the channel: it
syncs.
It writes the same signed artifacts ferry channel order writes, so it is not a
second control plane. Stop it and the fleet does not notice.
Never type a credential into it. A Telegram cloud chat is not end-to-end encrypted, so a token typed into one is stored on Telegram's servers, syncs to every device signed into that account, and stays in that history indefinitely. Nothing in the code can tell a task from a token, so this one is on you.
Secrets themselves are meant to move — that is what sealing is for. Set them with
ferry channel secret set, or in the dashboard, and they are encrypted to the specific
machines that should receive them before they ever leave the machine that typed them. What
must not travel is the plaintext, and the bridge is the one path that cannot carry ciphertext.
crates/ferry-deadman is its own thing, built and tested
here but useful with no Ferryman anywhere: timelocked succession for any git
repository. It seals an archive of a repo — git bundle --all plus any files you
name — so that it becomes decryptable only after a future point in time, enforced by
the drand threshold beacon rather than by a server, a company
or a person. While you keep running ferry-deadman heartbeat, the deadline keeps
moving away from you. The day you stop, the mathematics opens the envelope.
Ferryman's part is transport: a channel can carry the sealed artifact to your successors' machines, and it is ciphertext the whole way. Neither half needs the other. See its README.
| Guide | What it covers |
|---|---|
| Running in a container | podman and Docker, single or multi-project |
| How the channel works | delivery, failover, health |
| Architecture | boundaries and design constraints |
| Threat model | what it defends against, and what it does not |
| Writing a worker (server mode) | the lease-based worker protocol behind ferryman-server |
| Engine setup | pointing the worker at Claude Code, OpenCode, Codex or anything else — including API keys via credentials.json |
| Dashboard team and agent access | how the dashboard models human teammates vs agents; what is enforced today vs previewed |
| Getting started | install → first task walkthrough |
# in any repository you use Ferryman in
curl -fsSL https://raw.githubusercontent.com/estejosh/ferryman/main/scripts/ferry-up.sh | shInstalls or updates ferry, attaches the repository if it isn't attached, and then
tells you where you stand: version and commit before and after, whether your
signing key is unchanged, and a signature check on every artifact. Safe to run twice,
and it never rotates a key — it prints the fingerprint before and after so you can see
that rather than take it on faith. Windows: ferry-up.ps1, same thing.
Run it on every machine, then compare the commit in ferry --version. If they differ,
they aren't running the same Ferryman — which is the cause of a whole class of problems
that otherwise look like bugs.
sudo apt-get install -y libdbus-1-dev pkg-config # Debian/Ubuntu (Linux only)
cargo build --release --workspace
cargo test --workspacemacOS and Windows use their native keychains and need nothing extra.
Ferryman is in soak testing, and that is a request, not a disclaimer.
It works. It is also new, and the last set of problems in software like this is only ever found by other people running it on machines we do not have, for longer than we have. So: run it, and tell us what happened.
ferry soak # counts and category labels. Prints; sends nothing unless you ask.Then open an issue
or email lafamiliahale@gmail.com. "I ran it for a week and nothing broke" is a
real report and we want it — it tells us which platforms and shapes of fleet are
holding up, which is not knowable any other way.
If you would rather not copy and paste, set FERRYMAN_SOAK_URL and run
ferry soak --send. That is per invocation and opt-in twice over: there is no config
key that sends on its own, no timer, and a downloaded release has no endpoint set.
ferry soak --dry-run prints exactly what --send would transmit, from the same
value, so the two cannot disagree.
ferry soak carries no file paths, task text, prompts, results, agent output or
credentials. It is built out of values whose type cannot hold them rather than
filtered afterwards, and the whole thing is
one readable file if you would rather check that
than trust it.
Security issues go through private vulnerability reporting, not public issues.
Solid. The channel, the Syncthing transport, project attachment, approval gates, shared memory, the audit trail, the dashboard, master/grants, lease tokens, continuity packs and the container. Messages, orders, results and reviews all travel as files and cross networks without anything reachable. All covered by the test suite.
Working, but young. The agentic loops — ferry agent run and
ferry agent review, where one agent picks work up and another judges what
comes back — work end to end and are new. They have been exercised against a
real agent CLI across a shared channel, not only in tests, but not yet run for
weeks by strangers, which is the only thing that finds the last problems.
Not built yet. PostgreSQL, RBAC, and workflow graphs are design targets, not implementations.
Scope orders to a handful of tool calls. This is the single most useful thing
to know before your first task. "List the first three unwrap()s in this file"
completes reliably; "list every place that could panic in this file" often does
not. Bounded, specific orders are what the loop is good at, and they are also what
a reviewer can actually judge. With Claude Code as the engine there is a known
failure behind this rule, written up under Known
issues with the eight causes already eliminated.
Listed because you will hit some of these, and finding them written down beats discovering them. Every one is something we know about and intend to fix; none of them loses work or leaks anything.
-
Costs read as
$0.00for engines that don't account. Workers now record the token usage an engine prints andferry cost projecttotals it per engine; Claude Code's JSON output is accounted today, and prose-only engines stay at zero rather than a guess.ferry cost planremains what it was: an estimate from a token heuristic, labelled as one. -
ferry agent statusanswers "why is nothing happening" - worker liveness, the task it holds with heartbeat age, and the exact claim-gate decision (paused, outside working hours, you're typing, memory floor) with the setting that causes it. -
Engine prices and quality scores are hand-typed constants.
ferry cost ratesprints a table of list prices with no as-of date, an unrecognised engine is priced at a mid-range commercial rate — including a local model, which costs nothing — and thequalitycolumn is a static hint, not a measurement, wherever no outcomes have been recorded yet. Measured confidence is real and always shows its sample size (0.67 · 1/1 accepted); the priors beside it are opinion. -
ferry askreports its sources as signed without verifying them. The ledger half is genuinely verified; agent-profile and task claims are read from the channel and attributed by filename. On a fleet you control this is cosmetic. Do not rely on it as provenance until it verifies, and preferferry channel log/ferry channel tasks, which do. -
An addressed order reports as
claimedbefore anyone picks it up.--to fangshows as claimed by fang immediately, whether or not fang has started, because the holder is the assignee and no claim file is required. So "waiting for that machine" and "that machine is working" currently look the same. -
checkin = "off"inagent.tomldoes nothing. PRIVACY.md mentions it; no code reads it. Nothing sends automatically anyway — the check-in only ever runs when you runferry license checkin— so leaving the URL unset is the control that actually works. -
The SBOM omits the tray.
sbom.cdx.jsoncovers the workspace, andferryman-trayis excluded from the workspace with its own lockfile, so its dependencies are missing from it. The tray is optional and not installed by the install scripts. -
ferry bench --timeout-secsis accepted and ignored. The benchmark uses a fixed 300s per task. -
The MCP client has no timeouts. An external MCP server that hangs will block the gateway, and one that never answers at startup will stop
ferry mcp servefrom answering at all. Point it only at servers you trust to respond. -
External MCP tool output is not marked as third-party when it reaches an agent's prompt. Treat any MCP server you connect as something whose output can influence your agents, and prefer read-only ones.
-
Claude Code in
-pmode aborts on substantial tasks. Pointed at@anthropic-ai/claude-codeas the engine, small tasks complete and larger ones fail withExecution erroron stdout —is_error: true,terminal_reason: aborted_streaming,stop_reason: tool_use, typically at turn 8–12. It is intermittent and gets likelier the more work a task needs: "list the first threeunwrap()s in this file" completes reliably, "list every place that could panic in this file" mostly does not. Eliminated as causes, each tested directly: tool permissions, the git worktree the worker runs in, the scrubbed child environment, Ferryman's stall watchdog and timeout, output volume (a run producing 6,709 output tokens succeeded while one producing 2,094 failed), the publishing notice in the prompt (identical failure with and without it), and therenice +10applied to the child. Failed runs cost nothing — they abort before or during the first request. The practical rule until this is understood: scope orders to a handful of tool calls. Bounded, specific tasks are what this is good at anyway. -
Windows has less test coverage than Linux and macOS. CI runs all three, but several suites are Unix-only, and the two most recent platform bugs were both Windows-only and both found by running on a real machine rather than in review.
-
Nobody maintaining Ferryman runs it on a Mac day to day. CI builds and tests macOS on every push and it is green, but green tests are not the same as use. If you run Ferryman on a Mac, a soak report is worth more from you than from anyone else.
Fixed in 0.4.0 and worth knowing if you ran an earlier build: a worker could kill
its own running task in an unrecoverable retry loop; a peer could forge another
machine's signing key through several CLI paths; the container runner put
credentials on the process argument list; the audit ledger reported itself tampered
after ordinary two-machine use; and shell task sources never worked on Windows. See
the changelog.
Sandboxing is yours to turn on. By default a worker runs with the privileges
of the account that started it. Point sandbox at an image in agent.toml (or
ferry enable --sandbox IMAGE) and Ferryman runs each worker inside a fresh
podman or docker container instead, with a network-egress policy so a hermetic
task can be cut off from the network entirely. The container path is built; the
per-platform bind-mount wrinkles (SELinux, macOS, WSL) are still being smoothed
out. If you don't direct it to sandbox, it doesn't.
Nothing, on its own. There is no timer, no background sender, and no daemon that
phones home. ferry license checkin is a command you run, and a downloaded
release has no endpoint configured at all — it sends nothing until you set
FERRYMAN_CHECKIN_URL yourself.
When you do run it, this is the entire payload:
{
"deployment_id": "a41f...",
"emails": ["you@example.com"],
"seats": 1,
"computers": 2,
"mobile_devices": 0,
"over_limit": false,
"version": "0.5.3",
"sent_at": "2026-08-12T09:00:00Z"
}deployment_id is a random number generated on your machine — not derived from
your hardware, hostname or network. There is no field for a task, a prompt, a
file name, or anything your agents produce, so no amount of misuse can put one
there. ferry license checkin --dry-run prints the exact bytes and sends
nothing. PRIVACY.md walks the payload field by field.
The same is true of ferry soak: it sends a report only if you set
FERRYMAN_SOAK_URL and pass --send, per invocation. Never automatically.
Ferryman is source-available under the Ferryman Source-Available License: free for any non-production use, and free in production for up to 2 people, on 2 computers and 2 phones/tablets.
Agents are unlimited and never counted. One person running twenty agents across two computers is one Seat. Beyond that it is $60 per additional seat per year, dropping with volume — priced per human, not per machine or agent. See COMMERCIAL.md.
One obligation, and ferry enable discharges it for you. Section 6 asks any
project that uses Ferryman to carry a root-level FERRYMAN.md saying so. ferry enable writes that file into your project and tells you it did; if you already
have one, it leaves yours alone. There is nothing else to remember, and nothing
to strip: registration is a local file, the check-in endpoint is unset in every
release, and failing to report is explicitly not enforced.
Free production use asks for a contact email. Ferryman never sends anything on
its own. There is no timer and no background sender: ferry license checkin is
a command you run, and a downloaded release has no endpoint configured at all, so
it sends nothing until you set FERRYMAN_CHECKIN_URL yourself. Whether you are
inside the free tier is computed on your own machine — run ferry license status
and it counts your seats, computers and phones locally and lists what it counted.
See What Ferryman sends for the whole payload.
You may fork it and take the reporting out. Section 5 asks a redistributor not to weaken the Seat, Computer and Mobile Device limits — the commercial terms — and nothing more. It does not oblige anyone to keep reporting behaviour, and there is none to keep: a release ships with no endpoint. If you were reading §5 as "you may not fork out the phone-home", that is not what it means, and the wording was changed to stop it reading that way.
See CONTRIBUTING.md and SECURITY.md. Agents and operators working on Ferryman itself should read the operator brief first.
Ferryman is provider-neutral and runs no models itself. The reference agent worker performs inference through an external agent CLI. This project was first piloted on honemesh.net, credited for the inference work that shaped it.
Ferryman bundles Syncthing (MPL-2.0), unmodified — see THIRD_PARTY.md.
