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.

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 doctor reports tether.doctor.v1 with 11 stable check ids and verdict NOT_READY on this machine.
  • tether fabric run image-batch plans 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 analyze is not a subcommand of the tether entry 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.

From a checkout
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.

Run from a checkout
git clone <your copy of the repository> tether && cd tether
PYTHONPATH=src python3 bin/tether --version
Build and install a local macOS archive
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.

pair --dry-run, measured
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 totals, one read-only run
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.

test --auto, one loaded run
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, exits 2.
--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 ALIVE with cores, load and free score, or DOWN with a reason. On a TTY it renders a table; piped it stays key=value lines.
--action-manifest FILE
With --auto, execute exactly one declared ActionSpec and skip cwd discovery. A lost ActionSpec or typed receipt identity is refused, not guessed.
--shim-adapter CMD
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.

  1. Route. tether --auto decides per policy in src/tether_route.py. Interactive and trivial commands exec the real binary locally.
  2. Sync. The working directory is copied to an isolated directory on the peer, the command runs there, and results sync back. Under TETHER_ACTIONSPEC=1 only declared inputs cross the wire.
  3. Verify. tether sweep checks a sha256 verdict per unit. If the peer disagrees on any unit, or is unreachable or slow, that work finishes here.
  4. 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.
Split one verdict-returning command per file
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.

Local versus peer seconds, two bench campaigns
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.

pool preconditions
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
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
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
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
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.

Accepted argv
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_executable
  • generated_shims
  • managed_shell_block
  • shell_path
  • command_interception
  • pool_config
  • transparent_pool_gate
  • admission_evidence
  • adapter_implementations
  • adapter_evidence_preview
  • measured_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.

Pair grammar
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. On pair import it is required with --key-file and defaults to the invite's own name with --invite.
--invite
On create it is a switch that also prints a one-line expiring invite. On import it takes a CODE, and - reads one line from stdin. It is mutually exclusive with --key-file, and one of the two is required.
--invite-ttl SECONDS
Invite lifetime, bounded by the module's min and max.
--show-secret-once
On pair create, reveal the secret a single time.
--reason TEXT
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.

Plan, run local, or try a peer
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_FIXTURE paints 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
  • --local without 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.

  • identity
  • capacity
  • execute-terminal
  • lookup-terminal
  • lookup-artifact
  • status

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.

Check the feed
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-dir DIR
Unpack elsewhere, including another volume. Across devices it becomes copy, then verify, then rename on the install volume.
--feed-url URL
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 string from source
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.

Open an email draft

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.

Send through Tether on your Mac
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.

Working invocation
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.

--root DIR
The allowed root. Defaults to the process cwd. A relative PATH resolves under it, and anything resolving outside is refused with path is outside allowed root. A symlink that escapes is refused with symlink escapes allowed root.
--max-bytes N
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 with file exceeds N-byte cap.
--max-files N
Directory batches only; the default is DEFAULT_MAX_BATCH_FILES = 20000. Passing it with a file PATH exits 2 with --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.

  • blender
  • cargo
  • clang++
  • ffmpeg
  • g++
  • go
  • handbrakecli
  • nvcc
  • rustc
  • x265

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.

Mode flag
tether <command...>

run <command> on the best available borrowed machine (else local)

Mode flag
tether --auto <cmd>

DECIDE per policy (heavy+batch → offload, interactive/trivial → local exec)

Mode flag
tether --auto --action-manifest FILE -- <cmd...>

execute one explicit declared ActionSpec (no cwd discovery)

Mode flag
tether --auto --shim-adapter CMD -- <args...>

internal transparent-shim declaration boundary

Mode flag
tether --local <cmd>

force local (`--local-only` is the same escape)

Mode flag
tether --status

show the borrow pool (which machines are reachable + free cores)

Mode flag
tether --version

show semantic base + commit/dirty/install provenance; never dispatch

Command
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)

Command
tether analyze PATH

inspect one local file or directory; --json emits receipt-backed facts

Command
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)

Command
tether update [--json]

check the signed update feed (check-only; exit 10 = update available)

Command
tether support

write a redacted, fail-closed diagnostics bundle to a local zip; --out PATH · --json. Local file only; never sent anywhere

Command
tether receipt timeline --root DIR [--json]

read-only sorted execution receipt/summary history

Command
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

Command
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

Command
tether backend-only-reachability [--json]

report backend modules and capabilities with no user surface

Command
tether reachability [--json]

short alias for backend-only-reachability

Command
tether tool-entrypoint-coverage [--json]

report tests mapped to each tools/*.py entrypoint

Command
tether tool-coverage [--json]

short alias for tool-entrypoint-coverage

Command
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

Command
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

Command
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

Command
tether boost [TASK]

remote-testgen candidate selection; --n/--selector/--json (see `tether boost --help` for the full flag set)

Command
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

Command
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

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

Command
tether fabric …

Fabric CONTROLLER side (this main): pair / invite / status --json; same argv as tether-fabric; computes nothing the receipts do not authorize

Command
tether pair <ssh-host>

guided, fail-closed peer onboarding (use --dry-run for a no-touch plan)

Command
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).

Command
tether fabric replay PEER CLASS [--at EPOCH] [--json]

re-derive one stored qualification verdict from receipts; no dispatch

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

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

Command
tether donor-stats

the live borrow: per donor, cores, memory, OS, units, rate, share, speedup and efficiency (tether.donor-stats.v1). Read-only.

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

Command
tether job-card

the last finished job, measured: wall here vs borrowed, speedup, per-donor efficiency, hedged units (tether.job-card.v1). Read-only.

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

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

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

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

Command
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

Command
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

Command
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

Mode flag
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.

archive help
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
analyze confines 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 as symlink 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-files when the path is a single file. Exit 2.
live folder run is not wired; set TETHER_IMAGE_BATCH_FIXTURE to a fixture document
You ran fabric run image-batch with 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 with tether --status.
NOT_READY from doctor
At least one of the 11 checks failed. Run tether doctor --json and 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 update it is EXIT_UP_TO_DATE; for doctor --json it requires ready: true.
1
Refusal on a valid request: analyze raising AnalysisRefusal (outside root, escaping symlink, empty, unreadable, over cap, changed mid-read), or doctor --json reporting not ready.
2
Usage error or a declared-path refusal: unknown option, missing path, two paths, --max-files on a file, extra argv after doctor or --version, --auto with no command, any fabric run refusal, and EXIT_REFUSED for update.
10
update only: 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.