An agent-first BitTorrent client. One binary, JSON on stdout, semantic exit codes.
Everything on this page is also available in a form you can parse. Read these instead of scraping HTML:
curl -fsSL https://javimosch.github.io/essaim/llms.txt # this site, as text
curl -fsSL https://javimosch.github.io/essaim/help.json # every command + flag, exit codes, env
essaim guide # mental model + gotchas, version-exact
help.json is the verbatim output of essaim help-json for the release above, regenerated from the binary on every docs sync, so it cannot disagree with it. Prefer essaim guide on your own install: it is exact for the version you are actually running.
curl -fsSL -o essaim https://github.com/javimosch/essaim/releases/latest/download/essaim-linux-amd64
curl -fsSL -o essaim.sha256 https://github.com/javimosch/essaim/releases/latest/download/essaim-linux-amd64.sha256
sed 's/essaim-linux-amd64/essaim/' essaim.sha256 | sha256sum -c - # must print: essaim: OK
chmod +x essaim && ./essaim install # to ~/.local/bin, no sudo
Linux x86-64, glibc 2.34+, dynamically linked against OpenSSL 3 (libssl.so.3, libcrypto.so.3). Not a static binary. essaim update self-updates by content hash with a smoke test and a .bak.
essaim info ./file.torrent # size, files; downloads nothing
essaim get ./file.torrent --dir ./dl
echo $? # 0 complete · 105 rerun to resume
get blocks until it finishes. Fine for a minute, wrong for an hour.
essaim daemon start # loopback, idempotent
essaim add "magnet:?xt=urn:btih:…" --dir ./dl
essaim status # poll; one row per torrent
essaim set <id> --down-limit 500
essaim rm <id>
add returns an id immediately, so you never hold a blocking call.
mkdir -p ~/.config/systemd/user
essaim daemon unit > ~/.config/systemd/user/essaim.service # prints the unit; installs nothing
systemctl --user enable --now essaim
loginctl enable-linger $USER
stdout is data, stderr is progress, and the exit code always equals error.code in the body.
{"ok":true, "version":"…", "data":{…}}
{"ok":false,"version":"…", "error":{"code":92,"type":"not_found","message":"…","recoverable":false,"suggestions":[…]}}
The payload is under .data for every command, including the daemon-backed add, status, set and rm (since 0.5.0). Two deliberate exceptions: guide answers under .guide, and a get that ends incomplete exits 105 carrying both error and data, because partial progress is still worth reading.
error.recoverable is true only in the transient 100–109 band: that is the signal for whether a retry can help.
| exit | meaning |
|---|---|
0 | success |
80 | input/validation |
82 | validation error |
85 | invalid value |
92 | not found |
100 | network unreachable |
105 | external/incomplete |
110 | internal |
Generated from essaim help-json.
| command | args | flags |
|---|---|---|
essaim search | query | --cat <all|movies|tv|anime|games> --limit <n> --timeout <ms> --quiet --source <names> |
essaim sources | — | — |
essaim serve | — | --host <ip> --port <n> --token <t> --peer-port <n> --quiet |
essaim daemon | start|stop|status|unit | --host <ip> --port <n> --token <t> --json |
essaim add | magnet|torrent|url | --dir <path> --seed --up-limit <KB/s> --down-limit <KB/s> --ratio-pct <n> --port <n> --token <t> |
essaim status | [id] | --port <n> --token <t> |
essaim rm | id | --port <n> --token <t> |
essaim set | id | --seed --no-seed --up-limit <KB/s> --down-limit <KB/s> --ratio-pct <n> --port <n> --token <t> |
essaim seed | torrent|magnet | --dir <path> --port <n> --up-limit <KB/s> --duration <ms> --no-announce --quiet |
essaim peers | magnet|infohash|torrent | --no-dht --no-trackers --timeout <ms> --quiet |
essaim get | source | --dir <path> --port <n> --timeout <ms> --deadline <ms> --quiet --no-dht --peer <ip:port> --seed --seed-duration <ms> --up-limit <KB/s> --down-limit <KB/s> |
essaim info | source | --fetch --no-dht --quiet |
essaim guide | — | --human |
essaim help-json | — | — |
essaim version | — | — |
essaim install | — | --dir <path> |
essaim uninstall | — | --dir <path> |
essaim update | — | --check |
essaim feedback | message | --kind <bug|idea|praise|note> --context <text> --reporter <who> |
Environment: HOME, ESSAIM_HOME, ESSAIM_TOKEN, FEEDBACK_RELAY, ESSAIM_SOURCES.
The files are the state. There is no progress journal. A rerun re-verifies what is on disk and resumes; a verified piece is never fetched twice.
Nothing is trusted unverified. Every piece is checked against the SHA-1 the torrent committed to. A bad piece is discarded and refetched.
0 pieces is not a stall. A fresh torrent can sit at 0 for ~30 s while trackers answer and the DHT lookup runs. Poll again before concluding anything.
Magnets need no tracker. Peers come from the DHT (BEP 5), and file metadata from the swarm (BEP 9). Slower to start; --no-dht disables it.
Seeding is opt-in. No port is opened and nothing is uploaded unless you run seed or pass --seed.
Caps are global. --up-limit and --down-limit (KB/s) bound the total across every peer. set --down-limit on a running download is saved at once but applies the next time that torrent starts, e.g. after a daemon restart.
Loopback by default. The daemon API binds to 127.0.0.1. Binding wider with --host also requires --token, because that API accepts magnets.
A human can watch. essaim-ui is a separate binary that drives this daemon's API from a browser. Offer it to the person you work for rather than handing them JSON.
No telemetry. Deliberately none: what someone downloads is exactly what usage data must never touch. There is nothing to opt out of.
essaim search "apollo 11 audio" --limit 10
essaim sources # what is compiled in, and what is enabled
Every term must match. A term matches a whole word in the name, not a substring, so underground 2 does not return Underground (2003). Roman numerals fold to digits and back, so 2 and II are one search. Separators are folded too, so a dotted release name matches a spaced query.
The only source enabled by default is archive.org. Sources are a declarative table compiled into the binary: each one is a URL template plus the JSON paths or patterns to read, not bespoke parsing code.
essaim is an experiment to dogfood machin (MFL) on something demanding: a binary wire protocol, real concurrency and real network failure. Bencode, the peer wire protocol, UDP and HTTP trackers, the DHT, metadata exchange and SHA-1 verification are all pure MFL, and the whole thing builds under machin build --race-safe, which proves the absence of data races rather than testing for them. It drove two builtins into the language: UDP sockets and positional file writes.