Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@ name: release
#
# ## What has not been proved
#
# This file has never run. There is no published release of Fleet Runner, so
# neither installer has ever been pointed at a real one, and the container image
# has never been built at all -- docs/platforms.md has said so since the
# Dockerfile was written. Everything below is a first attempt with the reasoning
# written down, not a proven pipeline.
# It has published every release since v0.5.0. install.sh has been run against
# those archives on macOS (arm64 and Intel); install.ps1 never has. The amd64
# image is started by `container smoke` in fleet.yml on every pull request; the
# arm64 image is built here and has never been started.

on:
push:
Expand Down
46 changes: 45 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ a CI job fails when they disagree.

## [Unreleased]

## [0.8.0] — 2026-10-15

The fleet as something a program or an AI agent can drive: a versioned control
API, a CLI and MCP tools that are thin wrappers over it, an optional OAuth
gateway for chat tools, and a skill that tells an agent how to use them. See
[AI agents and automation](docs/agents.md).

### Added

- **A versioned control API, `/api/v1`.** Devices, workloads, jobs, results,
Expand Down Expand Up @@ -72,6 +79,42 @@ a CI job fails when they disagree.
- **The container image could not start on a fresh volume.** `VOLUME` named a
directory the image never created, so Docker made it root-owned and the
non-root `fleet` user died on `mkdir`. The image now creates it first.
- **The desktop app's build script failed before compiling anything.** The
bundled resource directory was named `fleet`, the same name the sidecar is
copied to in `target/debug`, so the copy failed with "Not a directory". The
resource is now `fleet-tree`, and the sidecar shim also looks beside itself,
where a dev build puts it.

### Now verified

Four items from earlier "Not verified" lists, each now checked by CI on every
pull request that touches them:

- **The container image runs.** `fleet / container smoke` builds
`fleet/Dockerfile`, starts it, and waits for its own agent to register and
answer `fleet devices`, `fleet workloads` and `fleet whoami`. It found the
volume bug above on its first run. linux/amd64 only; the arm64 image is still
built and not run.
- **`fleet service install` on systemd.** `fleet / systemd smoke` installs the
user unit on a real systemd, checks it with `systemd-analyze verify`, waits
for the collector it starts to answer, then uninstalls and checks nothing is
left answering. launchd and the Windows scheduled task are still unrun.
- **The desktop app's Rust compiles.** `desktop / cargo check` stages the
sidecar and runs `cargo check --all-targets` on macOS. It found the resource
bug above; once that was fixed the Rust compiled with no errors. Compiling is
all it proves: the app has still never been launched.
- **The Roku channel compiles.** `runner-roku / brighterscript` runs `bsc`
over the channel. It has still never run on a Roku.

### Not verified

- **The MCP gateway has not met a real identity provider.** Its tests use a
local fake that signs tokens the way Entra ID and Okta do; no tenant has an app
registration for it yet, and it is not on any company's approved-connector
list. [Authentication and the MCP gateway](docs/deploy/auth.md) has the setup.
- **The dashboard does not use the generated client yet.** It still calls the
unversioned routes with hand-written types
([#83](https://github.com/addisdev/fleet-runner/issues/83)).

## [0.7.2] — 2026-09-30

Expand Down Expand Up @@ -994,7 +1037,8 @@ The first public release, when the project was still four repositories.
that starts a throwaway collector on a spare port so it never touches a live
fleet's history.

[Unreleased]: https://github.com/addisdev/fleet-runner/compare/v0.7.2...HEAD
[Unreleased]: https://github.com/addisdev/fleet-runner/compare/v0.8.0...HEAD
[0.8.0]: https://github.com/addisdev/fleet-runner/compare/v0.7.2...v0.8.0
[0.7.2]: https://github.com/addisdev/fleet-runner/compare/v0.7.1...v0.7.2
[0.7.1]: https://github.com/addisdev/fleet-runner/compare/v0.7.0...v0.7.1
[0.7.0]: https://github.com/addisdev/fleet-runner/compare/v0.6.2...v0.7.0
Expand Down
24 changes: 24 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,30 @@ this is the first rule rather than a nice sentiment.
component suite is green **or skipped** — a change to one runner does not build
the others.

## Cutting a release

One version covers every component, so a release is one number everywhere.

1. On a branch from `develop`, run `node scripts/version.mjs set X.Y.Z`. It
writes `VERSION` and every component's own version. Then run
`npm --prefix collector run gen:api`, because `openapi.json` carries the
version and CI fails when it is stale. Android's `versionCode` is not
derived from it; bump that by hand if the Play Store build needs it.
2. In `CHANGELOG.md`, rename `## [Unreleased]` to `## [X.Y.Z] — YYYY-MM-DD`,
add a fresh empty `## [Unreleased]` above it, and update the compare links
at the bottom.
3. Merge that into `develop`, then open a `develop` → `main` pull request named
`Release X.Y.Z` and merge it once `ci-ok` passes.
4. Tag the merge commit on `main` and push the tag:
`git tag -a vX.Y.Z -m "vX.Y.Z" && git push origin vX.Y.Z`.

The tag runs `release.yml`. Its first job refuses to publish anything unless
the tag, `VERSION` and every component agree. Then it builds the six
platform archives and `SHASUMS256.txt`, pushes the container image, and creates
the GitHub Release, which `install.sh` and `install.ps1` read. A version with a
suffix (`0.9.0-rc.1`) is published as a pre-release and leaves `latest` alone.
Pushing to `main` also publishes the docs site.

## Dependencies

**Dependabot is deliberately limited to security alerts and security fixes.**
Expand Down
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,31 @@ curl -fsSL https://raw.githubusercontent.com/addisdev/fleet-runner/main/install.
> **[docs/install/](https://addisdev.github.io/fleet-runner/install/)** is honest
> per platform.

## Drive it from a script or an AI agent

```
fleetr run benchmark --pool phones && deploy
```

Devices, workloads, jobs, results, artifacts, schedules and alerts are one
versioned control API,
[`/api/v1`](https://addisdev.github.io/fleet-runner/api/#control-api-apiv1),
with an OpenAPI document and a generated client. Three wrappers sit on that
client and nothing else:

- **A CLI** (`fleetr`, the same program as `fleet`) whose exit code is the
verdict and whose `--json` is the API's answer, for scripts, CI and coding
agents.
- **An MCP server**, `fleet mcp`, for agents that prefer tools to shell
commands, and a remote one, `fleet mcp --http`, that sits behind your
company's sign-in (OAuth 2.1, Entra ID or Okta) for chat tools.
- **A skill**, installed with `fleet skill install`: a short file telling an
agent which commands to run in what order and what not to do, with tables
generated from the code so it cannot drift from the binary.

Authentication is opt-in: `FLEET_AUTH=tokens` turns on scoped tokens and an
audit log; a fleet on a LAN or tailnet works as before without it.

## Documentation

**[addisdev.github.io/fleet-runner](https://addisdev.github.io/fleet-runner/)**
Expand All @@ -78,6 +103,7 @@ curl -fsSL https://raw.githubusercontent.com/addisdev/fleet-runner/main/install.
| **[Get started](https://addisdev.github.io/fleet-runner/getting-started/)** | `fleet up`, a real job, and a result on the dashboard. Needs Node 22.13 and nothing else -- no Xcode, no NDK, no phone. |
| **[Exploratory QA](https://addisdev.github.io/fleet-runner/explore/)** | A vision model explores tonight's build on phones, simulators and TVs, and files only the bugs that reproduce on a clean install. |
| **[Wire in your own app](https://addisdev.github.io/fleet-runner/integration/)** | Publish builds on merge, run a nightly on your own devices, and block a pull request on the verdict. |
| **[AI agents and automation](https://addisdev.github.io/fleet-runner/agents/)** | The CLI, the skill, MCP over stdio, and the remote MCP gateway with company sign-in. |
| **[The protocol](https://addisdev.github.io/fleet-runner/protocol/)** | Register, long-poll, claim, beacon, report. Enough to write a runner in a language none of these are in. |

## How it fits together
Expand Down Expand Up @@ -140,14 +166,14 @@ languages, sharing not one line
| **[runner-ios/](runner-ios)** | Swift. SwiftUI, with llama.cpp and Core ML backends. The same sources build the tvOS and visionOS targets -- two conditions rather than a fork. |
| **[runner-machine/](runner-machine)** | TypeScript. A Node process that makes a laptop, desktop, board or NAS a fleet device, so a phone's tok/s and a laptop's land in the same table. |
| **[collector/runner-web/](collector/runner-web)** | JavaScript, one HTML file the collector serves at `/runner`: opening it enrols the browser that opened it. A smart TV, a console, a Chromebook -- anything with no way to install a signed app. |
| **[runner-roku/](runner-roku)** | BrightScript, because it is the only language a Roku will run. A SceneGraph channel, so a streaming player or a Roku TV is a fleet device. It has never been run on a Roku, and it has never been *compiled* -- there is no emulator -- so read its README before you read a number from it. |
| **[runner-roku/](runner-roku)** | BrightScript, because it is the only language a Roku will run. A SceneGraph channel, so a streaming player or a Roku TV is a fleet device. It has never been run on a Roku -- there is no emulator -- and CI compiling it with brighterscript is all that is checked, so read its README before you read a number from it. |

**The front doors**

| | What it is |
|---|---|
| **[fleet/](fleet)** | The CLI. `fleet up` runs this machine's components under a supervisor that backs off and gives up loudly rather than restarting a broken component forever; `fleet doctor` says what the machine can run and why not; `fleet service install` keeps it up. It wraps the three programs above and reimplements none of them. |
| **[desktop/](desktop)** | A Tauri menu-bar app around that CLI: a tray menu, role switches, the dashboard in a window, and a notification when a component gives up. It exists to test one hypothesis about macOS local-network permissions, and **none of its Rust has ever been compiled** -- read its README before believing anything in it. |
| **[desktop/](desktop)** | A Tauri menu-bar app around that CLI: a tray menu, role switches, the dashboard in a window, and a notification when a component gives up. It exists to test one hypothesis about macOS local-network permissions, and **it has never been launched**: CI compiles its Rust and that is all -- read its README before believing anything in it. |

Each directory has its own README, its own tests and its own CI job, filtered by
path so a change to a phone runner does not build the dashboard.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.7.2
0.8.0
2 changes: 1 addition & 1 deletion collector/dash/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fleet-dash",
"version": "0.7.2",
"version": "0.8.0",
"private": true,
"type": "module",
"description": "Fleet Runner dashboard SPA. Built to dash/dist and served by the collector at /dash.",
Expand Down
2 changes: 1 addition & 1 deletion collector/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"openapi": "3.1.0",
"info": {
"title": "Fleet Runner",
"version": "0.7.2",
"version": "0.8.0",
"description": "The Fleet Runner control API.\n\nEvery list is `{items, page, per_page, total, pages}`. Every failure is\n`{error: {code, message, hint?}}`; branch on `code`. Timestamps are ISO 8601 UTC.\n\nThe usual sequence for running something: listWorkloads (pick one, copy its\nexample), validateJob (fix what it says), createJob, waitJob until\n`finished`, then read `verdict`. `skipped` means nothing ran; it is not a pass.\n\nAuth is off unless the collector runs with FLEET_AUTH=tokens. Then send\n`Authorization: Bearer <token>`; scopes are read, run (implies read), admin\n(implies run), agent (the runner protocol) and delegate (a gateway naming the\nperson it acts for in X-Fleet-Actor). Each operation's `x-fleet-scope` says\nwhich it needs. Send `X-Fleet-Source` (mcp, cli, ci...) so jobs record where\nthey came from.",
"license": {
"name": "MIT"
Expand Down
2 changes: 1 addition & 1 deletion collector/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fleet-collector",
"version": "0.7.2",
"version": "0.8.0",
"private": true,
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion collector/runner-web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ <h1>Fleet Runner</h1>
* shelf forever as an offline device nobody can find.
*/

const APP_VER = "0.7.2";
const APP_VER = "0.8.0";
const SCHEMA = 1;
const BLOCK_SIZE = 4096;
const ROUNDS_PER_TOKEN = 1000;
Expand Down
2 changes: 1 addition & 1 deletion collector/src/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
* which CI runs on every push. See its header for why one number across four
* components is the rule.
*/
export const APP_VERSION = "0.7.2";
export const APP_VERSION = "0.8.0";
19 changes: 12 additions & 7 deletions desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,18 @@ the first `codesign` invocation.

## Status

Be suspicious of this whole directory. **None of the Rust has ever been
compiled**, on this machine or any other — there is no Rust toolchain here
(`cargo` and `rustc` are both absent), so `cargo check` was never run.
Be suspicious of this whole directory. **The Rust compiles, and that is all
that is known about it.** There is no Rust toolchain on the machine it was
written on, so CI does the compiling: `desktop / cargo check` stages the
sidecar and runs `cargo check --all-targets` on macOS for every pull request
that touches `desktop/`.

What **has** actually been checked:

- **`cargo check` passes.** Its first run failed in the build script, before
any Rust was compiled, because the bundled resource and the sidecar were
both named `fleet` in `target/debug`. With the resource renamed `fleet-tree`
the Rust compiled without errors on the first try, Tauri 2 APIs included.
- Every JSON file validates (`python3 -m json.tool`), and both plists lint
(`plutil -lint`).
- `ui/app.js` parses (`node --check`), and every element id it reaches for
Expand All @@ -239,10 +245,9 @@ What **has** actually been checked:
What has **not**:

- **It has never run.** Not the tray, not the settings window, not the sidecar
under Tauri, not a notification, not the dashboard window. Assume the first
`cargo check` produces a list of errors; the Tauri 2 APIs most likely to have
moved are `App::on_menu_event`, `TrayIcon::set_menu`, and
`tauri::async_runtime::spawn_blocking`. The Cargo package is `fleet-desktop`
under Tauri, not a notification, not the dashboard window. Compiling says
the Tauri 2 calls exist with those signatures, not that they do what the
code assumes. The Cargo package is `fleet-desktop`
and `productName` is `Fleet`; if the bundler cannot find its binary, that
mismatch is why, and `mainBinaryName` in `tauri.conf.json` is the knob.
- **The Local Network hypothesis.** See The bet, above. Everything this app is
Expand Down
8 changes: 4 additions & 4 deletions docs/deploy/headless.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ a Mac collector reachable at all.
one unit running `fleet up` instead of one unit per component. They ship one
more release and are then removed.

They are documented here rather than deleted because of an awkward fact:
**`fleet service install` has never been run on any platform**, and these
have. Until somebody has watched the new path start at login, the old path
is the one with mileage on it.
They are documented here rather than deleted because they have more
mileage: `fleet service install` now runs a real fleet's brain under launchd
and passes a systemd smoke test in CI, but nobody has watched its systemd or
Windows backends start a machine at boot, and these have.

## The plists are templates, and that is not tidiness

Expand Down
16 changes: 10 additions & 6 deletions docs/deploy/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,18 @@ depends on the machine:
| Linux | a systemd **user** unit | `~/.config/systemd/user/fleet.service` |
| Windows | a `schtasks` task that runs at logon | the task scheduler |

!!! danger "`fleet service install` has never been run, on any platform"
!!! warning "How much each backend has been run"

Not launchd, not systemd, not the Windows task. Every path it writes is
resolved absolutely -- launchd expands no `~` and reads no login `PATH`,
systemd is the same and Windows is worse -- and none of it has been watched
start at login. The code is written and reviewed; that is all.
- **launchd:** a real fleet's brain runs under it, on macOS 12.
- **systemd:** a CI smoke test installs the user unit on Ubuntu, checks it
with `systemd-analyze verify`, waits for the collector it starts to
answer, and uninstalls it. Nobody has watched it come back after a reboot.
- **Windows task:** never run.

What *has* been run, on macOS, are the older per-component plists in
Every path it writes is resolved absolutely -- launchd expands no `~` and
reads no login `PATH`, systemd is the same and Windows is worse.

Also run, on macOS, are the older per-component plists in
`collector/deploy/` and `runner-machine/deploy/`. Those are what the
reference deployment actually uses today, and they are documented in
[headless hosts](headless.md). They are deprecated -- they ship one more
Expand Down
15 changes: 9 additions & 6 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ seen the loop work.
curl -fsSL https://raw.githubusercontent.com/addisdev/fleet-runner/main/install.sh | sh
```

!!! warning "No release has been published yet, so that line does not work"
That downloads the latest [release](https://github.com/addisdev/fleet-runner/releases),
refuses it unless its checksum matches, and unpacks it into `~/.fleet`. It
prints the `PATH` line to add if `~/.fleet/bin` is not already on it. On
Windows the command is
`irm https://raw.githubusercontent.com/addisdev/fleet-runner/main/install.ps1 | iex`.

There are no tags and no archives, so the installer asks GitHub for the
latest release, finds nothing, and stops. **Build from a checkout instead:**
??? note "Or build from a checkout"

```bash
git clone https://github.com/addisdev/fleet-runner
Expand All @@ -36,8 +39,8 @@ curl -fsSL https://raw.githubusercontent.com/addisdev/fleet-runner/main/install.
`PATH`, or type the whole thing -- the rest of this page says `fleet` either
way.

[Install](install/index.md) has the same thing per platform, and is honest
about which of them anyone has actually run. macOS/arm64 is the answer.
[Install](install/index.md) has the details per platform, and is honest about
which of them anyone has actually run. macOS is the answer.

## 2. `fleet up`

Expand All @@ -46,7 +49,7 @@ fleet up
```

```
fleet 0.5.0-dev
fleet 0.8.0
home /Users/you/.fleet
roles brain, agent
brain http://127.0.0.1:8788 (bind 0.0.0.0)
Expand Down
12 changes: 10 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ fleet on. The answer is macOS; the suites run on all three.
Publish builds on merge, run a nightly on your own devices, block a pull
request on the verdict.

- __[AI agents and automation](agents.md)__

---

`fleetr` for scripts and coding agents, a skill that tells an agent how to
use it, and MCP tools, locally or behind your company's sign-in.

- __[Deploy](deploy/index.md)__

---
Expand Down Expand Up @@ -115,13 +122,14 @@ Five runners, in five languages, sharing not one line:
[`collector/runner-web/`](https://github.com/addisdev/fleet-runner/tree/main/collector/runner-web)
(JavaScript) and
[`runner-roku/`](https://github.com/addisdev/fleet-runner/tree/main/runner-roku)
(BrightScript, and never compiled -- see [platforms](platforms.md)).
(BrightScript, compiled in CI and never run on a Roku -- see [platforms](platforms.md)).

And two front doors:
[`fleet/`](https://github.com/addisdev/fleet-runner/tree/main/fleet), the CLI
behind `fleet up`, and
[`desktop/`](https://github.com/addisdev/fleet-runner/tree/main/desktop), a
menu-bar app around it whose Rust has never been compiled.
menu-bar app around it whose Rust compiles in CI and which has never been
launched.

[The history section](history/index.md) keeps the original architecture plan and
the design journals as they were written. They are not maintained, and they are
Expand Down
Loading
Loading