Engineering preview Read from src/ at commit time
CLI reference
Every command, flag, exit code, and schema name below was read out of the
checkout in src/. Where a behaviour could not be
confirmed from source it was run in a worktree, and where it still could
not be confirmed it is not on this page.
No section matches that filter. Clear the box to see all sections.
What this page is
Tether is an ENGINEERING PREVIEW. The reference below documents what the code in this checkout does today, including the parts that refuse.
-
tether doctorreportstether.doctor.v1with 11 stable check ids and verdictNOT_READYon this machine. -
tether fabric run image-batchplans a real folder, runs it locally on an explicit--local, or makes one receipt-gated peer attempt on--remote. It never falls back silently. -
tether analyzeis not a subcommand of thetetherentry point. It is a separate module — see analyze.
Entry point
bin/tether is a 185-byte shim: it puts the
checkout's src/ on
sys.path and calls
tether_cli.main(). An installed copy gets the
same callable from the tether console script
declared in pyproject.toml.
PYTHONPATH=src python3 bin/tether --version
--version
prints one line and never dispatches:
version=, commit=,
dirty=, mode=,
root=. Passing any argument after it exits
2.
Install
There is no public installer to download yet. Two routes work today, and both are verified by the tests in this checkout.
git clone <your copy of the repository> tether && cd tether PYTHONPATH=src python3 bin/tether --version
python3 tools/tether_build_cli.py --version 0.1.0 --build 1 --platform macos-arm64 --out-dir dist/cli (cd dist/cli && shasum -a 256 -c tether-0.1.0-macos-arm64.tar.gz.sha256) sh tools/install.sh --yes --archive dist/cli/tether-0.1.0-macos-arm64.tar.gz export PATH="$HOME/.local/opt/tether:$PATH"
The installer verifies the sha256 pin before it unpacks and never edits your shell startup files. The macOS archive is unsigned and unnotarized. The download page carries the Windows route and the current release status.
Getting started
Install Tether, add one machine you can SSH to, confirm it in the pool, then ask a test run to split. The pair line below is a dry run: it prints the plan and does not open a connection.
The install section above is the route that works today. From a checkout, the check that the entry point runs is tether --version. It prints the build identity and dispatches nothing.
Add a donor with tether pair and the SSH host you already use. Pass --dry-run first. It prints eight plan lines and does not call SSH or write.
tether pair donor-example --dry-run PLAN reachability probe SSH reachability (no remote call) PLAN backing detect mount or explicit home backing (no remote call) PLAN install stage the sealed node runtime (no write) PLAN identity ingest a peer identity receipt (no write) PLAN capacity ingest a node-signed capacity receipt (no write) PLAN qualification measure and authorize the python-cpu-parallel-v1 class (no run) PLAN keepalive register refresh source and check receipts (no write) PLAN pool show the final pool line (no write)
The host in the sample is not a machine. A real host is the name in your SSH config. The fabric section documents the lower-level invite command. That command is not how you add the first donor.
tether pool is read-only. It prints this machine, then each paired peer, and it names the reason a peer contributes nothing.
Pool: 16 cores, 128 GiB, GPU 1 (Apple M3 Max) (0 of 3 peers contributing)
CPU cores RAM GPU
local 16 128 GiB 1 (Apple M3 Max)
connected 0 0 GiB none
reserved 0 0 GiB none
usable now 0 0 GiB none
effective 16 128 GiB 1 (Apple M3 Max)
effective = local + usable now. connected/reserved are shown, never added.
One read-only run on the machine that edited this page. Your pool will differ. Peer rows are left off because they name machines. On that run, 0 of 3 peers were contributing.
tether test --auto collects the selection and asks the fabric to split it. On a busy machine it refuses to split before it looks at any peer.
tether test: 1-minute load 308.57 exceeds the quiet ceiling 4.00 (0.25 per core). This measurement would be a contention artifact, which this repo has published and retracted once.; falling back to serial (pass --allow-loaded to split anyway)
What that command printed here, against a path that does not exist, so it could not start the suite. The load number is this machine at that moment, not a limit you should copy. The ceiling on this 16-core machine was 4.00.
The ceiling is the core count times 0.25, and at least 1. Wait until the 1-minute load is at or under it. Pass --allow-loaded only if you accept a contended run. That flag does not make a slow donor faster.
tether doctor and tether analyze do not add a donor. They stay in their own sections.
Dispatch modes
A bare tether <command> offloads to the
borrow pool and falls back local on transport failure. The mode flags
below change that decision.
--auto-
Decide per policy in
src/tether_route.py. Heavy batch work can offload; interactive or trivial work execs the real binary locally so streaming semantics survive. With no command after it, exits2. --local- Force local execution. No pool probe, no remote sync.
--here- Run in the current directory, syncing cwd to the remote. The default path syncs cwd too.
--status-
Print the borrow pool: each host
ALIVEwith cores, load and free score, orDOWNwith a reason. On a TTY it renders a table; piped it stayskey=valuelines. -
--action-manifestFILE -
With
--auto, execute exactly one declared ActionSpec and skip cwd discovery. A lost ActionSpec or typed receipt identity is refused, not guessed. -
--shim-adapterCMD - The internal declaration boundary the transparent shims call. Not a user-facing surface.
Under TETHER_ACTIONSPEC=1 local fallback is
REFUSED when no remote is available — the isolation contract only makes
declared inputs visible, so a silent local rerun would be a different
execution.
How splitting works
Tether can run part of a job on a paired donor. A stored receipt that the peer won is not, by itself, a reason to send the job. The receipt is a price only when both legs were timed at a 1-minute load of 150 or below, the campaign's load spanned at most 2.0, this directory has at least 3 measured sync-ups, and the plan still finishes sooner after the stage cost. Otherwise the job stays here.
-
Route.
tether --autodecides per policy insrc/tether_route.py. Interactive and trivial commands exec the real binary locally. -
Sync. The working directory is copied to an isolated
directory on the peer, the command runs there, and results sync back.
Under
TETHER_ACTIONSPEC=1only declared inputs cross the wire. -
Verify.
tether sweepchecks a sha256 verdict per unit. If the peer disagrees on any unit, or is unreachable or slow, that work finishes here. - Remember. A paired result is stored as a receipt. One decisive remote loss keeps the class local. A win measured above a 1-minute load of 150, or across a load span above 2.0, is kept as evidence and is not a price.
tether sweep --run 'CMD {}' --root DIR --peer HOST
tether sweep --run 'CMD {}' --root DIR --peer HOST --compare 5
Bench numbers
BENCH, not a promise. Two paired campaigns of the same Python
CPU-parallel workload, copied from committed evidence in
web/landing/fabric-measured.json. Each ran 5
pairs on a loaded Mac, outputs sha256-identical in both.
| Campaign | Median local | Median peer | p95 local | p95 peer | Mac load | Gate verdict |
|---|---|---|---|---|---|---|
| 2026-10-10 16:34Z | 309.65 s | 108.29 s | 349.12 s | 127.00 s | 500-875 | qualified |
| 2026-10-10 18:57Z | 46.93 s | 92.20 s | 97.06 s | 104.87 s | about 150 | stays local |
The second campaign lost, and the gate kept the class on the Mac. Both ran on a loaded machine, so they are client-observed measurements, not clean benchmarks. Your numbers will differ.
The qualified row is that day's gate verdict, copied from the evidence file. It is not a price the planner will use now. That campaign ran at Mac load 500-875, and a ratio whose legs ran above a 1-minute load of 150 is not a price.
What it does not do
- It never runs an arbitrary command on a peer you have not paired.
- It never sends your files anywhere but a peer you own.
- It does not promise a speedup; a loss is recorded and honoured.
- It has no account, billing or telemetry.
Why did my job stay local?
A split is declined when the CLI cannot show it would finish sooner. Match the line you see. The text in each block is the string the code prints.
Before any peer is considered
Before any peer is read, tether pool prints a precondition block. The peer rows under it are not the cause. tether test --auto does not print the kill-switch sentence: with TETHER_FABRIC_AUTO=0 it leaves the fabric lane without a line and runs the selection on this machine. tether pool is the place that names it.
Nothing will be dispatched: N precondition(s) refuse before any peer is considered. The peer rows below are not the cause.
automatic Fabric placement is switched off (TETHER_FABRIC_AUTO=0)
fix: unset TETHER_FABRIC_AUTO # or set it to 1
Automatic placement is switched off (TETHER_FABRIC_AUTO=0).
no peer is paired with this controller
fix: python3 tools/tether_hk_peer.py enroll --host <ssh-alias> --peer-id <peer>
authority watermark unreadable: <type>: <error>
fix: move the unreadable authority watermark file aside; it is rebuilt on the next dispatch
wall clock moved backwards: now=<now> < watermark=<previous> (<delta>s)
fix: correct this machine's clock; no peer is placed until it passes the watermark
now=<now> is <delta>s ahead of the wall clock <wall>, beyond MAX_AUTHORITY_FUTURE_SKEW_S=120
fix: correct this machine's clock; no placement accepts a forged reading
<ledger> unreadable: <type>: <error>
fix: repair or remove the <ledger> file, or set <flag>=0 to run without it
The pairing line on this page is tether pair. The pool's own fix line, quoted above, is the lower-level enroll command. Either one is a real way to add a peer. The ledgers are the regret ledger, the sync ledger, and the liveness ledger. A path that cannot be named for one peer is that peer's refusal, not a whole-fleet one: the line says cannot be derived, and the fix is to set that variable to a normalized absolute path.
What tether test --auto prints
When every peer refuses, tether test --auto prints one full line per peer. It does not cut the list after the first peer. With no peer lines, it prints the decision reason instead.
tether test --auto: runs here: every peer refused tether test --auto: <peer>: <refusal> tether test --auto: runs here: Fabric pairing is absent tether test --auto: runs here: Fabric receipt authority inactive: <refusals> tether test --auto: runs here: regret ledger unreadable: <type>: <error> tether test --auto: runs here: sync ledger unreadable: <type>: <error> tether test --auto: runs here: liveness ledger unreadable: <type>: <error> tether test --auto: runs here: wall clock moved backwards: now=<now> < watermark=<previous> (<delta>s); no placement until the clock passes the watermark
A stored win is not always a price
A donor's old win is a price only when both legs of every pair were timed at a 1-minute load of 150 or below and the whole campaign spanned at most 2.0. Anything else is appended to that peer's refusal as qualification ratio is unmeasured or unrecorded. Only measured is a price.
qualification ratio is unrecorded: no per-leg load evidence: this receipt cannot show its local and remote legs were timed under the same conditions, so it carries no ratio qualification ratio is unrecorded: per-leg load evidence is unpaired (<n> local, <m> remote); a pair needs one sample per leg qualification ratio is unmeasured: <label> leg <index> ran at 1-minute load <value>, beyond the absolute bound 150; a box this loaded cannot produce two comparable legs, so this is not a speedup pair <index>: load moved <before> -> <after> DURING the measurement (tolerance 2.00); the halves were not timed under the same conditions, so the ratio is not a speedup campaign load spanned <low>..<high> across its legs (tolerance 2.00); the p95s may come from different repeats, so they were not timed under one load and the ratio is not a speedup
Re-run qualification while the machine is inside those bounds. A receipt that fails them stays on disk and is not priced.
Fewer than 3 measured sync-ups
A working directory with fewer than 3 measured sync-ups is refused. The refusal names the command that records them. Fewer than 3 still refuses: the wording changed, the verdict did not.
sync cost unverified for this cwd: n=<count><3 measured sync-ups — refusing to guess. The qualification campaign is NOT gated by this check and records the sync-ups it pays, so it is the bootstrap: run `tether-fabric-qualify <host> --class <class> --cwd <cwd>` measured sync cost alone (<high>s) exceeds the qualification's win margin (<margin>s) — refusing a stale margin
tether test --auto stages a selection at a stable directory under ~/.tether/test-fabric-stage/, named from the repo, the node ids, and the extra roots. The same selection keeps the same directory, so the sync count can pass 3. A fresh temporary directory makes every run look like the first.
Run the same selection again. Do not point the stage at a new temporary directory. If you set TETHER_TEST_FABRIC_STAGE, keep that path stable too. The directory is rebuilt in place. The marker file is .tether-test-fabric-stage.
The 6.0 second stage, on tether test --peers
Sending a test stage is priced at 6.0 seconds until a donor's own runs replace that prior. The line that names the prior says prior 6s. These lines are printed by tether test --peers, prefixed tether test --peers:. tether test --auto does not print this prior. A plan that does not beat running here, or that is not at least 10 percent faster, stays local.
tether test --peers: <peer>: ratio <r>x for this selection (qualified <q>x on <covered>/<n> of its files; others <u>x); fixed cost prior 6s (no measured runs) tether test --peers: planner kept everything local: remote placement would not beat local (predicted <p>s >= local-only <l>s) -- all <n> kept local; running locally tether test --peers: predicted <p>s vs local-only <l>s is only <g> faster; a peers run needs 10% faster to be worth its fixed cost; running locally fixed dispatch cost exceeds its benefit (given zero units)
On a busy Mac, splitting beat a local run at 16 units and lost at 12. That is a lower bound, because the load slows only the local side. With the clean peer terms the planner uses, 32 units stay local and 33 is the first it sends out. The prior was raised from 4.0 seconds. The measured pushes of a 24.71 MB stage were 4.70, 5.70 and 5.84 seconds.
Give it a larger selection. A job under that line stays local because sending it would be slower, not because a peer is broken.
The second sync sends nothing
The second sync of an unchanged pytest shard sends nothing when the stage path is stable and the remote directory was left in place. The planner calls that mode skip. There is no separate warm line to look for.
Keep the default stage path. If a cold send ate the win, the peer line says the measured sync cost exceeds the qualification's win margin. Do not delete the remote stage between samples of one campaign.
The quiet ceiling is a different bound
tether test also refuses to split at all when the 1-minute load is above the core count times 0.25. That check runs before any peer is considered. It is a different bound from the 150 used to price a receipt. The line is prefixed tether test:, and it names --allow-loaded. One run of that line is in Getting started. The load number there is not universal.
The printed tail is falling back to serial (pass --allow-loaded to split anyway).
doctor
Readiness only. doctor never dispatches a
command. It prints the build identity line, then the transparent-activation
checks.
tether doctor tether doctor --json tether doctor --json --extended
Anything else after doctor exits
2. --json emits one
tether.doctor.v1 document and exits
0 only when ready is
true, otherwise 1.
--json --extended adds the
tether.doctor.v2 fields (install mode,
in-process fabric-controller round trip, update-feed key pin) without
changing a v1 byte.
The 11 stable check ids
Ids come from DOCTOR_CHECK_IDS in
src/tether_shim.py. Labels and order match the
text output exactly, so a script can pin an id instead of scraping prose.
tether_executablegenerated_shimsmanaged_shell_blockshell_pathcommand_interceptionpool_configtransparent_pool_gateadmission_evidenceadapter_implementationsadapter_evidence_previewmeasured_admissions
MEASURED in this worktree: passed 2,
total 11,
verdict NOT_READY,
ready false. Your machine will differ. The page
does not probe your fabric.
fabric
The controller side, same argv as the
tether-fabric console script. Its parser has
ten commands:
status,
diff,
pair,
ingest,
revoke,
diagnostics,
classes,
regress,
replay,
cost-preview.
tether-fabric pair create PEER_ID --display-name NAME --json tether-fabric pair create PEER_ID --display-name NAME --json --invite tether-fabric pair import PEER_ID --invite CODE tether-fabric pair import PEER_ID --key-file PATH --display-name NAME tether-fabric ingest PATH tether-fabric revoke PEER_ID WORKLOAD_CLASS --reason TEXT
--display-name-
Required on
pair create. Onpair importit is required with--key-fileand defaults to the invite's own name with--invite. --invite-
On
createit is a switch that also prints a one-line expiring invite. Onimportit takes aCODE, and-reads one line from stdin. It is mutually exclusive with--key-file, and one of the two is required. --invite-ttlSECONDS- Invite lifetime, bounded by the module's min and max.
--show-secret-once- On
pair create, reveal the secret a single time. --reasonTEXT- Required on
revoke. A revocation with no stated reason is refused.
Invite lines begin with tether-invite-v1. and
never belong in a URL or query string. A second import into the same
store is refused as already used. The
pairing steps page carries the app's own
three-step copy.
run
image-batch
run lives in
tether_cli.main only; it is not a
tether-fabric parser command. There is one
class, image-batch, and three ways to run it.
tether fabric run image-batch FOLDER --json tether fabric run image-batch --local /abs/FOLDER --output-dir /abs/OUT tether fabric run image-batch --remote FOLDER --peer PEER
- Plan walks the folder for real and reports every timing it has not measured as an explicit unknown. It is not a run.
-
--local needs absolute paths and publishes the
result plus a sidecar to
--output-dir. -
--remote makes one receipt-gated peer attempt with no
local fallback. It cannot be combined with
--local. -
With no folder,
TETHER_IMAGE_BATCH_FIXTUREpaints one fixture document instead.
Refusals are exit 2:
- no class named:
tether fabric run needs a class (image-batch) - any other class:
unknown class -
no folder and no fixture:
live folder run is not wired; set TETHER_IMAGE_BATCH_FIXTURE to a fixture document -
--localwithout both paths:requires FOLDER --output-dir DIR - unreadable fixture:
unreadable fixture
fabric-node
The peer side, same argv as the
tether-fabric-node console script. It emits
signed receipts and is never an arbitrary executor. Its parser has six
subcommands.
identitycapacityexecute-terminallookup-terminallookup-artifactstatus
update
Reads the signed update feed with the same pinned key as the Status app.
The default action is check, which never
downloads and never installs.
tether update check --json
apply- Linux and Windows archive installs only. Downloads the signed archive for the platform, verifies size, sha256 and manifest, then swaps it into place with a rollback path.
rollback-
Restore the unique verified
.bak-tree beside this install. --dry-run- Verify and print what would happen. Downloads nothing, changes nothing.
--keep-backup- Keep the previous install instead of deleting it after a successful apply.
--scratch-dirDIR- Unpack elsewhere, including another volume. Across devices it becomes copy, then verify, then rename on the install volume.
--feed-urlURL- Override the canonical feed. Defaults to the pinned constant in the module.
Exit codes are named constants:
EXIT_UP_TO_DATE = 0,
EXIT_REFUSED = 2,
EXIT_UPDATE_AVAILABLE = 10. Exit
10 is not a failure — it means an update exists.
No signed stable.json envelope has been published
yet, so a check against the public feed host has nothing to verify. See
get the bits for the measured
snapshot.
support
Writes a redacted, fail-closed diagnostics bundle as a local file. It is never uploaded anywhere.
usage: tether support [--out PATH] [--json]
The controller's own
tether-fabric diagnostics reports only counts and
permission audits for the key directories — never file names, sizes, or
bytes.
Contact Tether support
This static site cannot send mail or confirm delivery. The link opens your mail app; nothing has been sent by this page. Send the draft there, or use the Mac CLI below.
Send through Tether on your Mac
Import your workspace mail kit first. The CLI reads your message from standard input and sends only to Tether support through Ember Mail. It validates input and limits requests to one/minute and five/hour. Accepted means SMTP 250, not confirmed inbox delivery. Do not include secrets or attach diagnostics without reviewing them.
printf '%s' 'Describe the problem (no secrets)' | tether mail support --reply-to you@example.com --subject 'Help with Tether'
Optional CLI notices
Enable with tether mail notices enable --to you@example.com; disable with tether mail notices disable. Job notices contain only exit status. Peer notices report a failed live status probe, not a confirmed outage. External-domain delivery has not been verified.
analyze
VERIFIED against the built binary:
analyze is
dispatched by tether_cli.main, which hands
args[1:] to
tether_analyze_cli.
Both invocations below are real; the subcommand is the one to use.
Nothing leaves the machine — the analyzer reads local bytes only.
tether analyze PATH \ [--json] [--reveal-private-metadata] \ [--understand] [--caption] \ [--root DIR] [--max-bytes N] [--max-files N] \ [--auto | --local-only] # equivalent, module form (no install required) PYTHONPATH=src python3 -m tether_analyze_cli PATH
Exactly one PATH is accepted. A second positional
path is refused. A file gets one
tether.file-analysis.v1 document; a directory gets
one tether.file-analysis-batch.v1 document.
--rootDIR-
The allowed root. Defaults to the process cwd. A relative
PATHresolves under it, and anything resolving outside is refused withpath is outside allowed root. A symlink that escapes is refused withsymlink escapes allowed root. --max-bytesN-
Positive integer size cap; the module default is
DEFAULT_MAX_BYTES = 512 * 1024 * 1024. A non-integer value is refused. Over-cap files are refused withfile exceeds N-byte cap. --max-filesN-
Directory batches only; the default is
DEFAULT_MAX_BATCH_FILES = 20000. Passing it with a filePATHexits2with--max-files only applies to a directory PATH. --json- Write the canonical JSON document to stdout with no trailing newline and no human table.
--understand- Add on-device image facts to image files (Vision and pixel measurements). Off unless you pass it; nothing is uploaded.
--caption-
Also request the optional local caption backend. It implies
--understand. -
--reveal-private-metadata - Opt in to metadata the analyser withholds by default. Off unless you pass it.
--auto- Ask the Fabric whether any workload class owns analysis. None does, so this reports that and runs here. It is never a silent no-op.
--local-only- Never consult the Fabric. Analysis is local anyway; this is the escape that skips the question.
Document fields
A single-file document carries
schema, path,
kind, size_bytes,
sha256, extension,
facts and
secret_findings. A batch document carries
schema, root,
counts (analyzed, refused, total),
files, errors and
duplicates.
extension compares the claimed suffix against
what the content implies, so
matches_content: false is a real answer, not an
error. Secret findings report kind, offset, and a redacted marker — never
the secret.
Shim adapters
An adapter name in the tree is not shipped coverage.
IMPLEMENTED_SHIM_ADAPTERS in
src/tether_adapter.py holds ten names, and the
adapter_implementations doctor check still counts
against them.
blendercargoclang++ffmpegg++gohandbrakeclinvccrustcx265
Dynamic adapters
IMPLEMENTED_DYNAMIC_ADAPTERS is a separate,
deliberately smaller set:
cmake,
javac,
make,
ninja. The source
comment is explicit that these are never added to the shim set by fiat —
the doctor count stays bound to the measured set.
The ffmpeg and
x265 adapters pin an exact argv shape by index,
cap output at 500 MB, and accept only the presets
medium, slow,
slower, veryslow.
Anything outside the contract is refused rather than reshaped.
Command index
Generated from tether --help by
tools/gen_docs_cli.py, so it cannot drift. A test
fails when this list and the real help disagree.
tether <command...>
run <command> on the best available borrowed machine (else local)
tether --auto <cmd>
DECIDE per policy (heavy+batch → offload, interactive/trivial → local exec)
tether --auto --action-manifest FILE -- <cmd...>
execute one explicit declared ActionSpec (no cwd discovery)
tether --auto --shim-adapter CMD -- <args...>
internal transparent-shim declaration boundary
tether --local <cmd>
force local (`--local-only` is the same escape)
tether --status
show the borrow pool (which machines are reachable + free cores)
tether --version
show semantic base + commit/dirty/install provenance; never dispatch
tether doctor
show build identity + transparent-activation readiness; never dispatch --json = tether.doctor.v1 · --json --extended = tether.doctor.v2 (install mode, fabric-controller round-trip, update-feed key pin; v1 stays byte-identical)
tether analyze PATH
inspect one local file or directory; --json emits receipt-backed facts
tether serve
start the loopback upload/analysis bridge for the status dashboard's drop surface; --host/--port (default 127.0.0.1, OS-assigned port)
tether update [--json]
check the signed update feed (check-only; exit 10 = update available)
tether support
write a redacted, fail-closed diagnostics bundle to a local zip; --out PATH · --json. Local file only; never sent anywhere
tether receipt timeline --root DIR [--json]
read-only sorted execution receipt/summary history
tether receipt redact-preview (--root DIR [--generation NAME] | --file PATH) [--json]
show exactly what a receipt looks like after the support-bundle redaction, before you share it; exit 1 if any receipt is refused
tether action routes [QUERY...] [--json] | show ROUTE [--json] | admission [--json]
search the declared ActionSpec routes (inputs, outputs, limits, refused environment) joined to the backend module census; read-only, never dispatches
tether backend-only-reachability [--json]
report backend modules and capabilities with no user surface
tether reachability [--json]
short alias for backend-only-reachability
tether tool-entrypoint-coverage [--json]
report tests mapped to each tools/*.py entrypoint
tether tool-coverage [--json]
short alias for tool-entrypoint-coverage
tether speedup [--runtime-dir DIR] [--config PATH] [--json]
did borrowing actually make this machine faster? read-only; per command and host, with the round trip counted; never guesses. Text mode shows verdicts only; --all includes local-only commands
tether lint [PATH ...] [--root DIR] [--json] [--local]
pyflakes over every .py file, one file per unit, split across qualified peers when (and only when) a receipt-backed plan beats local; every peer answer is verified and the merged findings equal a local-only run. Exit 0 clean, 1 findings, 2 refused
tether sweep --run 'CMD {}' --root DIR --peer HOST
run a verdict-returning command per file on this machine AND a donor at once (shared queue, sha256 verdict check, local fallback); --compare N for paired local-only vs borrowed runs
tether boost [TASK]
remote-testgen candidate selection; --n/--selector/--json (see `tether boost --help` for the full flag set)
tether archive list | verify [NAME] | restore NAME --to DIR
read back what Tether moved to the NAS (MOVES.tsv); verify re-hashes each archive against the ledger, restore re-verifies the recorded sha256 and refuses on mismatch. Fails closed when the NAS is not mounted — never a local fallback
tether model serve GGUF [--donor HOST=FREE_BYTES ...] [--experts] [--dry-run]
plan one GGUF across donor memory. --experts places MoE expert tensors on donors by --override-tensor and keeps dense weights local; --dry-run prints the plan and starts nothing
tether serve-overflow --socket PATH | --port N [--peer PEER ...]
share one meeting server's transcript windows with the least-loaded paired peer, and only once this host's own queue or latency crosses the live-caption threshold. Listens on a localhost unix socket or on 127.0.0.1 only. That socket has no authentication beyond the bind. A peer is reached over the existing ssh channel. A unit whose peer does not qualify, or whose peer does not answer, runs locally.
tether fabric …
Fabric CONTROLLER side (this main): pair / invite / status --json; same argv as tether-fabric; computes nothing the receipts do not authorize
tether pair <ssh-host>
guided, fail-closed peer onboarding (use --dry-run for a no-touch plan)
tether fabric run image-batch [FOLDER]
paint one opt-in TETHER_IMAGE_BATCH_FIXTURE document (TUI or --json passthrough). Use --local FOLDER --output-dir DIR for an explicit local exact run; use --remote FOLDER [--peer PEER] for one receipt-gated peer attempt (no local fallback).
tether fabric replay PEER CLASS [--at EPOCH] [--json]
re-derive one stored qualification verdict from receipts; no dispatch
tether policy show
the placement policy: each rule's id, effect, class, donor, donor kind, region, hours and remote-node cap (tether.placement-policy.v1), plus the last refusal. Read-only.
tether policy check CLASS DONOR
ask the policy about one placement; prints the rule that refused it and exits 1. Deny overrides allow; an unknown class is refused by default.
tether donor-stats
the live borrow: per donor, cores, memory, OS, units, rate, share, speedup and efficiency (tether.donor-stats.v1). Read-only.
tether overflow-status
what the meeting-overflow endpoint did: whether it is listening, how many units stayed local and how many a donor took, which donor, and the queue/latency gate on the last unit (tether.overflow-status.v1). Read-only, no flag.
tether job-card
the last finished job, measured: wall here vs borrowed, speedup, per-donor efficiency, hedged units (tether.job-card.v1). Read-only.
tether mcp
MCP server on stdio for agent sessions (Claude, Codex, Cursor): tether_capacity + tether_job_card are read-only, tether_run dispatches through this same run path and is off unless TETHER_MCP_ALLOW_DISPATCH=1.
tether sdk --objective fastest|lowest_cost|lowest_energy [--trusted-devices-only]
place one command with an explicit objective; prints the result and the per-job card (tether.sdk-run.v1). Same call as `tether.run(...)` in Python.
tether regret [--last N] [--json]
scheduler regret over the last N finished jobs: the plan's own prediction beside the measured wall, the best placement hindsight proves, and the running average regret + prediction error (tether.sched-regret.v1; "not measured" under 3 jobs). Read-only.
tether history [--json] [--limit N]
the last N finished jobs (tether.job-history.v1): class, wall, measured speedup, donors and their units, cost and Wh, outcome; plus this week's time saved and the share of units on donors. An unmeasured value is null, never 0. Read-only.
tether pool [--json]
the one hardware pool: local, connected, reserved and usable-now CPU/GPU/RAM from the same receipts that authorize execution; per-peer reason + fix command when a peer contributes 0. --json = tether.pool.v1; read-only
tether warm ready [--host HOST] [--json]
report each warm-route prerequisite as met/unmet with the reason; read-only, exit 0 only when every one is met
tether fabric-node …
Fabric PEER side (Linux/Windows nodes): signed identity/capacity/execute-terminal receipts; same argv as tether-fabric-node; never an arbitrary executor
tether --here <cmd>
run in the current dir (syncs cwd to the remote); default syncs cwd too
Archive
tether archive list, verify and restore read back what Tether moved to the NAS. Use them when you need a tree back. They fail closed: a missing NAS is an error, and they never read a copy on this disk instead.
Use list to see the ledger. Use verify to re-hash the archives on the NAS against the ledger before you rely on them or free local disk. Use restore when you want one of those archives extracted, after the recorded checksum matches.
tether archive list
list every archive recorded in MOVES.tsv on the NAS
tether archive verify [NAME ...]
re-hash every ledgered archive and report ok / tampered / missing
tether archive restore NAME --to DIR
re-verify the recorded sha256, then extract NAME into DIR
NAME is the archive basename (hostspeedup.tar), its source path, or its full
archive path. Exit 0 ok, 2 the NAS is not usable, 3 the archive does not match
its ledger or the request is ambiguous, 4 verify found an archive that is not
ok. Nothing is ever read from a local fallback: a missing NAS is an error, not
a cue to look on this disk.
list prints one tab-separated row per ledger entry: name, size in bytes, sha256, the source path, then any extra fields. A missing NAS exits 2 and names the mount. The unusable line starts NAS not mounted at. Source paths are not copied onto this page.
Exit 2 means the NAS is not usable. Exit 3 means the bytes do not match the ledger, the name is ambiguous, or the destination already exists. Nothing is extracted in that case. A byte mismatch prints REFUSE and the sha256 the ledger recorded. A destination that already exists is refused rather than overwritten. verify exits 4 when any archive is tampered or missing, and it prints that state per archive instead of stopping at the first bad one.
Troubleshooting
Each message below is quoted from src/. Match
the text you see, then follow the fix.
path is outside allowed root-
analyzeconfines itself to--root, which defaults to the current directory. Run it from the right directory or pass--root DIR. A symlink that leaves the root is refused assymlink escapes allowed root. file exceeds N-byte cap- The file is larger than
--max-bytes. Raise the cap on purpose or analyze a smaller file. file is empty- There is nothing to analyze. An empty file is refused, not reported as clean.
--max-files only applies to a directory PATH- Drop
--max-fileswhen the path is a single file. Exit2. live folder run is not wired; set TETHER_IMAGE_BATCH_FIXTURE to a fixture document-
You ran
fabric run image-batchwith no folder. Pass a folder to plan it, or--local/--remote. donor refused: …-
The peer rejected the session during
sweep. The work finishes on this machine. Read the reason after the colon, then check the peer withtether --status. NOT_READYfromdoctor-
At least one of the 11 checks failed. Run
tether doctor --jsonand read each check's reason. This is the expected state before any peer is paired.
Still stuck? tether support --out PATH writes a
redacted bundle as a local file. Nothing is uploaded. Read it before you
share it.
Exit codes
Refusal and misuse are different answers and get different codes. The analyze codes below were MEASURED by running the module in a worktree.
0-
Success. For
updateit isEXIT_UP_TO_DATE; fordoctor --jsonit requiresready: true. 1-
Refusal on a valid request:
analyzeraisingAnalysisRefusal(outside root, escaping symlink, empty, unreadable, over cap, changed mid-read), ordoctor --jsonreporting not ready. 2-
Usage error or a declared-path refusal: unknown option, missing path,
two paths,
--max-fileson a file, extra argv afterdoctoror--version,--autowith no command, anyfabric runrefusal, andEXIT_REFUSEDforupdate. 10-
updateonly:EXIT_UPDATE_AVAILABLE. An update exists. Not an error.
A bare tether <command> returns the
borrowed command's own exit status, and prints a
[tether: ran on HOST in N.Ns] line to stderr —
with (fell back to local: …) appended when the
remote path failed.