Close the nine open SDK tasks: one engine per directory, a tabbed dashboard, re-pairing, run recovery
Docs / docs (push) Successful in 27s
Playwright Tests / test-playwright (1, 2) (push) Failing after 17s
Playwright Tests / test-playwright (2, 2) (push) Failing after 12s
pre-commit / pre-commit (push) Failing after 1m59s
Test Backend / test-backend (push) Failing after 2m30s
Compose Smoke Test / test-compose (push) Failing after 13s
Playwright Tests / merge-reports (push) Failing after 2m19s

serve: refuse a second engine for one data directory whatever port it was
asked for, using the pidfile and a token this directory signed. The check
runs before the database is touched and before the credential is written,
which is what left every later CLI call pointing at a dead port.

The terminal dashboard is three tabs (Overview, Runs, Logs) with the toolbar
following the focused pane, the engine's output goes to serve.log rather than
down a pipe, and closing the screen stops both reader threads so the prompt
comes back. It adopts a running engine on every start, so stop/start and
restart work on one it did not start, and a stop waits for the process to be
gone before the next start. Enrolment reports itself in the modal.

enroll: a new claim code replaces the pairing instead of being refused. The
code is redeemed before anything is written, mappings to a portal being left
are cleared, and a running engine redials when the stored enrolment changes.

runs: an engine re-queues the runs left `queued` by the one before it, and
`fluksio retry <id>` / `retry --group <sweep>` submits an interrupted run
again with the same inputs and group, recorded through Run.parent_id.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9BoNGq6V9MdRWAte7JBuC
This commit is contained in:
2026-08-31 17:37:13 +02:00
co-authored by Claude Opus 5
parent bdad6d7fc2
commit 8a94bf10d7
14 changed files with 873 additions and 140 deletions
+53 -9
View File
@@ -55,10 +55,13 @@ The default port moves out of the way when something already has it (8001,
moved off: `--port 9000` on a taken 9000 fails, because something else is
there and you named it.
What it will *not* do is start a second engine for the same instance. If
the port is held by an engine already serving this directory, it says so and
stops, since one SQLite database wants one engine. Another instance's Fluksio
on that port is named, and the move happens as usual.
What it will *not* do is start a second engine for the same instance. Before
anything else, `serve` looks for an engine already serving this directory
the pidfile beside the data says where, and a token this directory's key
signed says whether what answers there is ours — and stops if it finds one,
whatever port the second was asked for. One SQLite database wants one engine,
and the second would repoint `client.json` at a port that dies with it.
Another instance's Fluksio on the port is named, and the move happens as usual.
| Option | Default | What it does |
|---|---|---|
@@ -115,25 +118,36 @@ dashboard is served from there rather than here.
### The dashboard
At a terminal, `serve` draws the health overview, the recent runs, and the
engine's own log in a pane below, so the output above is in there rather than
replaced by it.
At a terminal, `serve` opens three tabs: **Overview** is the health block and
the flows, **Runs** is the history, and **Logs** is the engine's own output.
| Key | What it does |
|---|---|
| `1` `2` `3` | the Overview, the Runs and the Logs |
| `q` | close the dashboard. **The engine keeps running**, and the pid is printed |
| `s` | stop the engine, or start it again |
| `r` | restart it |
| `c` | cancel the run the cursor is on |
| `e` | pair with a portal, without leaving the screen |
On the Runs tab the toolbar carries what a run is for:
| Key | What it does |
|---|---|
| `space` | tick the run under the cursor into a comparison |
| `enter` | compare the ticked runs, or draw the one under the cursor |
| `c` | cancel the run the cursor is on |
| `a` | list what the run left behind, and fetch it |
| `e` | pair with a portal, without leaving the screen |
The engine is a child process rather than a thread, which is what makes those
possible, and what makes `q` a way out of the screen rather than a way to
stop the engine. Running `fluksio serve` again reattaches to it.
Its output goes to `serve.log` in the data directory rather than down a pipe,
which is what lets the screen be closed while the engine keeps running — a
pipe with nobody reading it breaks the next line the engine writes, and a
node's `print` is one of those. It also means the Logs tab has the output of
an engine this screen only adopted, and the scrollback of the one before it.
An engine started elsewhere is adopted rather than duplicated, and can be
stopped from here only when it is this instance's own: both the pidfile
beside the data and a token this directory's key signed have to agree. Another
@@ -185,6 +199,14 @@ Get the code from the portal under **Instances → Add instance**. It is
single-use and expires in fifteen minutes. `--as` matters when the instance
has several superusers. Without it, enrolment refuses rather than guessing.
An instance that is already paired is re-paired: a new claim code replaces the
connection, at the same portal or another one, and a running engine drops the
old tunnel within a few seconds. The code is redeemed before anything is
written, so one the portal rejects leaves the connection as it was. Moving to
a different portal clears the local accounts' mappings to the old one, since
nothing in a mapping says which portal issued it — the people who had access
are admitted again from the new portal.
Afterwards, `fluksio serve` dials the portal as it comes up, and keeps dialling:
a portal that restarts, a wifi that changes, a laptop that suspends and wakes
somewhere else all end the same connection, and the link is put back up without
@@ -405,6 +427,26 @@ everything else off the line. `Client.runs()` and
`--local` reads the same history from an in-process engine, without one having
to be served.
### `fluksio retry`
```sh
fluksio retry <run-id> [<run-id> ...]
fluksio retry --group <sweep-id>
```
Runs the same thing again as a run of its own, carrying the flow, the inputs,
the seed and the group of the one it repeats, and recording it as its parent.
What it is for is a run an engine restart interrupted: those are marked
`abandoned` once their lease goes stale, and `--group` retries every run of a
sweep that did not end `ok`, so the missing combinations are filled in rather
than the whole grid being submitted again. The stage cache is what makes it
cheap — the nodes that finished are restored rather than recomputed.
A run that has not finished is refused; cancel it first.
Runs that were still *queued* when an engine stopped need none of this: the
next engine reads them back out of its own history and picks them up.
### `fluksio flavors`
```sh
@@ -493,6 +535,8 @@ The message name is the one to pass, since it is what addresses the bytes;
```text
.fluksio/ (or ~/.fluksio, with `--global`)
├── client.json the token `serve` wrote, mode 600
├── serve.pid the engine serving this directory, while one is
├── serve.log what the engine under the dashboard printed
├── .gitignore `*`, so a database and a credential are ignored from within
├── fluksio.db SQLite: users, runs, metrics, observability, agents
├── flows/ a git repository, one directory per flow