The agent and the client
The remote user of a bench is the developer running the install → update → field-test loop from their own machine. This page is the mechanism behind Work from another machine: the two distributions, the wire, and the one place the design deliberately breaks its own rule.
Two deliverables, one repository
|
|
|
|---|---|---|
What |
The client: |
The bench application: model, services, presenter, CLI, console, agent |
Ships as |
wheel on PyPI, distribution |
wheel on PyPI, distribution |
Depends on |
|
|
Imports |
nothing from |
|
pyproject.toml makes the split: only-include = ["plesty/bench"] for this
project’s wheel, plesty-bench as its only console script; the server’s
dependencies are the server extra (and the dev group, so uv sync in a
checkout has everything). The version and the CHANGELOG are shared, and
describe reports the server version so a client can tell what it is talking
to.
The point of the split is that a developer machine that only installs, updates
and field-tests pulls pyzmq and click and nothing else — no Qt, no
plesty-lib, none of the bench application.
The contract: the CLI is the API
The agent accepts a plesty-server argument list and returns the exit code and
the output. Anything the command line can do is remote, with no second schema
to keep in step. Four request types on the plesty-lib device-server wire (ZMQ
DEALER → ROUTER, JSON, status: ok|error):
{"type": "describe"}
→ {"status": "ok", "result": {"kind": "plesty-server-agent", "version": "0.1.0",
"home": "…", "fleet": "…", "commands": ["declare", …],
"requests": ["describe", "exec", "fetch", "device"]}}
{"type": "exec", "argv": ["status", "--json"], "token": "…"}
→ {"status": "ok", "result": {"code": 0, "stdout": "[…]", "stderr": ""}}
{"type": "fetch", "path": "repos/plesty-pm100d/exp/reports/pm100d-s120c/field-test.json", "token": "…"}
→ {"status": "ok", "result": {"path": "…", "size": 2210, "data": "<base64>"}}
{"type": "device", "name": "pm100d", "request": {"type": "query", "param": "wavelength"}, "token": "…"}
→ the device server's own reply, forwarded unchanged
Because it is the device-server wire, plesty.lib.service.build_client and any
describe probe understand the agent without plesty-bench at all.
Long work fits without streaming: install, update and field-test --no-wait return a job id, and jobs log <id> --offset N --json returns
{text, offset, running, returncode, state} — BenchClient.follow() polls
that until the job ends. So a dropped connection costs the output and not the
run. list, status, reports and jobs list take --json for structured
results.
The one exception: device
device relays a request to a declared device’s own server. It breaks the rule
above deliberately, for two reasons.
exec cannot carry it. A control panel is a long-lived session of many
small round trips — a poll per parameter per second — and exec answers each
one by running the click application. A schema fetch and a stream of reads is
not a command invocation.
Nor should the client reach the device directly. A device server binds
tcp://*, so it is reachable across the network already — but it checks no
token. The agent does. Routing through it keeps one authenticated door for a
bench instead of one unauthenticated one per instrument, and confines what a
client can reach to the devices the bench is actually running.
The reply is the device server’s own envelope, passed through: an instrument
that refused a write says so in its own words rather than as a relay failure.
BenchClient.device(name) wraps it as a RemoteDevice — describe, query,
write, call — which is exactly plesty-lib’s DeviceHandle, so the same
DevicePanel the console opens beside an instrument is driven from a laptop.
One door, and what it does not decide
The agent compares one shared secret in constant time and learns nothing else about who is calling. That is enough for what it guards — a bench on a lab network — and it is why the console’s master and node roles (Several benches) are a configuration convention rather than a permission.
A master is a bench that has been told to keep a list of other benches and
show their devices. Nothing on the wire distinguishes its exec from anyone
else’s: a node cannot tell the master from a shell with the same token, so it
cannot refuse one and serve the other. Two benches can each declare themselves
master of the same nodes, and both will work.
Making it a permission needs a second, control-scoped secret — one the node
checks on device and on the lifecycle commands, and hands to nobody but its
master. That is a change to this page’s contract and is deliberately not made
here: the asymmetry it would enforce is, today, an asymmetry of configuration.
Serving it
The agent runs three ways, all the same service: plesty-server agent,
fleet run --agent in the headless loop, and a thread of the Qt console
(Server ▸ Start agent). One service means no second process to supervise and
no way for the surfaces to disagree.
Settings are per bench — not per fleet and not packaged — so they live in the
home as <home>/agent.yaml (bind, token, workers, role, Plesty share and bench folder;
owner-readable). Every
command-line option overrides the file. Saving does not move a running agent:
its clients connected to the endpoint it bound, with the token it started with,
so the console asks for a stop and a start instead.
Every
execruns the click application in-process with captured output and the agent’s--home/-fprepended — a freshBenchper command, so the agent and a local CLI see the same files.Requests run on a worker pool; a
describeis answered while astartwaits for its probe. Replies go back through the socket thread.execrefusesagentandgui;fetchis confined to the bench home and capped at 16 MB;devicereaches only the declared devices this bench is running; the token is compared in constant time.Same trust model as the device servers: the lab network, no TLS.
Without an agent
ssh lab-bench-03 plesty-server status needs neither the agent nor
plesty-bench — only OpenSSH on the bench, which Windows has too. It is the
fallback where a second listening port is unwelcome.