Refuse what a node cannot publish, and stop timing out work that is fine

Four things the python SDK turned up, each fixed where every client sees it.

A key no port declares is now an error rather than a silent drop, on the
return, the yield and the emit alike — the contract the docs already stated.
The SDK reads literal yields at sync time, so a typo fails before anything
runs, and an emission of one fails the call rather than being logged where
nobody looks.

NaN and infinity are refused at the port. JSON cannot spell either, so one
that travelled came back as a 500, a socket frame that stopped the canvas, or
a metric batch the database dropped whole.

An artifact input takes `@run:<id>.<output>` or a bare digest, resolved on the
engine — so the CLI, the run dialog and a python caller mean the same thing,
and a sweep can pass one at all.

Node timeouts are off by default. The clock measured silence, which a training
node is full of, and remote workers had already stopped enforcing it — their
heartbeat reset it. Now a heartbeat proves the agent rather than the node,
ninety seconds of nothing fails the call either way, and the engine touches
work it is still running so a long node is not redelivered at sixty seconds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019V5bsYGNxcgPs4xXmTPx69
This commit is contained in:
2026-08-25 07:30:14 +02:00
co-authored by Claude Opus 5
parent c33fa404a4
commit 93374a310e
32 changed files with 968 additions and 67 deletions
+1 -1
View File
@@ -123,7 +123,7 @@ warning into a refusal to start.
| Variable | Default | Notes |
|---|---|---|
| `FLOW_MAX_WORKERS` | `4` | node-code subprocesses run in parallel |
| `FLOW_NODE_TIMEOUT` | `30.0` | seconds a node may run, unless it sets its own |
| `FLOW_NODE_TIMEOUT` | `0` | seconds a node may be silent, unless it sets its own; 0 is no limit |
| `OBS_RETENTION_DAYS` | `30` | how long metrics, events and run records are kept |
## Agents
+12
View File
@@ -24,6 +24,12 @@ the state backend, the work queue and the worker protocol unchanged.
is not a measurement, and a switch bound to a temperature is a mistake worth
catching.
`NaN` and infinity are refused, wherever they sit — including inside a `json`,
`record`, `series` or `list`. JSON cannot spell either, so one that travelled
would come back as a response nobody can parse and a row the database rejects,
a long way from the node that made it. An empty subset or a division with no
denominator is what usually produces one; publish `None` instead.
## The structured ones
These are *declared shapes* rather than "some JSON", which is what makes a
@@ -93,6 +99,12 @@ stays valid wherever the store is reachable from, including on another machine.
Node code produces one with `fluksio.save_artifact` and opens one with
`fluksio.load_artifact`. See [Writing node code](../code/nodes.md#bytes-artifacts).
As a *run parameter* it is also accepted as text, since nobody wants to paste
the object into a shell: `@run:<id>.<output>` names what a run produced, and a
bare `sha256:…` digest names the content. Both resolve to the reference above
before the run starts, so the CLI, the run dialog and a python caller all mean
the same thing.
### `json`
Anything JSON-serializable. The escape hatch, and the right answer when a