Skip to content

Latest commit

 

History

379 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ferryman - self-hosted, local-first team coordination for AI agents

CI container ghcr.io license free tier

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.

Nothing to type

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.

Or from a terminal

# 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 here

Windows, in PowerShell:

irm https://raw.githubusercontent.com/estejosh/ferryman/main/scripts/install.ps1 | iex

No 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.

Why this shape

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
Loading

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.

What you get

  • 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 telegram turns 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 plan sizes a project against per-engine list rates (editable in a rates.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 project is the other half: it totals the usage workers actually recorded, per engine.

Two repositories, on purpose

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.

Agents that check each other

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.

What it looks like

Two agents on one channel: an order is issued, claimed, submitted, sent back with notes, revised, and accepted — every signature verifying.

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.

From your phone

export TELEGRAM_BOT_TOKEN=...        # from @BotFather
export TELEGRAM_APPROVER_ID=...      # your numeric user id; ask @userinfobot
ferry channel telegram --workspace ~/your-project-ferryman --agent you

Send 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 you

The 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.

ferry-deadman — a sub-product in this repository

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.

Documentation

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

Keeping a fleet on the same build

# in any repository you use Ferryman in
curl -fsSL https://raw.githubusercontent.com/estejosh/ferryman/main/scripts/ferry-up.sh | sh

Installs 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.

Building from source

sudo apt-get install -y libdbus-1-dev pkg-config   # Debian/Ubuntu (Linux only)
cargo build --release --workspace
cargo test --workspace

macOS and Windows use their native keychains and need nothing extra.

Soak testing — please break it

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.

Current status — honest about where this is

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.

Known issues

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.00 for engines that don't account. Workers now record the token usage an engine prints and ferry cost project totals it per engine; Claude Code's JSON output is accounted today, and prose-only engines stay at zero rather than a guess. ferry cost plan remains what it was: an estimate from a token heuristic, labelled as one.

  • ferry agent status answers "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 rates prints 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 the quality column 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 ask reports 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 prefer ferry channel log / ferry channel tasks, which do.

  • An addressed order reports as claimed before anyone picks it up. --to fang shows 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" in agent.toml does nothing. PRIVACY.md mentions it; no code reads it. Nothing sends automatically anyway — the check-in only ever runs when you run ferry license checkin — so leaving the URL unset is the control that actually works.

  • The SBOM omits the tray. sbom.cdx.json covers the workspace, and ferryman-tray is 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-secs is 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 serve from 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 -p mode aborts on substantial tasks. Pointed at @anthropic-ai/claude-code as the engine, small tasks complete and larger ones fail with Execution error on 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 three unwrap()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 the renice +10 applied 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.

What Ferryman sends

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.

License

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.

Contributing

See CONTRIBUTING.md and SECURITY.md. Agents and operators working on Ferryman itself should read the operator brief first.

Acknowledgments

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.

About

Private coordination for a fleet of AI agents, across machines you own. Agents leave each other files; Syncthing carries them. No server, no ports, no cloud in the middle.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages