feat: add SilaNodeClient for native SiLA2 device integration - #291
Merged
Conversation
Add a SilaNodeClient implementing AbstractNodeClient to enable MADSci workcells to communicate directly with SiLA2 servers over gRPC without REST middleware. Uses the sila2 Python SDK and integrates via the existing URL-scheme dispatch mechanism with sila:// URLs. Key changes: - SilaNodeClient with support for observable/unobservable SiLA commands, dot-notation action names, server introspection, and property reading - SilaNodeClientConfig with gRPC-specific settings (TLS, timeouts, polling) - Conditional registration in NODE_CLIENT_MAP when sila2 is available - sila2 as optional dependency (pip install "madsci.client[sila]") - Rename MadsciClientConfig -> MadsciHttpClientConfig (with compat alias) to clarify it's HTTP-specific now that gRPC clients exist - 46 unit tests covering all client capabilities Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a working SiLA2 example server to the example lab with a validated Jupyter notebook demonstrating all SilaNodeClient capabilities. Key changes: - SiLA example server with Greet (unobservable), CountDown (observable), and ServerUptime (property) — generated via sila2-codegen - Example server registered in compose.yaml and settings.yaml workcell_nodes so it appears on the dashboard - sila_node_notebook.ipynb exercising get_info, get_status, get_state, send_action (sync + async), polling, and error handling - validate_nb_sila justfile recipe added to validate_notebooks - get_info() now populates ActionDefinition.args with ArgumentDefinition entries extracted from SiLA command parameters - get_info() sets asynchronous flag based on observable vs unobservable - Fixed FQI-to-short-identifier conversion for feature discovery - Fixed observable command polling (instance.done instead of get_responses) - Fixed Docker issues: replaced typer with argparse, added --disable-discovery - Updated OpenSpec specs to match implementation learnings - ruff per-file ignores for SiLA codegen output Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Archive sila-node-client and example-sila-node changes, syncing 5 delta specs (sila-client-dispatch, sila-command-execution, sila-server-introspection, sila-example-server, sila-notebook-validation) to openspec/specs/. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Fix get_action_result to propagate FAILED status instead of hardcoding SUCCEEDED, add callable() guard in qualified-name command resolution, align validate_url type signature with parent class, and document private SiLA SDK attribute usage. Includes 3 new tests and synced OpenSpec specs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SiLA commands can return binary data (bytes), but the SilaNodeClient
previously converted these to lossy string representations via str().
This change detects bytes in SiLA responses, writes them to disk as
.bin files in .madsci/sila_files/{action_id}/, and populates
ActionResult.files as ActionFiles. The bytes are also base64-encoded
in json_result for lightweight inspection.
- Add bytes detection in _serialize_value() with sentinel dict pattern
- Add _extract_bytes_files() post-processor for file extraction
- Integrate into all action result paths (unobservable, observable, polling)
- Set action_files=True in NodeClientCapabilities
- Add GenerateData command to SiLA example server
- Add Binary Data / ActionFiles section to SiLA notebook
- Add 16 new tests covering all bytes handling scenarios
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove unused action_id parameter from _serialize_value(), document top-level-only bytes extraction limitation and get_state() property detection heuristic, narrow ruff per-file-ignores for SiLA example server, simplify validate_url() to match parent class, add sila_example_server to notebook_validator depends_on, and fix validate_nb_sila recipe cleanup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… images Two bug fixes: 1. `madsci new lab -n <name>` (and other `madsci new` subcommands) ignored the -n/--name argument when running in interactive mode (the default). The interactive prompt function now accepts an overrides dict so CLI-provided values are used as defaults for all 7 parameter types (string, integer, float, boolean, choice, multi_choice, path). 2. Generated Docker Compose templates used nonexistent local image names (e.g. `madsci-squid:latest`). Updated to use the correct GHCR images: `ghcr.io/ad-sdl/madsci_dashboard:latest` for lab_manager and `ghcr.io/ad-sdl/madsci:latest` for all other services. Also removed the stale `build:` section from lab_manager. Adds 10 regression tests covering overrides for every parameter type. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Classify connection errors into actionable categories (DNS, refused, timeout, TLS, gRPC) and include target host:port, TLS mode, and diagnostic hints in all error messages from SilaNodeClient. Applies consistently across _get_sila_client, send_action, get_status, get_state, and get_info. Adds 28 new tests covering classification, formatting, and integration with client methods. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: CLI interactive mode respects --name arg; correct compose images
Captures the standards-exploration deliverable for SiLA2 Migration issue #294. Design-only proposal (no spec deltas) covering the seven scope areas: SiLA core standards audit, action mapping, state decomposition, NodeStatus/NodeInfo evolution, file/binary handling, admin commands as a SiLA Feature, and AbstractNodeClient abstraction. Includes resolved decisions RD1-RD8 and a draft list of ~12 downstream implementation issues (#A-#M) to open against the parent project (#293) once the design is accepted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Path-traversal hardening: _extract_bytes_files now sanitizes both
action_id and response keys via _safe_path_component (Path(name).name +
explicit reject of empty/./..) so malicious inputs can no longer write
outside the per-action sila_files/ subdirectory.
Command introspection: import sila2.client.{ClientObservableCommand,
ClientUnobservableCommand} and use isinstance(_, _SILA_COMMAND_TYPES)
in get_info, _resolve_sila_command (both dotted and short form), and
_is_command_observable. Properties (ClientObservable/Unobservable
Property), SDK helpers, and arbitrary callables no longer surface as
ActionDefinitions or resolution matches.
Layered error classification in send_action: split the single
try/except into three blocks - (A) _get_sila_client, (B) command
resolution + arg prep, (C) command execution. Only block C enriches,
and only when _classify_connection_error returns a non-"unknown"
category. Resolution errors and plain command errors keep their
original message; connection failures from block A pass through
already-enriched (no double-wrap).
Server startup: __main__.py now sys.exit(1) on bind failure instead of
returning silently with exit 0.
Compose: sila_example_server gets a Python-based TCP healthcheck on
port 50052; notebook_validator.depends_on is converted to long-form
with sila_example_server: condition: service_healthy. validate_nb_sila
in .justfile drops --no-deps so Compose handles the dep graph itself.
Tests: 86 -> 110 (+24). New TestSafePathComponent,
TestExtractBytesFilesSafety, TestSendActionErrorLayering classes; new
get_info exclusion tests for properties and non-command callables;
fixed test_unknown_action_returns_failed mock ordering. Test helpers
now build proper MagicMock(spec=ClientObservableCommand /
ClientUnobservableCommand) instances so isinstance checks succeed.
OpenSpec: sila-client-pr291-must-fixes archived. Modified deltas on
sila-command-execution, sila-server-introspection,
sila-example-server, sila-notebook-validation. One-time normalization
of the four affected finalized specs from "## ADDED Requirements" to
"## Purpose" + "## Requirements" so openspec's MODIFIED matcher could
locate existing requirements.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Following audit of PR #291, sync documentation, examples, and the CHANGELOG with the SiLA artifacts that ship in this branch and frame them consistently as an experimental client-only preview pending the broader sila2-native-node-design migration. - example_lab README: add sila_example_server to node list, port 50052 to network/troubleshooting, TCP healthcheck snippet, notebook to notebook list, and a dedicated SiLA Example Server section. - sila_node_notebook: switch install cell to '[sila]' extra; intro now carries the experimental notice and a tutorial-vs-validation-harness disclosure. - sila_example_server/server.py: drop misleading code-generator header and document the file as hand-customized. - docs/guides/node_development.md: add Consuming SiLA2 Devices section with what's available now vs. deferred (Sila2Node, async, admin, file inputs, log/history/resources). - docs/CHANGELOG.md: add SiLA2 Native Node Client (Experimental) subsection under [Unreleased] / Added. - src/madsci_client/README.md: add SiLA2 Client (Experimental) section alongside the REST client. - src/madsci_client/.../node/__init__.py: fix stale 'unitelabs-sila' comment to reference the [sila] extra. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
What ships in this PR
SilaNodeClient(madsci.client.node.sila_node_client): NewAbstractNodeClientsubclass that connects to SiLA2 servers via gRPC using thesila2SDK. Installed as an optional dependency (pip install "madsci.client[sila]") with graceful degradation when the SDK is absent. Supports:sila://host:portURL dispatch (auto-routed byfind_node_client()alongsidehttp://).get_info,get_status,get_state).FeatureName.CommandNamedot-notation resolution; short-form when unambiguous.isinstance(_, ClientObservable/UnobservableCommand)against the SDK's public command base classes — properties, SDK helpers, and arbitrary callables are correctly excluded.Bytes /
ActionFilessupport (with path-traversal hardening): SiLA commands returningbytesare written to disk under.madsci/sila_files/{action_id}/as.binfiles, surfaced asActionFilesonActionResult.files, and base64-encoded injson_result. Bothaction_idand response keys are sanitized via_safe_path_component()so a malicious server response or upstream caller cannot escape the per-action subdirectory.action_files=Truein capabilities.Layered connection-error diagnostics: Errors include structured context — classification (
dns_resolution,connection_refused,connection_timeout,tls_error,grpc_error,unknown), target host:port, TLS mode, and actionable hints.send_actionis split into three try/except layers (connection / resolution / execution); only connection-category execution failures get the wrapper. Command-resolution errors and plain command errors keep their original messages — no misleading"SiLA connection error: ..."for arg errors. Original messages always preserved; never double-wrapped.Example SiLA2 server (
examples/example_lab/example_modules/sila_example_server/): Minimal server with oneExampleDeviceFeature exposingGreet(unobservable),CountDown(observable, intermediate progress),GenerateData(binary), andServerUptime(Property). Runs as a Docker Compose service on0.0.0.0:50052with a Python TCP-socket healthcheck (the base image lacksnc/grpcurl);notebook_validator.depends_onusescondition: service_healthyto gate papermill startup. Bind failure nowsys.exit(1)(was silentlyreturn). Wired intoworkcell_nodesinexamples/example_lab/settings.yamlassila_example: sila://localhost:50052.Notebook & validation:
sila_node_notebook.ipynbexercises every supportedSilaNodeClientcapability end-to-end (introspection, observable polling, binary data round-trip with content verification, error handling). It doubles as the SiLA validation harness —just validate_nb_silaruns it via papermill against the live compose service, integrated intojust validate_notebooks. The recipe is self-sufficient (no priorjust uprequired; healthcheck gates startup).Documentation sync (post-audit):
SiLA2 Native Node Client (Experimental)subsection under[Unreleased] / Addedwith explicit experimental framing.docs/guides/node_development.md: New "Consuming SiLA2 Devices (Experimental)" section covering install, quick start, and what's not yet available.src/madsci_client/README.md: New "SiLA2 Client (Experimental)" section under Node Clients.examples/example_lab/README.md:sila_example_serveradded to node list, port 50052 to network requirements +netstattroubleshooting, TCP-socket healthcheck snippet, notebook to notebook list, and a dedicated SiLA Example Server section.pip install "madsci.client[sila]".unitelabs-silacomment insrc/madsci_client/madsci/client/node/__init__.pycorrected to reference the[sila]extra.OpenSpec history: Spec-driven design artifacts for every increment (
sila-node-client,example-sila-node,sila-client-review-fixes,sila-bytes-to-action-files,sila-client-review-cleanup,sila-client-connection-errors,sila-client-pr291-must-fixes) with proposals, designs, delta specs, and task lists. 6 capability specs synced toopenspec/specs/.110 tests (
test_sila_node_client.py) covering URL dispatch, command execution, observable polling, introspection, error handling, FAILED-status propagation, non-callable command rejection, graceful import failures, bytes serialization, ActionFiles extraction, end-to-end bytes handling, connection error classification, enriched error formatting, path-traversal sanitization,get_infoexclusion of properties and non-command callables, and layered error classification (arg errors, unknown commands, double-wrapping, recognized vs unknown execution categories).What is not in this PR (deferred)
The current preview is client-only. The following are intentionally out of scope and tracked as downstream implementation issues against the design proposal:
Sila2Nodeserver base class for authoring MADSci nodes natively as SiLA2 servers (downstream issue #A).async_*) method parity onSilaNodeClient(#B).send_admin_commanddispatch over SiLA — Lock/Cancel/Pause/Reset/Shutdown/SafetyStop (#D, #E).state_handlershim (#F), NodeStatus/NodeInfo decomposition (#G).get_log/get_action_history/get_resources(#B removesget_resourceswholesale; #H re-homes the others as MADSci Features).If you need any of these now, stay on
RestNodeClient— the migration is "side-by-side with full parity until REST removal" by design.SiLA2 Migration design proposal
This PR also lands the design-only OpenSpec change
sila2-native-node-design— the standards-exploration deliverable for issue #294. It scopes the broader SiLA2 Migration project (#293) across the seven scope areas of #294: SiLA core standards audit (adopt/extend/replace/propose-upstream for eachorg.silastandard/coreFeature), action → Command mapping, state → Properties decomposition, NodeStatus/NodeInfo evolution, file/binary handling, admin commands as a SiLA Feature (MadsciAdminController), and theAbstractNodeClientabstraction story.Includes eight resolved decisions (RD1–RD8) and a draft list of ~12 downstream implementation issues (#A–#M) to open against #293 once accepted. Notable decisions: vendor-and-PR strategy for missing standard SiLA core Features (CancelController, PauseController, SimulationController),
Sila2Nodedefaults to Observable Commands, legacy state shim namespaced underMadsciState.LegacyState.<key>(no top-level back-compat),get_resourcescapability removed wholesale, and push observability deferred to v2 (sketched but not adopted in v1).This is design-only — no spec deltas, no code. Each downstream implementation issue will land its own OpenSpec change with concrete deltas.
Test plan
pytest src/madsci_client/tests/test_sila_node_client.py— 110 tests pass (mocked SiLA SDK)pytest src/madsci_client/tests/— full client suite passes (1365 tests, no regressions)pytest src/madsci_common/tests/test_templates/test_template_engine.py— 311 pass (no template regressions)madsci-release-auditskill — CHANGELOG, docs, guides, example lab, notebooks, templates, skills all in syncjust validate_nb_sila— notebook executes against live example server via papermill (self-sufficient — no priorjust upneeded; healthcheck gates papermill startup)just validate_notebooks— no regressions in other notebook validations🤖 Generated with Claude Code