32 — YAML task scenarios + a live event stream from the node
Three things in one example:
- Describe computer tasks as YAML scenarios and run them against a urirun node over
the mesh — login to a site, drive the desktop, write an office document, audit a box.
- Receive the node's logs/errors live, as URIs — the runner subscribes to the
node's SSE event stream (GET /events) and prints each run/error event the node emits *while* the steps dispatch, so you see both sides in real time.
- Generate scenarios from natural language —
nl_scenario.pyturns an NL goal into
a YAML scenario of URIs (LLM, constrained to the node's live action space), runs it, captures both directions of the flow, and writes a Markdown report.
NL → YAML scenario → run → Markdown (both directions)
./nl_scenario.sh "sprawdź zdrowie maszyny, wypisz 5 procesów i zapisz notatkę 'audyt OK'"
NODE_URL=http://192.168.188.201:8765 ./nl_scenario.sh "otwórz https://example.com i zrób zrzut ekranu"
It (1) asks the LLM for [{uri, payload, why}] using only the node's /routes, (2) saves the YAML to scenarios/gen-<slug>.yaml, (3) dispatches each step (host→node) while a background SSE subscriber collects the node's events (node→host), (4) reads the node's own log back, and (5) writes generated/<slug>.md — goal, the generated YAML, a Host → Node dispatch table, a Node → Host live-events table, and the node-side log. See the committed generated/*.md for real runs against lenovo. The model + key come from examples/.env; with no key it falls back to a deterministic generator.
HOST NODE
──── ────
run_scenarios.py ──POST /run {uri,payload}──► executes a step
│ (one per YAML step) │ emits an event
▼ ▼
◄─────────────── GET /events (SSE) ─────────── run://… / error://local/E-…
prints "░ node-event: run him://lab/… ok" (logs + errors as URIs, immediately)
Run
./run_scenarios.sh # every scenarios/*.yaml
./run_scenarios.sh scenarios/web-login.yaml # one scenario
NODE_URL=http://192.168.188.201:8765 ./run_scenarios.sh scenarios/system-audit.yaml
A live run against a mock office node (4 scenarios) interleaves dispatch + node events:
[1] him://lab/keyboard/command/type-text
-> ok: {"typed": "jan@firma.pl", ...}
░ node-event: run him://lab/keyboard/command/type-text ok
[5] browser://lab/page/command/screenshot
-> FAIL: "HTTP 400"
░ node-event: error error://local/E-1ebc9205/query/info INVALID_ARGUMENT
== 4 scenario(s): 15/18 steps ok; 21 live node-events received ==
system-audit.yaml works on any node (even the default 7-route one), so it runs against the live remote node too; the others need the office surface (him/kvm/ browser/urioffice) — see example 31.
Scenario format
name: web-login
description: ...
steps:
- uri: browser://{host}/page/command/open # {host}/{monitor}/{session} are resolved
payload: {url: "https://example.com/login"}
why: open the login page
- uri: him://{host}/keyboard/command/type-text
payload: {text: "$ref:0.title"} # thread an earlier step's output
{host}→ the node's name,{monitor}→0, etc. (also handles the
percent-encoded %7B…%7D that /routes emits).
$ref:<step>.<field>pulls a value from an earlier step's result.- No PyYAML required — a tiny built-in parser covers this subset (PyYAML is used if present).
The live event channel (GET /events, SSE)
Any urirun node now exposes a one-way node→host Server-Sent Events stream. Each dispatched route publishes a run event, and every failure an error event whose uri is the error://local/E-… address — so the controller receives logs and errors as URIs the instant they happen, without polling /errors.
urirun host watch lab # or: urirun host watch http://NODE_IP:8765
curl -N http://NODE_IP:8765/events # raw SSE
run him://lab/keyboard/command/type-text ok
run browser://lab/page/command/screenshot FAIL INVALID_ARGUMENT
error error://local/E-1ebc9205/query/info INVALID_ARGUMENT
Fan out events to MQTT (many subscribers / a UI)
For more than one consumer — a dashboard, several operators, automations — republish the node's events to an MQTT broker. urirun host watch does it inline:
urirun host watch lab --mqtt-broker localhost:1883 # node events -> MQTT
urirun host watch lab --mqtt-broker localhost:1883 --scheme error # only errors
# any number of subscribers then consume, wildcarding by node or kind:
mosquitto_sub -t 'urirun/events/lab/#' # everything from node 'lab'
mosquitto_sub -t 'urirun/events/+/error/#' # every node's errors
Topic layout: urirun/events/<node>/<event>/<uri-scheme> (e.g. urirun/events/lab/run/him, urirun/events/lab/error/error). Needs paho-mqtt on the watcher (pip install paho-mqtt) and a reachable broker. The same publish point can feed the urirun-connector-mqtt device bridge or any MQTT-native dashboard.
See NOTES-bidirectional.md for why SSE (not WebSocket) and whether a fuller two-way channel needs a package refactor.
Files
scenarios/*.yaml— task scenarios (web login, desktop control, office doc, audit) +
gen-*.yaml (NL-generated).
run_scenarios.py/run_scenarios.sh— dispatch steps + watch the event stream.nl_scenario.py/nl_scenario.sh— NL goal → generated YAML → run → Markdown report.generated/*.md— Markdown reports of real both-direction runs.test_scenarios.py— offline test (parse, thread, run against a fake node, NL generate).NOTES-bidirectional.md— design assessment of the live channel.
Test
python3 -m pytest test_scenarios.py -q # or: python3 test_scenarios.py