Add ty-on: a placement resolver extension that decides which host a task runs on - #698
Merged
Merged
Conversation
ty is gaining the ability to run a task on another machine. The policy for that — which hosts exist, what they are provisioned for, which one to pick — is fleet-specific and must not live in core, so it ships as a standalone extension with its own module. Nothing outside extensions/ty-on is touched. The binary reads one JSON placement request on stdin and writes one response on stdout. An empty target means "run locally" and is the answer to every question it cannot confidently answer: unknown project, missing or malformed inventory, no reachable host, malformed request, `on` not installed. It never fails a task and never guesses a host — it exits 0 in all cases, and the required reason field explains the choice to the user. Placement reads the same inventory as the `on` CLI (ON_HOSTS, else XDG_CONFIG_HOME, else ~/.config/on/hosts.yaml): no host serving the project means local, one means that host, several are ranked by free memory. Ranking shells out to `on ls` rather than reimplementing the probe, treating `on` as an optional dependency. Only the multi-host rule probes at all, so the common paths answer from a single file read; the probe is bounded by TY_ON_TIMEOUT (3s) because a late answer is worth less than a local one in the spawn path. Tests are table-driven over the resolution rules with the inventory in a temp file via ON_HOSTS, and inject a fake prober so the suite needs no fleet and no `on` on PATH. The `on ls` table parser is pinned against real output. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
extensions/ty-on/, a standalone Go module that decides which machine a task should run on. It touches nothing ininternal/orcmd/— core invokes it, it answers.The policy for remote execution (which hosts exist, what they are provisioned for, which one to pick) is fleet-specific and must not live in core. A normal ty user never encounters any of it.
The contract
One JSON request on stdin, one JSON response on stdout, once per task, before the executor spawns.
An empty
targetmeans "run locally", and is the answer to everything the resolver cannot confidently answer — unknown project, missing or malformed inventory, no reachable host, malformed request,onnot installed. It never fails a task and never guesses a host: it exits 0 in all cases, and the requiredreasonis written to explain a surprising placement without further digging.Placement rules
Reads the same inventory as
on:$ON_HOSTS, else$XDG_CONFIG_HOME/on/hosts.yaml, else~/.config/on/hosts.yaml.reposmap contains the task's project.oninstalled.on lsrather than a reimplemented probe. Unreachable hosts are dropped; ties break on host name so the answer is stable.onis an optional dependency — missing, failing, or slow all resolve to local with a reason.Speed
Rules 1–3 are a single file read. Rule 4 costs one
on lsbounded byTY_ON_TIMEOUT(default 3s), because a late answer is worth less than a local one in the spawn path. Measured against the real fleet: single-host and unknown-project placements return instantly; the multi-host probe took ~1.0s wall.Files
go.mod/go.sumgopkg.in/yaml.v3. No dependency on core.cmd/main.goTY_ON_TIMEOUT,--help/--version.internal/placement/placement.gointernal/placement/inventory.gooninventory loading and project→host matching.internal/placement/probe.goProberinterface, theon lsimplementation, and its table parser.README.mdTests
Table-driven over the resolution rules with the inventory in a temp file via
ON_HOSTS, covering unknown project, single host, multiple hosts, unreachable candidates, missing inventory, malformed inventory, malformed request, andonnot installed — every one producing a local placement with a useful reason rather than an error.Comparison is injected through a
Proberinterface, so the suite needs no fleet and noononPATH; the realOnProberkeeps shelling out toon ls, and its table parser is pinned against verbatim real output.Note that CI does not currently build or test
extensions/, so these were run locally.Out of scope
No core changes, no hook invocation (core calls this), and no rsync/worktrees/ssh — that is
on's job. Hostcapabilitiesare parsed but not used for filtering; matching an executor against them is the obvious next lever, noted in the README.🤖 Generated with Claude Code