ttr balancer places a requested run on one or more registered machines. Each
run uses a fresh checkout of an exact commit and executes inside that machine’s
own ttr resources run queue. The result goes back to the request that asked
for it. The balancer adds no second queue: each host’s canonical resource
queue stays the final admission for every project and consumer on that machine.
Request a run
--shatakes a full commit SHA ormain, which resolves originmain. The commit must be on an origin branch. Dirty-source sync is not enabled yet.--replicas Nruns the same request onNdistinct hosts.--priority 0-9places higher priority first when capacity is scarce.--host/--exclude-hostconstrain placement.--request-idis the idempotency key: repeating it resumes the same request, and reusing it with different parameters is refused. A retry after an interrupted submit only adds missing runs; it never re-queues a run that was already placed.--cwdmust resolve inside the exact checkout. A path that leaves it through a symlink component fails setup.--detachplaces once and returns.ttr balancer reconcile(no argument) places queued runs and settles active ones later.
status [requestId] [--json], probe, cancel <run|request>,
log <runId>, consume <runId> --thread <id> (records one consumption per
thread and result) and publish [requestId].
Register hosts
Hosts live in~/.tuturuuu/balancer/hosts.json on the requesting machine. It
must be a regular 0600 file owned by you. Each entry names a transport
(local, or ssh with an SSH config alias), the verified hostname,
platform and arch, disk floors (minDiskGiB, minDiskFreePercent,
default 10), an optional maxLoadPerCpu (default 1), capacity, reserved
headroom, per-class weights and an optional preference (lower first). An
optional hold keeps a host out of placement.
Placement rules
- A host is eligible only with fresh (under 60 s), complete telemetry from a
probe of that host: identity, load, memory pressure, free disk (GiB and
percent) and its
ttr resourcesqueue. Missing or stale telemetry is UNKNOWN and never receives work. - Lanes are the slot count. Every owner and waiter in the host queue occupies them, whatever project or tool started it. Dispatched runs that the host has not reported yet stay reserved.
- Placement reuses the
--hostsplanner (planAdmissions): preference first, then remaining CPU. Account or provider capacity is never a host slot. - When no host qualifies, the run stays queued with its closest blockers.
Execution and identities
The controller ships a content-addressed agent bundle (Node built-ins only)
to the host and runs it with the host’s own bun. The host creates
~/.tuturuuu/balancer/runs/<runId> exclusively, so a second admission of the
same run is refused. A supervisor (a systemd-run --user unit on Linux, a
detached process elsewhere) runs the whole job inside one
ttr resources run admission:
- Fetch the commit into a per-host partial mirror and add a detached worktree.
- Resolve Node.js: the
.nvmrcpin, else the newest installed version that meetsengines.node. The choice is recorded intoolchain.json. Toolchain installation is not automated yet. - Run
bun install --frozen-lockfileand then the command.
result.json with the lease, exit code, phase and log digest,
then removes the checkout. Host admission refusals (for example, low disk)
record rejected, setup errors record setup_failed, and timeouts record
failed.
Unknown outcomes and cancellation
An uncertain dispatch or three failed status reads mark a rununknown. An
UNKNOWN run is never rescheduled. reconcile settles it from the host record
once the host answers, and only a result carrying the run’s lease and fence is
accepted. cancel stops a queued run locally and signals a placed run’s host
supervisor, which stops the queue admission and its command.
Host registry and log readers open with O_NOFOLLOW, validate the opened
descriptor, and read through that same descriptor. Log appends also validate
and retain their descriptor. A pathname replacement cannot redirect an open
operation; symlinked or non-private log files are refused.
Host JSON records use an exclusive, private temporary file in the destination
directory, sync its complete contents, and rename it into place. Cancellation
notices publish the complete temporary file with an exclusive link instead,
retaining the first notice without a check-then-write race. Regression coverage:
packages/sdk/src/cli/balancer-host-files.test.ts.
Controller ledger locking
The publishedttr balancer launcher selects Node.js for controller execution.
Node.js must be available on PATH. Direct Bun calls to ledger mutation fail
before store creation because Bun 1.4.4 lacks the native binding’s libuv error
and thread APIs. Host agents continue to use their host’s Bun runtime.
Controllers serialize submit and revision updates with a descriptor-owned OS
lock (fs-native-extensions 1.5.1: Linux OFD locks, macOS BSD flock). The SDK
ships the dependency’s prebuilt binaries; an unavailable native binding or
unsupported locking syscall fails before mutation. Busy locks use bounded
asynchronous retries. Closing the descriptor, action failure and process death
release its lock. No PID or elapsed-time heuristic recovers a lock.
A fresh store creates ledger/.lock/protocol.json last, recording the inode
of its mutex and store directories. Device numbers can change at reboot, so
historical device fields are ignored when reading existing manifests. Each
acquisition compares live entry device numbers with the current root device;
the open mutex descriptor and pathname must share that live device.
Ordinary acquisition opens
ledger/.lock/mutex without O_CREAT. After acquiring the lock, it verifies
the open descriptor, current pathname and recorded identity before allowing
mutation. A missing or replaced mutex is refused, including while another
controller still holds the original inode; it is never silently recreated.
The new protocol also permanently fences both predecessors. The .lock
directory has no PID, so PID-lock controllers cannot acquire or reclaim it.
A directory at ledger/mutation-lock.sqlite prevents earlier SQLite
controllers from opening their database. Both fences are created before
checking that no legacy run/request directories exist. A controller already
inside a predecessor mutation therefore prevents new-store admission, and a
predecessor started later cannot enter the fresh store.
Existing unmarked ledgers, interrupted initialization, changed store identities
and insecure or symlinked store directories fail closed. Stop old controllers
and use a fresh TTR_BALANCER_HOME; preserve the old store for reconciliation.
Do not replay old request/run IDs from a fresh home until their outcomes have
been reconciled.
There is no automatic state migration, reset or repair. Concurrent callers
that observe incomplete first initialization must retry after its creator
finishes. Never remove or replace a protocol fence or mutex while controllers
may run.
Deleting and recreating the entire store also removes its identity record and
protocol fences; the controller cannot detect that loss as a replaced store.
Whole-store removal or restore requires stopping all controllers and reconciling
in-flight outcomes first. Do not run whole-store cleanup while any controller
may still be active.
Filesystem admission allows APFS and HFS on macOS; ext2/3/4, XFS, Btrfs and
tmpfs on Linux. Unknown types, NFS, SMB, FUSE and cluster filesystems are
refused. macOS type IDs are mount-assigned; an unrecognized APFS/HFS numeric
assignment is also refused until name-based admission is available.
Store directories and the mutex must remain private, owned by the
controller user and on the same device. This does not qualify shared block
storage mounted concurrently by independent kernels; such storage is outside
the supported local-store contract.
An exclusive request-intent link that loses reads and validates the winning
marker. Conflicting parameters fail without creating runs; a matching intent
retains its original source identity and creation time, and a committed marker
returns the existing runs. Regression coverage lives in
packages/sdk/src/cli/balancer-stale-lock.test.ts (the concurrent stale-recovery
dispatch race and process-death recovery) and balancer-ledger.test.ts (intent
link conflicts and partial-submit recovery). The mixed-protocol, unsupported
filesystem and missing/replaced-inode regressions live in
packages/sdk/src/cli/balancer-ledger-admission.test.ts.
Reboot device renumbering and live cross-device rejection are covered in
packages/sdk/src/cli/balancer-ledger-reboot.test.ts.
Infrastructure view
Controllers publish each changed run (best effort) toPOST /api/v1/infrastructure/balancer/runs. Infrastructure shows them at
/<wsId>/balancer. The database keeps only a newer revision from the same
publisher. Any root member may publish, but reading runs (the page and
GET) requires view_infrastructure. Published records include the command
argv, so never pass secrets as arguments.