Skip to content

Execute Python skills on the connected client and clear stopped activity - #41

Draft
Jepson2k wants to merge 5 commits into
mainfrom
feat/skill-runtime
Draft

Execute Python skills on the connected client and clear stopped activity#41
Jepson2k wants to merge 5 commits into
mainfrom
feat/skill-runtime

Conversation

@Jepson2k

@Jepson2k Jepson2k commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Run decorated Python skills through the existing synchronous client connection and event loop. Skills can use a typed PAROL6 async client internally, compose nested calls, and run unchanged through the Commander stepping wrapper. Live and dry-run clients expose their supported skill capabilities.

Cancellation testing exposed stale activity after stopping a planned trajectory: the legacy command executor did not own that trajectory, so status remained EXECUTING after the segment player stopped it. Clear that player's activity when cancelling without marking the interrupted command complete.

This is stacked on #40 for the shared shape contract and incorporates #39 so Commander's pending diagnostics changes receive real drive-fault data. Both prerequisite PRs must land before this branch is rebased onto main. Requires the companion waldoctl feat/skill-runtime branch (release target v0.14.0). CI resolves the matching contract branch before package installation so an unreleased tag does not break dependency resolution.

Validation: 38 selected integration/dry-run tests passed, covering real fake-serial motion, confirmed cancellation, rejection of another move after catching cancellation, reuse of the connection, stop semantics and blended previews. All eight executable examples passed; their subprocesses use an OS-probed status port so Windows reserved port ranges do not block the suite. Source type checks and pre-commit hooks passed. The full GitHub CI matrix passed across Linux, macOS and Windows on Python 3.11–3.14. Linux Python 3.13 recorded 289 unit/integration tests plus all eight examples; Windows Python 3.13 also passed all eight examples. No physical robot motion was used.

Companion drafts: waldoctl #26, PAR6 #67, and Commander #49.

claude and others added 3 commits September 4, 2026 21:02
The drivers report fault bits over the serial link rather than analog
temperature or current registers, so drive_health carries faults and
nothing else here: one label tuple per joint, empty when that drive is
healthy. A list of empty tuples is deliberately not the same as an empty
list — the first is an all-clear, the second is a backend with no fault
reporting at all, and a display that cannot tell them apart will show a
bus it never asked about as healthy. The label view is rebuilt only when
a bit moves, so the 100 Hz path allocates nothing while the drives are
fine, and the wire entry is replaced only on change so a snapshot's
shallow copy keeps the labels current when it was taken.

SET_STATUS_RATE makes the broadcast rate a session knob rather than a
boot constant, which is what lets a capture or a tuning run get
resolution the 50 Hz default cannot. Status is emitted every Nth control
tick, so the achievable rates are the divisors of the control rate; the
STATUS_RATE query reports that loop rate rather than a list of legal
values, letting a caller compute the set itself. A rate that does not
divide evenly is refused with a remedy naming the ones that do, never
rounded to a neighbour — a capture taken at a rate nobody asked for is
wrong in a way nothing reports.

The control loop re-derives its broadcast interval when the rate moves
instead of capturing it once at startup, and the status cache adopts the
new period too, since it differentiates TCP position against it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsMBr94VMTsLJtPJq7h6A5
The validator divided before it screened: control % int(hz) ran ahead of the
integrality check, so 0.5 Hz floored to a zero divisor and NaN could not be
made an int at all. Both escaped as a generic tick failure, which is the one
thing a refusal must not do -- whoever asked for 0.5 Hz got a crash instead
of the rates that would have worked. The guard now establishes finite, in
range and integral before the modulo sees anything, and refuses through the
command protocol rather than an exception the executor has to unwrap.

make_error() formatted title and cause only, so the one template that puts
its parameter in the remedy -- the remedy being where a refusal says what
would have worked -- reached the client reading "Pick a rate that divides
{control} Hz: {allowed}". All four fields format now.

The rate also had more owners than one. STATUS_BROADCAST_INTERVAL was
computed at import from the boot rate, the status cache kept a
_status_rate_hz shadow of its own, and StatusBroadcaster took a rate_hz it
never read. ControllerState owns it now: status_broadcast_interval(hz)
derives the tick count on demand, and the TCP-speed derivative divides by
the live broadcast period while keeping the period its previous sample was
taken at, so the frame that straddles a rate change is not differentiated
against a period it never spanned.

Drive-fault labels are a pre-built lookup keyed by the two bits, because
that path runs at 100 Hz and the hot path does not allocate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Base automatically changed from feat/mujoco-world-model to main September 8, 2026 11:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants