AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
An agent-first BitTorrent client in pure MFL: one 144 kB binary, JSON on stdout. Trackers (BEP 3/15), peer wire, magnets (BEP 9), SHA-1 verification. An experiment to dogfood machin.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
An agent-first BitTorrent client. One 242 kB binary, JSON on stdout, no daemon
required.
Written in MFL — bencode, the peer wire
protocol, trackers, BEP 9 metadata exchange and SHA-1 verification are all pure
MFL. No libtorrent, no Node, no Python.
Progress goes to stderr and the result to stdout, so essaim get … | jq works
while you still watch it run. Exit codes are semantic: 0 complete, 105
incomplete (rerun to resume), 100 no peers, 82 bad input — and the code
always equals error.code in the body.README 전문 보기 ↗An agent-first BitTorrent client. One 242 kB binary, JSON on stdout, no daemon required.
Written in MFL — bencode, the peer wire protocol, trackers, BEP 9 metadata exchange and SHA-1 verification are all pure MFL. No libtorrent, no Node, no Python.
essaim search "apollo 11 audio" --limit 10 # across the enabled indexes
essaim peers <infohash> --no-trackers # who is in the swarm, via DHT only
essaim info ./file.torrent # read metadata, download nothing
essaim get ./file.torrent --dir ./dl # or a magnet, or a https URL
essaim info "magnet:?xt=urn:btih:…" --fetch # list the files, download nothing
essaim seed ./file.torrent --dir ./dl --up-limit 500 # share, capped
essaim guide # the whole mental model, embedded
Progress goes to stderr and the result to stdout, so essaim get … | jq works
while you still watch it run. Exit codes are semantic: 0 complete, 105
incomplete (rerun to resume), 100 no peers, 82 bad input — and the code
always equals error.code in the body.
| Trackers | UDP (BEP 15) and HTTP (BEP 3) |
| Peers | BEP 3 wire protocol, 16 KiB blocks pipelined per piece |
| Magnets | BEP 9 metadata exchange over the BEP 10 extension protocol |
| DHT | BEP 5 Kademlia — a bare magnet with no trackers works |
| Verification | nothing is believed until a piece's SHA-1 matches the torrent's commitment |
| Resume | no progress journal — the files on disk are the state |
| Seeding | opt-in, with a global upload cap in KB/s |
| Search | indexes as declarative config rows |
.torrentdiff -r against the .torrent download: no differencessearch → 7.2 MB archive.org item → get → 14/14 pieces in 6.4 s
→ unzip -t reports no errorsmachin build --race-safe passes: engine, seeder and daemon are all
proved data-race freeessaim is agent-first and that is the point — but a person looking at a download queue would usually rather see it than read JSON. essaim-ui is a separate binary that drives this daemon's HTTP API from a browser: add by magnet, per-torrent upload and download caps, a stop-at ratio, the full path on disk, and remove-with-data.
curl -fsSL -o essaim-ui https://github.com/javimosch/essaim-ui/releases/latest/download/essaim-ui-linux-amd64
chmod +x essaim-ui && mv essaim-ui ~/.local/bin/
It is a separate program and no part of essaim depends on it. It is also
mentioned in essaim guide, so an agent driving this daemon can offer it to
the human it is working for rather than leaving them reading JSON.
Sources are config rows, not code — adding one is a table entry in
src/indexer.src: a URL template, how to walk the response,
and where the fields live.
Only archive.org is enabled out of the box. The other rows ship disabled
and exist as worked examples of the two supported response shapes — flip
enabled to 1, or add your own row, and rebuild.
| source | kind | category | default |
|---|---|---|---|
| archive.org | JSON (.torrent links) | all | enabled |
| others (6) | JSON / RSS examples | various | disabled |
Enable a disabled row for one run without rebuilding:
essaim sources # the row names, and which are live
essaim search "…" --cat games --source <name> # one run, one row
ESSAIM_SOURCES=all essaim search "…"
Two response shapes cover every index worth querying: a JSON API addressed by
path templates ({i} = row index) and an RSS feed split on <item> and read
with regexes. A source that errors is skipped with a note on stderr — one dead
index must not sink a search. Rows marked filter: "local" are narrowed by essaim
itself, which every index row now is: some ignore the query outright, and the
ones that rank by relevance still return rows that miss a term.
Every term of a query must match, as a whole word rather than a substring —
so underground 2 does not return Underground (2003), whose only 2 is inside
2003. Roman numerals fold to digits and back (need for speed 2 finds the II
releases), and separators are folded, so a dotted release name matches a spaced
query.
essaim sources lists what is compiled in and which rows are live.
get and seed block, which is fine for something that finishes in a minute and
useless for anything that doesn't — an agent can't hold a call for hours. So
there is a daemon:
essaim daemon start # loopback, idempotent
essaim add "magnet:?xt=urn:btih:…" --dir ./dl --seed # returns an id immediately
essaim status # poll; JSON, one row per torrent
essaim set <id> --no-seed --up-limit 512 # adjust a torrent in place
essaim rm <id>
essaim daemon stop
set exists because the control plane could create and destroy a torrent but
not change one: seed, up_limit and down_limit were fixed at add time, so
altering a single integer meant rm + add, which threw away the record and
re-resolved the metadata. It patches only the fields you pass, so set --seed never
silently resets an upload cap.
essaim get "magnet:…" --dir ./dl --down-limit 512 # KB/s, global across peers
essaim add "magnet:…" --dir ./dl --down-limit 512
essaim set <id> --down-limit 512
It paces how fast blocks are drained from the socket, not how fast they are requested. Requests stay pipelined — a piece still costs one round trip — and TCP's window does the actual limiting. Pacing the requests instead would serialise the pipeline and still not stop a fast peer from filling the buffer.
Measured against a live swarm, 45 s each, uncapped ≈ 1040 KB/s:
| cap | completed-piece throughput |
|---|---|
| 512 KB/s | 353 KB/s |
| 128 KB/s | 85 KB/s |
Both are under their cap and the ratio between them is 4.15 against the 4.0
asked for. The figures sit below the cap because bytes counts only
completed pieces, and with dozens of peers there is always data in flight
that has not finished a piece yet.
Unlike the other three, a change to --down-limit applies the next time the
torrent starts. The pacer takes its interval when the job starts, and
restarting a healthy download to apply a new one throws away its peers — the
resolver then has to find them again, which on an intermittent swarm can leave
the torrent sitting in resolving for minutes. Trading a working download for
a faster-applied speed limit is a bad deal.
--up-limit bounds the rate and never ends. A ratio is the thing that
ends it:
essaim add "magnet:…" --dir ./dl --seed --ratio-pct 150 # stop after uploading 1.5x
essaim set <id> --ratio-pct 50 # or change your mind later
It is expressed in percent of what you downloaded — 50 is 0.5x, 200 is
2x, 0 never stops — because there are no floats here and percent is what the
number means to a person.
The ratio is checked in two places, after every pump and before starting a
seeder, because a limit that only stopped a running seeder would be undone by
the next loop restarting it. Your seed flag stays your intent; the ratio is
what decides whether it is acted on, so "I asked to seed" and "it has finished
seeding" remain two different facts.
It is single-actor: one loop owns the job table and handles each request
inline, so there is no lock and no second writer. That isn't caution for its own
sake — the download engine already runs a goroutine per peer, and an HTTP handler
touching the same state would make machin build --race-safe refuse to build.
A control-plane request takes microseconds against a download that takes minutes.
A freshly added torrent sits in resolving, then at 0 pieces, for up to ~30 s
while it announces to trackers and runs a DHT lookup. That is discovery, not a
stall — which is exactly why this belongs in a daemon rather than a call an agent
has to hold open.
State lives in ~/.essaim/ (override with ESSAIM_HOME): a small registry of
which torrents you asked for, plus a .torrent cache. Per-torrent progress is
still re-derived from the files on disk, so a restart re-verifies rather than
trusting a journal — a completed torrent comes back instantly, with no network
at all.
mkdir -p ~/.config/systemd/user
essaim daemon unit > ~/.config/systemd/user/essaim.service
systemctl --user daemon-reload && systemctl --user enable --now essaim
loginctl enable-linger $USER # keeps it running after you log out
essaim daemon unit prints a unit rather than installing one. Registering a
service is privileged and host-shaped, and cli-daemon-spec deliberately leaves
boot persistence out of scope, so essaim hands you a file to read first. No sudo
needed for the user-unit path.
The control API binds to loopback by default. Reaching further needs both
--host and --token, because that API accepts magnets. The peer port (6881) is
the only thing meant to face the internet.
Off by default. essaim opens no listening port and uploads nothing unless you ask:
essaim seed ./file.torrent --dir ./dl --up-limit 500 # share what you have
essaim get ./file.torrent --dir ./dl --seed # download, then keep seeding
--up-limit is a global cap in KB/s — a token bucket shared by every
connection, not a per-peer limit. A burst of up to 4 blocks is allowed, so a very
short transfer can measure a few percent over the cap; over any real duration it
converges.
--down-limit is the mirror image, in the same units and with the same global
shape:
essaim get ./file.torrent --dir ./dl --down-limit 500 # leave the link usable
essaim add "magnet:…" --dir ./dl --down-limit 500 # same, under the daemon
essaim set <id> --down-limit 0 # 0 means uncapped
It paces how fast blocks are drained from the socket, not how fast they are requested. Requests stay pipelined, so a piece still costs one round trip, and TCP's own backpressure does the limiting. Pacing the requests instead would serialise the pipeline — slower for the same cap, and still no bound on a fast peer filling the receive buffer.
One caveat: set --down-limit on a running download is saved at once but
takes effect the next time that torrent starts (a daemon restart, say). A job's
pacer is created with its rate when the job starts, and the live one keeps it.
Setting it at add time has no such gap.
Two machines, no tracker and no DHT:
# on the sender
essaim seed ./f.torrent --dir ./dl --port 51413 --no-announce
# on the receiver
essaim get ./f.torrent --dir ./in --peer 10.0.0.2:51413 --no-dht
Follows the cli-specs family:
| spec | status |
|---|---|
| cli-output-spec | stdout=data, stderr=context, semantic exit codes, typed errors, help-json |
| cli-guide-spec | essaim guide — model, loop, concepts, gotchas, embedded in the binary |
| cli-update-spec | essaim update (content-hash + smoke test + .bak), install/uninstall |
| cli-feedback-spec | essaim feedback "…" --kind bug — dual-write with an idempotency key |
| cli-telemetry-spec | deliberately not adopted (see below) |
| cli-daemon-spec | serve + /_health + token-gated /_shutdown + daemon start|stop|status, loopback by default |
Success is {"ok":true,"version":"…","data":{…}} and failure is
{"ok":false,"version":"…","error":{…}} with a matching exit code. As of 0.5.0 the
daemon-backed commands (add, status, set, rm) obey this too — before that
they printed the daemon's raw HTTP reply, so their payload sat at the top level
instead of under .data. Parsing no longer depends on which commands happen to be
daemon-backed.
Two deliberate exceptions: guide puts its payload under .guide (that shape is
cli-guide-spec's, not ours), and a get that ends incomplete exits 105 with
both an error and a data block, because partial progress is still worth
reporting.
No telemetry, on purpose. cli-telemetry-spec §8.1 says a tool that cannot satisfy the must-not-send list for its domain should ship none. For a BitTorrent client the signal is weak and the trust cost is high: what someone downloads is exactly what usage data must never touch. There is nothing to opt out of.
curl -fsSL -o essaim <release-url>/essaim-linux-amd64
chmod +x essaim && ./essaim install # relocates to ~/.local/bin, no sudo
Linux x86-64, glibc 2.34+, dynamically linked against OpenSSL 3
(libssl.so.3, libcrypto.so.3). A fully static build is blocked on static
OpenSSL archives for musl.
./build.sh # machin encode src/*.src > essaim.mfl && machin build essaim.mfl
./tests/run.sh # 345 assertions across 11 module suites + a CLI suite
Tests need no network: the tracker suite stands up a fake BEP 15 tracker, the engine suite stands up a real seeder speaking the actual peer protocol, and the index suite parses recorded response fixtures.
watch (folder monitoring) yet.--no-dht turns it off.essaim sources lists
what is compiled in and which rows are enabled.essaim is an experiment: it exists to dogfood
machin by building something demanding in
it — a real binary protocol, real concurrency, real network failure modes — and
it drove two additions into the language along the way (udp_socket /
udp_sendto / udp_recvfrom and write_file_at).
It is provided as-is, with no warranty and no responsibility taken for how it is used or for anything any third-party index returns. You are responsible for what you download and for complying with the law and the terms of any service you point it at. See the MIT licence.
Designed and written with Claude Code, which holds authorship of essentially all of this repository — the protocol implementations, the test suites, and the machin language changes underneath it.
MIT — see LICENSE.