Add composable Python skills with sync and async execution - #26
Merged
Merged
Conversation
This was referenced Sep 7, 2026
Draft
A skill's own asyncio.timeout()/wait_for() around a supplied-client call or a nested skill cancels the running task exactly like an external cancel, so the per-call guard sent a real STOP to the arm and poisoned the execution on an ordinary timeout. Cancellation is now authoritative only where it escapes the root invocation; nested invocations report cancelled and propagate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A program is written once and runs against a live backend or a preview by swapping the client, so every wrapper standing in for a client -- the stepping wrapper, the path preview, a backend's dry run -- has to agree on which calls queue on the controller, which mint an index the program may wait on, which cancel the queue, and which read live state a plan cannot predict. Each wrapper kept its own list, so a new method was stepped, previewed and waited on by whichever list happened to mention it. The classification now lives where the method is declared: a @command marker per method, read back by command_table(). The conformance test refuses an unclassified public method, and a backend override inherits the ABC's spec, so the contract belongs to the interface rather than to each override. Streamed motion and estimate_payload are marked as minting no index -- they are the commands whose return is not a queue position. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`requires="motion"` became a frozenset of its letters, every letter passed the identifier pattern, the skill registered, and every invocation then demanded five one-letter capabilities of the client. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
move_c, move_s, move_p, tool_action and write_io carried NotImplementedError defaults, which made them read as optional. Both backends implement all five, so they join the rest of the control surface as @AbstractMethod. What does vary by hardware is already declared as a has_* flag on Robot, so a skill's requirements need only cover those. Requires is one boolean per flag, replacing the free-form capability set that lived on the client: a mistyped requirement is a type error at the call site instead of a MissingCapability at run time, and a backend's support is declared in one place rather than two. The check reads client.robot, so a client that names no backend reports every requirement missing rather than assuming it met. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N8zt66KjkzgZLtQuSfJd9r
isinstance against the DryRunClient protocol resolves members statically, so a client that forwards attribute access, such as a preview wrapper or the skill guard, never matches. is_dry_run resolves each public member with ordinary lookup instead. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N8zt66KjkzgZLtQuSfJd9r
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N8zt66KjkzgZLtQuSfJd9r
Jepson2k
marked this pull request as ready for review
September 17, 2026 22:33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reusable robot sequences need one Python implementation that works with connected synchronous and asynchronous clients. This adds
@skill, a typed sync facade (skill(rbt, ...)) and explicit async entry point (await skill.async_call(rbt, ...)), while keeping connection ownership with the supplied client.waldoctl.skillsentry points discover callable plugins; ordinary imports need no registration. Broken plugins are diagnosed, and duplicate ids exclude all conflicting providers.Validation: 150 tests passed, 4 existing tests skipped; source type checks, all pre-commit hooks and the full GitHub CI matrix passed. Live-controller cancellation and sync execution are exercised by PAROL6 #41 and PAR6 #67; preview and GUI stepping are in Commander #49. All use
feat/skill-runtime.Release dependency: companion backends and Commander target waldoctl
v0.14.0; publish that release after this PR merges and before releasing the consumers.