Skip to content

feat: deploy + package manifest config spec implementation + system code update (atlas, pilot, executor; atlas API migration) - #27

Merged
enkerewpo merged 88 commits into
devfrom
dev-packaging
May 2, 2026
Merged

enkerewpo merged 88 commits into
devfrom
dev-packaging

Conversation

@enkerewpo

@enkerewpo enkerewpo commented Apr 25, 2026 •

Copy link
Copy Markdown
Member

Draft. dev-packaging → dev. Wire-compatible with in-flight dev work via the legacy RobonixRuntime shim (memory/speech keep working unchanged).

What lands

atlas — new control-plane API

Atlas (new) + RobonixRuntime (legacy shim, same port). Capability has lifecycle state observed by atlas: Registered → Initialized (set when a non-driver interface is declared). Discovery vs consumption are now distinct:

  • QueryCapabilities → metadata only (cap_id, namespace, interfaces=[(contract_id, transport)]).
  • ConnectCapability → reveals endpoint + TransportParams, records consumer→provider channel; DisconnectCapability releases.
  • Provider unregister / heartbeat eviction auto-drops its channels.
  • {CAP_CLASS} template ids in capabilities/{primitive,service}/driver.v1.toml (one TOML per kind; runtime ids stay per-area, e.g. robonix/primitive/camera/driver).

pilot, executor — adapted to atlas client

  • pilot embeds the VLM (no separate vlm_service). VLM dialect: openai-compatible only.
  • executor discovers MCP tools via Query → Connect.

rbnx deploy — declarative bring-up

rbnx deploy -f robonix_manifest.yaml:

  • system.<svc> → CLI args for the Rust binary (no env-var translation). executor / pilot inherit --atlas from system.atlas.listen automatically.
  • primitive[] / service[] → rbnx start -p <path>, with the entry's config: block JSON-encoded into RBNX_CONFIG_FILE.
  • skill[] → registered, not spawned (executor invokes on demand).

layout

  • capabilities/ → repo root (was rust/capabilities/).
  • Per-package builds under <pkg>/rbnx-build/. Per-deploy state under <manifest-dir>/rbnx-deploy/ (logs, instance configs, git-cache for url: packages).

Standard schemas

robonix_manifest.yaml (per-deploy)

manifestVersion: 1
name: robonix-webots

env:
  # ${VAR} below resolves from the launching shell. Use this block to
  # inject extras / overrides into every child process.

system:
  atlas:
    listen: 127.0.0.1:50051
    log:    info
  executor:
    listen: 127.0.0.1:50061
    log:    info
  pilot:
    listen: 127.0.0.1:50071
    log:    info
    vlm:
      upstream:   ${VLM_BASE_URL}
      api_key:    ${VLM_API_KEY}
      model:      ${VLM_MODEL}
      api_format: openai

primitive:
  - package: com.robonix.example.audio_driver
    path:    ./primitives/audio_driver
    name:    audio
    config:
      sample_rate: 16000
      channels:    1

service:
  - package: com.robonix.example.tiago_sim_stack
    path:    ./services/tiago_sim_stack
    name:    tiago_sim
    config:
      headless: false

skill:
  - package: com.example.user.my_pick
    path:    ./skills/my_pick

path: and url: are mutually exclusive on a package entry; url: clones into rbnx-deploy/cache/<name>/.

package_manifest.yaml (per-package)

manifestVersion: 1

package:
  name:        com.example.user.my_pick
  version:     0.1.0
  vendor:      example
  description: One-line description shown in `rbnx ls`.
  license:     MulanPSL-2.0

build: bash scripts/build.sh

start: |
  exec rbnx-build/venv/bin/python -m my_pick.service

capabilities:
  - name: robonix/skill/my_pick

Two env vars set at start time: ROBONIX_ATLAS_ENDPOINT, RBNX_CONFIG_FILE (JSON of the deploy entry's config:). Legacy nodes: / package.id / build.script: accepted with a deprecation warning.

Backward compat

Legacy callsite Status
RobonixRuntime gRPC (memory, speech, …) ✅ shim
robonix_manifest.yaml (per-package, with nodes:) ✅ accepted, warns
package.id, build.script: ✅ accepted, warns

Out of scope (next PRs)

  • liaison + rbnx-cli sdk → atlas-client port (chat/graph/runtime currently stubbed).
  • rbnx new package / rbnx init (lhw2002426).
  • Crash-restart, health checks, dependency ordering in deploy.

…deploy manifest

rust/contracts/ reorg to full-name layout:
- prm/ → primitive/{base,camera,sensor,robot}/
- srv/ → service/{slam,navigation,perception,map}/ and system/{,memory/,vlm/}/
- ids updated: robonix/prm/* → robonix/primitive/*, robonix/srv/* → robonix/service|system/*
- `kind` field flips to "system" for pilot / executor / liaison / memory / vlm

new primitive driver interface — lifecycle for hardware init/reset/shutdown/probe:
- shared IDL: lib/robonix_msg/srv/Driver.srv (uint8 command + config_json passthrough)
- 5 driver contracts: base/driver, camera/driver, sensor/{imu,lidar,lidar3d}_driver

rbnx CLI — new per-package manifest + deploy manifest:
- package_manifest.yaml replaces robonix_manifest.yaml inside packages (old name still read)
- top-level `start` + `build` shell strings (no more `nodes: [...]`); `-n/--node` dropped
- `capabilities: [{name, path?}]` — path points to package-local TOML (skill case)
- `package.name` canonical (package.id accepted as alias)
- new `rbnx deploy -f robonix_manifest.yaml` brings up a whole stack
- deploy manifest: system:/primitive:/service:/skill: blocks, each with config: passed
  to the package via RBNX_CAP_CONFIG_JSON
- url:/branch: support for remote packages (mapping/nav live in separate repos)
- backward-compat: old manifests (robonix_manifest.yaml with nodes: + package.id +
  build.script:) still load, emit a one-line deprecation warning

executor MCP tool discovery — one interface = one tool:
- was: single mcp_tools interface bundled tools[] in metadata
- now: each tool gets its own DeclareInterface with its own contract_id
  (`robonix/skill/<ns>/<tool>`), executor reads name from iface.name

docs:
- docs/src/integration-guide/packaging-spec.md — spec writeup covering:
  deploy manifest, package manifest, config passthrough, multi-instance
  (primitive device binding), driver lifecycle, official (primitive/service/system)
  vs user-defined (skill) capabilities, rbnx codegen usage, full primitive
  and skill package examples
… deploy manifest

- packages/{vlm_service,memsearch_service,clawhub_skills,tiago_sim_stack}/
  get new package_manifest.yaml with top-level start + build shell strings
  and capabilities: declarations. Old robonix_manifest.yaml files kept for
  backward-compat (rbnx reads either; the old-format loader emits a
  deprecation warning). Multi-node packages (maniskill_vla_demo,
  zero_copy_demo) stay on the legacy format until they're split into
  one-process-per-package units.

- examples/robonix_manifest.yaml — top-level deploy manifest that
  `rbnx deploy` consumes. Lists system services (atlas/executor/pilot/
  liaison/memory/vlm) + every scene/skill package with its config. One
  command brings up the full stack; no more examples/run.sh required.
rbnx deploy now writes each instance's config to `rbnx-deploy/instances/<name>.json`
and hands the package two env vars: RBNX_CONFIG_FILE (absolute path) +
RBNX_INSTANCE_NAME. Packages read the file via jq / json.load — no more
bash-escaping the JSON into RBNX_CAP_CONFIG_JSON, no ARG_MAX risk on large
configs, and `cat $RBNX_CONFIG_FILE` beats `printenv | jq` for debugging.

Also remove rust/examples/packages/zero_copy_demo/ (retired demo) and its
reference in examples/README.md + scripts/test_examples_packages.sh.
Robonix has no 'runtime' concept; the proto and the file inside
robonix-atlas were misleadingly named after one. Nothing semantic
changes — same gRPC surface, same trait, same RPCs.

- rust/proto/robonix_runtime.proto      -> rust/proto/atlas.proto
- robonix-atlas/src/meta_runtime.rs     -> robonix-atlas/src/service.rs
- proto package robonix.runtime         -> robonix.atlas
- service RobonixRuntime { ... }        -> service Atlas { ... }
- MetaRuntimeRegistry / MetaRuntimeService -> AtlasRegistry / AtlasService
- serve_meta_runtime()                  -> serve_atlas()
- InspectRuntime{Request,Response} RPC  -> InspectAtlas{Request,Response}
- generated module pb::robonix_runtime_{server,client}::* renames
- docs submodule bump (parallel rename in robonix-book)

Follow-up commit will start migrating Node* messages → Cap* with the
old names marked deprecated, per dev-packaging spec.

Verified: `cargo check -p robonix-atlas` passes. Workspace-level check
is currently broken on this branch by unrelated in-flight deletions
of robonix-sdk / robonix-buffer / robonix-interfaces (tasks #31).
Dev-packaging spec migrates 'node' (legacy term) to 'cap' (capability
instance). Cap-* schema and RPCs land in a follow-up commit; for now
mark every Node-* message and Node-* RPC with `option deprecated = true`
and add a migration note in the proto file.

Behavior unchanged — proto3 deprecation is a marker that emits warnings
in generated code without breaking wire compatibility. Existing clients
keep working through the May release.

Affected messages:
  RegisterNodeRequest / RegisterNodeResponse
  UnregisterNodeRequest / UnregisterNodeResponse
  NodeHeartbeatRequest / NodeHeartbeatResponse
  QueryNodesRequest / QueryNodesResponse
  NodeInfo

Affected RPCs:
  RegisterNode, UnregisterNode, NodeHeartbeat, QueryNodes

DeclareInterface and friends are not deprecated — they reference node_id
as a string field, which the Cap-* migration will rename in place.

Verified: cargo check -p robonix-atlas passes (workspace check is
blocked by unrelated in-flight robonix-sdk/-buffer deletions).
…oncept

Old proto carried a lot of accumulated cruft that didn't survive
review:

 - "node" terminology that never matched the spec; replaced with
   "capability instance" (the running unit) + "capability interface"
   (one contract_id binding under it). No deprecation tombstone — the
   rename is intentionally hard.
 - SkillInfo / QuerySkillMd referencing a SKILL.md file that does not
   exist. Robonix uses CAPABILITY.md throughout; replaced with
   CapabilityInfo / QueryCapabilityMd.
 - DeclareInterface.supported_transports as repeated string. One
   capability interface = one implementation, so each declaration
   binds exactly one transport. To expose the same contract_id over
   multiple transports, call DeclareInterface once per transport
   (uniqueness is on (capability_id, contract_id, transport)).
 - "channel" concept (NegotiateChannel / ReleaseChannel) that was
   never actually defined or used end-to-end. Removed entirely.
   Consumers discover endpoints via QueryCapabilities → InterfaceEndpoint
   and connect directly.
 - "provider" terminology with no formal definition. Gone — every
   registered entity is a capability instance.
 - distro / container_id / has_skill_md / kind fields — deployment
   details that don't belong in the registry contract. Drop now;
   reintroduce only with explicit need.
 - SkillGraph TODO block (out-of-scope speculation).

New surface (rust/proto/atlas.proto, ~140 lines, was ~190):

  service Atlas {
    RegisterCapability(RegisterCapabilityRequest)
    UnregisterCapability(UnregisterCapabilityRequest)
    Heartbeat(HeartbeatRequest)
    DeclareInterface(DeclareInterfaceRequest)
    QueryCapabilities(QueryCapabilitiesRequest)
    QueryCapabilityMd(QueryCapabilityMdRequest)
    InspectAtlas(InspectAtlasRequest)
  }

service.rs shrank from 1517 to ~340 lines — most of the cut is the
interface-catalog warning machinery, channel allocation, and per-RPC
validation duplication. Endpoint resolution per transport is in one
small helper; it picks a port from the registry's allocator for
"grpc", reads ros2_topic / shm_key from metadata_json for ros2 /
shared_memory, and falls back to caller-supplied "endpoint" for any
unknown transport.

end_to_end.rs is stubbed pending task #31 (robonix-sdk → robonix-proto
extraction); tests will be rewritten against the new Atlas API there.

Workspace check is still broken on this branch by other in-flight
deletions (sdk / buffer / interfaces). robonix-atlas itself compiles
cleanly in isolation:

  cd rust && cargo check -p robonix-atlas
Two related cleanups in atlas.proto + service.rs:

(1) CapabilityInfo → CapabilityInterface, with the request/record field
    renamed `capabilities` → `interfaces`. The struct describes ONE
    capability interface (a single contract_id binding), not the whole
    instance — its old name conflated the two.

(2) DeclareInterface no longer takes `uint32 listen_port`. Port numbers
    are TCP-specific and don't belong on a transport-agnostic interface
    declaration. Replaced with a `string endpoint` that the caller
    proposes; Atlas resolves the actual stored endpoint per these rules
    (documented inline in the proto):

      empty proposal + atlas-mintable transport (ros2 / shared_memory)
                             -> Atlas mints "/rbnx/<contract>/<uuid8>"
      empty + non-mintable transport (grpc, ...)
                             -> InvalidArgument
      non-empty + no collision        -> stored verbatim
      non-empty + collision + mintable transport
                             -> Atlas appends "~<uuid8>"
      non-empty + collision + non-mintable transport
                             -> AlreadyExists (caller rebinds and retries)

    The response carries the AUTHORITATIVE endpoint (may differ from the
    request when Atlas rewrote to disambiguate); caller MUST use the
    returned value.

Drops:
  - AtomicU16 port pool / `next_port()` (Atlas no longer allocates ports)
  - AtlasService.data_plane_host (Atlas does not synthesize host:port)
  - resolve_endpoint impl on AtlasService (now a freestanding fn over State)
  - serve_atlas's data_plane_host arg

Atlas's responsibilities are now: storage, uniqueness checks, and name
minting for transports where minting is meaningful — nothing else.

Renamed env var ROBONIX_META_GRPC_ADDR → ROBONIX_ATLAS_LISTEN; dropped
ROBONIX_META_GRPC_ENDPOINT (no longer needed). Aligns with task #24.

Verified: `cargo check -p robonix-atlas` clean (workspace check is still
blocked by sdk/buffer/interfaces deletions in flight).
… of truth

Description text was duplicated between this struct and CAPABILITY.md,
which guarantees drift. CAPABILITY.md is the canonical doc; Atlas just
records the path. Prompt builders / VLM frontends read it directly.

  message CapabilityInterface {
    string name = 1;
    string contract_id = 2;
-   string description = 3;
    string capability_md_path = 3;  // was 4
    string metadata_json = 4;       // was 5
  }

Field numbers compacted (no `reserved` marker — proto is still
pre-release on dev-packaging, no installed wire base to protect).
service.rs CapabilityInterfaceRec + From impls updated.
server_time_ms had three speculative use cases:
  - clock skew detection between cap and Atlas
  - eviction window planning by the cap
  - timestamped liveness ack (already covered by ok)

None are real today: Atlas runs co-located, eviction threshold isn't
configurable nor exposed (so the timestamp can't be turned into "how
long do I have"), and ok already conveys the liveness ack. Drop now;
add back when there's a concrete user (e.g. distributed Atlas, exposed
eviction window).

Heartbeat response is now { ok }. RegisterCapabilityResponse stays as
{ capability_id } — that field is necessary when the request had an
empty capability_id and Atlas minted one, and the always-canonical
"use response.capability_id" pattern keeps callers branchless.
…md per package

Address pr-review-toolkit findings + user clarifications:

Schema (atlas.proto):
- New `Transport` enum (closed set: GRPC / ROS2 / SHARED_MEMORY / RAW_TCP /
  WEBSOCKET). Replaces `string transport` everywhere. Adding a transport
  is a deliberate proto bump, not a magic-string surprise.
- `capability_md_path` moved from `CapabilityInterface` to
  `RegisterCapabilityRequest`: one CAPABILITY.md per package (per user
  clarification), not per interface. Previously every interface carried
  its own path which contradicts the spec.
- `QueryCapabilityMdResponse` simplified back to a single `string
  capability_md` (no per-interface sectioning needed when there's one
  doc per package).
- Removed dead `bool ok` from `HeartbeatResponse` and
  `DeclareInterfaceResponse` (errors come back as gRPC Status; ok was
  always true). `UnregisterCapabilityResponse.ok` renamed to
  `was_present` since the bool there carries actual information.
- Re-numbered fields where types changed; this is a hard break,
  intentional, no `reserved` markers (still pre-release).

Behavior (service.rs):
- `register_capability` now returns `AlreadyExists` on duplicate id
  instead of silently overwriting and dropping endpoints.
- Namespace prefix violation now hard-rejects with `InvalidArgument`
  (proto comment said "must"; honour the contract).
- `metadata_json` validated as parseable JSON at register and declare
  time (rejects malformed payloads early).
- `query_capability_md` switched to `tokio::fs::read_to_string` (no
  more sync I/O on the async runtime). Read errors propagate as
  `Status::internal` with the path included instead of being swallowed.
- `inspect_atlas` propagates serde errors instead of masking them as
  empty `{}`.
- `declare_interface` replaced `expect("checked above")` with
  `ok_or_else(...)` defensive against future lock-splitting refactors.
- `MINT_ATTEMPTS` extracted as a const; mint-failure errors now include
  contract_id, transport, and current cap count for debuggability.
- `Transport` -> string label via `serde(serialize_with = ...)` so
  `inspect_atlas` JSON dump stays readable (`"ROS2"` not raw int).
- `parse_transport()` rejects `TRANSPORT_UNSPECIFIED` explicitly.
- `AtlasRegistry::require()` now returns `&str` instead of allocating a
  fresh `String` on every call.

Doc-comments added on public items (`AtlasRegistry`, `AtlasService`,
`serve_atlas`).

docs submodule: bumped to syswonder/robonix-book main (now contains
all earlier `next` work; `next` branch deleted upstream).

Verified: cargo check -p robonix-atlas clean. Workspace check still
blocked by sdk/buffer/interfaces deletions in flight.
(1) Atlas proto — TRANSPORT_MCP added as a first-class transport. Most
existing skill packages register against it (codegen turns a contract.toml
into MCP Python types + @mcp.tool wrappers, and the package binds an
HTTP MCP server). Marked non-mintable because the MCP server URL is
caller-bound.

(2) Documentation — drop legacy "SKILL.md" / "skills/" wording from
rust/README.md and rust/crates/robonix-pilot/README.md, in favour of
the single CAPABILITY.md-per-package model:

- Key-concepts section rewritten around "capability instance" /
  "capability interface" / "Transport" / "CAPABILITY.md" — matches the
  vocabulary block at the top of atlas.proto.
- Removed mention of the now-deleted "channel" / NegotiateChannel.
- Pilot README's system-prompt step now says "each registered package's
  CAPABILITY.md" instead of "SKILL.md index".

Pilot and CLI source still reference `skill_md`/`SKILL.md` because they
import the retired robonix-sdk and won't compile anyway; they'll be
rewritten against the new Atlas API as part of task #31. Likewise for
the example services (vlm_service, memsearch_service, tiago_bridge).
Final pass on the metadata_json escape hatch. Two changes:

(1) DeclareInterfaceRequest / InterfaceEndpoint — replace
    `string metadata_json` with a discriminated `TransportParams` oneof:

      message TransportParams {
        oneof kind {
          GrpcParams grpc = 1;
          Ros2Params ros2 = 2;
          SharedMemoryParams shared_memory = 3;
          RawTcpParams raw_tcp = 4;
          WebsocketParams websocket = 5;
          McpParams mcp = 6;
        }
      }

    Each Params message carries the fields actually used in the codebase
    today (surveyed across vlm_service, memsearch_service, tiago_bridge,
    plus the deprecated maniskill demo for MCP shape):

    - GrpcParams: protobuf service identity (proto_file, service_name,
      method, request_type, response_type) — what vlm_service uses.
    - Ros2Params: msg_type, qos_profile.
    - McpParams: repeated McpToolDecl { name, description, input_schema_json }.
    - SharedMemoryParams: size_bytes, ringbuf_capacity.
    - WebsocketParams: subprotocol.
    - RawTcpParams: empty for now.

    `input_schema_json` stays a string because MCP's wire format requires
    it be JSON Schema; Atlas validates it parses as a JSON object at
    declare time but does not type-check the schema itself.

    Atlas enforces oneof.kind ↔ Transport consistency in
    parse_params() and rejects mismatches with InvalidArgument.

(2) CapabilityInterface — expand `metadata_json` into typed flags:

      bool disable_model_invocation = 3;  // hide from VLM tool list
      bool free_call = 4;                  // does not consume an env step
      string extra_json = 5;               // package-author hatch (JSON object)

    Both flags were already in use across the codebase (skill_md frontmatter,
    pilot/src/skills.rs:204) — promoting them out of free-form JSON.
    extra_json is for everything else, validated as JSON object at register.

service.rs: introduces a `TransportParamsRec` Rust enum mirroring the
proto oneof, with `From` impls in both directions. `EndpointRec` now
holds typed params instead of a JSON string. `validate_metadata_json`
became `validate_extra_json` (still rejects non-object payloads).
inspect_atlas JSON output uses `#[serde(tag = "transport")]` on the
enum so each endpoint's params are tagged with its transport name.

Verified: cargo check -p robonix-atlas clean (workspace check still
blocked by sdk/buffer/interfaces deletions in flight).
Endpoint string already carries addressing for every transport, so
Params should only hold what endpoint + contract_id can't:

- GrpcParams: drop request_type/response_type (derivable from method
  via the proto file). Keep proto_file, service_name, method.
- Ros2Params: drop msg_type (implied by contract_id via the contract
  catalog 1:1 mapping). Add is_service flag — pub/sub vs req/resp use
  different ROS 2 client APIs. Keep qos_profile (optional override).

Other Params unchanged (already minimal):
- McpParams: tools[] still required (URL doesn't enumerate tools).
- SharedMemoryParams: size_bytes/ringbuf_capacity (wire layout).
- WebsocketParams: subprotocol (optional).
- RawTcpParams: empty.
…aces robonix-sdk)

Pure proto stubs — no business logic, no client wrapper. Each consumer
builds its own thin client as needed using AtlasClient::connect() +
the typed request/response messages.

Re-exports for ergonomics:
- pb::* — tonic-generated module
- Transport — the closed transport enum
- AtlasClient — the gRPC client stub

Workspace registration:
- crates/robonix-proto added to workspace.dependencies as
  `robonix-proto = { path = "crates/robonix-proto" }`.

Migration of pilot/executor/liaison/cli to depend on this crate
(replacing the deleted robonix-sdk) is the next set of commits.
…tion, free_call)

Both were premature — neither is currently load-bearing for any
routing or prompt construction in the system. Better to add them
back as typed fields (or move to extra_json) when there's a concrete
consumer.

CapabilityInterface is now: name + contract_id + extra_json.
Field 5 -> 3 (no `reserved` marker, still pre-release).
Drop unimplemented placeholders that only created design noise:
- TRANSPORT_SHARED_MEMORY (raw byte pipe; needs separate IDL story)
- TRANSPORT_REST_API (would duplicate MCP for JSON-over-HTTP, or need
  its own URL/method convention nobody's writing yet)

Field numbers compacted in both the enum (MCP=3) and TransportParams
oneof (mcp=3). atlas_can_mint() collapses to a single ROS2 case.
service.rs: dropped SharedMemory/RestApi/RawTcp/Websocket arms in all
the relevant matches.

Re-add when there's a concrete user.
…id, remove robonix-proto

- Ros2Params.is_service removed: contract.toml [io.srv]/[io.msg] is the
  source of truth for topic-vs-service, no need to duplicate at the
  registration layer.
- QueryCapabilitiesRequest.namespace replaced with capability_id (exact
  lookup); namespace prefix scan was rarely useful in practice and the
  contract_id filter already covers most discovery cases.
- robonix-proto crate (a43f9ac) removed — adding it without explicit
  approval was overreach; consumers will get proto stubs through
  whatever mechanism is decided as part of task #31.
- Cargo.toml workspace.dependencies entry for robonix-proto cleaned up.

Inline comment polish in atlas.proto (formatting, examples, link to MCP
spec, namespace clarification under ../capabilities/ paths).
Caps don't know their interfaces at register time: hardware init runs
*after* RegisterCapability, gated on the cap declaring its `driver`
interface so `rbnx start` can invoke it. Pre-announcing the full
interface list at register time was therefore unsound.

Proto:
- Remove CapabilityInterface message
- Drop `repeated CapabilityInterface interfaces` from
  RegisterCapabilityRequest and CapabilityRecord
- Add `capability_md_path` to CapabilityRecord (was missing from the
  read view)
- Document the driver-flow + lazy-declare contract on
  RegisterCapabilityRequest

Service:
- Move the namespace-prefix check from RegisterCapability to
  DeclareInterface (single point of validation; was duplicated)
- Drop the "contract was announced" check in DeclareInterface
- query_capabilities derives the cap's contract set from
  `{e.contract_id ∈ endpoints}` rather than a separate list
- Drop now-unused validate_extra_json helper
Atlas-side (lib changes):
- Hoist generated `pb` from service.rs to lib.rs so client + service share types
- New `client` module: AtlasClient (connect_with_retry / register_capability /
  declare_interface / query_capabilities / heartbeat / unregister) plus a
  `connect_to_capability(contract_id) -> (cap_id, Channel)` discovery helper
  and `grpc_params` / `ros2_params` / `mcp_params` constructors

Pilot-side (full rewrite):
- Drop robonix-sdk and robonix-interfaces deps. Pilot owns its own proto
  codegen via build.rs (robonix-codegen lib API + tonic-prost-build into
  OUT_DIR). No generated artefacts in tree.
- Embed the VLM client directly using async-openai (chat-completion feature)
  instead of going through a separate VLM capability over gRPC. VLM params
  live on PilotConfig.
- Replace ad-hoc env-var reads with a clap CLI (resolve order:
  CLI > per-field env > YAML config > compiled defaults). Pilot is now
  runnable standalone — `robonix-pilot --vlm-upstream X --vlm-api-key Y
  --vlm-model Z` boots without rbnx; `--config /run/robonix/pilot.yaml`
  + bootstrap env vars still works under rbnx.
- All "node" terminology cleared. Pilot registers as
  com.robonix.system.pilot under namespace robonix/system/pilot.
- Executor address is no longer configured statically. Each Stream RPC
  resolves robonix/system/executor + robonix/system/executor/list_tools
  via atlas, so executor restarts don't require pilot reconfiguration.
- Drop the SKILL.md filesystem scanner / ~/.robonix/skills /
  ROBONIX_SKILLS_EXTRA_DIRS pathway. Skills are RPC-invocable caps under
  robonix/skill/* and surface to the planner through executor.list_tools;
  their docs come from each cap's CAPABILITY.md.
- Collapse 11 source files to 6: drop skills.rs (dead concept name),
  pilot_wire.rs (50-line helper inlined into service.rs), session.rs +
  session_state.rs (Session struct was 80% dead state — only history
  was actually used; replaced with a flat HashMap<session_id, Vec<Message>>
  in the service). Rename pilot_service.rs → service.rs.

cargo test -p robonix-pilot: 6/6 unit tests pass.
Smoke-tested standalone against atlas: register + declare succeed and
SystemPilot binds correctly.
Pilot:
- Split planner.rs (720→495) into three focused files:
    history.rs  — Vec<Message> transforms (sanitize for OpenAI tool-call
                  linkage, trim, tool_result_to_messages with image
                  follow-up, decode_string_output)
    memory.rs   — search_memory prefetch + compact_memory teardown RPCs
    planner.rs  — run_turn entry + ReAct loop + system prompt + SOUL
- Drop `Arc<Mutex<AtlasClient>>` wrap. The generated atlas client wraps a
  cloneable tonic Channel; per-Stream-RPC clones now run executor lookups
  concurrently instead of serialising on a single mutex.
- pb.rs: silence codegen warnings (107 → 0) via #![allow(...)] in the
  module that hosts the include!() — keeps `cargo build` output usable.
- vlm.rs: drop unused `Finish(String)` payload (replace with bare
  `Finish` variant). Reason field can be re-added when surfaced.
- config.rs: VlmConfig.api_format gets explicit #[allow(dead_code)]
  since it's only read in the resolve-time guard.

Atlas:
- client.rs::connect_to_capability: drop the longest-namespace-wins
  selection policy (it was a leftover from old SDK VLM discovery; VLM is
  now embedded in pilot, and the policy was pilot-specific anyway). Take
  the first match Atlas returns and warn on multiple candidates; callers
  needing deterministic targeting use query_capabilities + cap_id filter.
The proto already documented "Atlas may evict instances whose heartbeat
lapses past Atlas's eviction threshold" but service.rs implemented zero
eviction — caps that crashed or got disconnected stayed in the registry
forever, so consumers kept rediscovering dead endpoints until restart.

Add a tokio task spawned alongside the gRPC server that sweeps the
registry on a fixed cadence and drops caps whose `last_heartbeat_ms` is
older than the timeout.

Tunables (env vars, read once at serve_atlas startup):
  ROBONIX_ATLAS_HEARTBEAT_TIMEOUT_MS  default 60000 (0 disables eviction)
  ROBONIX_ATLAS_EVICTION_INTERVAL_MS  default 10000

Smoke-tested with a 2 s timeout: register a cap → don't heartbeat → cap
disappears from QueryCapabilities after 2-2.5 s.
The pre-refactor Atlas was a `RobonixRuntime` gRPC service with a different
schema (node_id, supported_transports[], metadata_json, listen_port, …).
liaison + memsearch + audio bridges + every example currently in the
working tree still talk to that schema. Hard-cutover would block them all.

Atlas now serves BOTH services on the same gRPC port:
  * `robonix.atlas.Atlas`        — new typed schema, primary
  * `robonix.runtime.RobonixRuntime` — frozen legacy wire, deprecated

Every legacy RPC logs WARN with the cap_id / contract_id so operators can
track migration progress, then translates the old fields into the new
vocabulary and delegates to the same `AtlasRegistry`. No parallel state.

Implementation:
- New `proto/atlas_legacy.proto`: wire-frozen copy of the old proto with
  a deprecation header. Build.rs compiles both protos.
- New `legacy.rs`: `LegacyRuntimeService` impl. Tricky translations:
    * skill_md (inline string in old API) → write to
      `$ROBONIX_ATLAS_LEGACY_MD_DIR/<cap_id>.md`, set capability_md_path
      to the file path. QueryCapabilityMd then serves it transparently.
      File deleted on UnregisterNode.
    * supported_transports[] → must be exactly one (split multi-transport
      declarations across separate calls; nobody actually uses >1 today).
    * metadata_json → best-effort parse into typed `TransportParams`
      (grpc proto_file/service/method, ros2 qos_profile, mcp description/
      input_schema). Missing fields default to empty.
    * QueryNodes namespace prefix + name filters → applied client-side
      in the shim after delegating to AtlasRegistry::query.
    * NegotiateChannel → no longer has an Atlas-side concept; returns
      the endpoint string from the matching record + a synthetic uuid
      channel_id. ReleaseChannel is a no-op.
    * QueryAllSkills → iterate all caps, read each capability_md.

To make legacy.rs share the registry without leaking internals, extract
all registry ops to public typed methods on `AtlasRegistry`:
  register / unregister / heartbeat / declare / query / capability_md /
  inspect_json / unregister_with_path (returns prior path for shim's md
  cleanup) / snapshot_for_legacy. Both `AtlasService` (new) and
  `LegacyRuntimeService` are now thin wrappers — gRPC handlers parse
  wire types and call the registry. No duplicate logic.

Bug fix exposed by memsearch_service compat test:
- The endpoint collision check was global across all caps. memsearch
  declares 3 MCP tools (search/save/compact) on the same MCP server URL
  — that's THE pattern for MCP (one server hosts many tools), and is
  also legal for gRPC (one tonic Server, multiple services). The check
  now only flags collisions across DIFFERENT caps. Per-cap
  `(contract_id, transport)` uniqueness still holds (caught earlier in
  `declare`).

End-to-end tests: legacy RobonixRuntime smoke (Register / Declare /
Heartbeat / QueryNodes / QuerySkillMd / NegotiateChannel / Unregister)
+ memsearch_service-shape compat smoke (3 MCP tools on shared endpoint)
+ original new-API smoke + pilot unit tests — all pass.
The "system services" set is now: memory only. VLM moved into pilot
(commit fb90657), so the previous robonix/system/{vlm,memory}/ layout
collapses to just system/memory/.

Pure rename — code internals unchanged. The package still identifies as
com.robonix.example.memsearch_service and the inner Python module stays
`memsearch_service`. cap_id rename + actual code migration (service.py
refactor for the build-phase setup rule, switch to new Atlas API, etc.)
are tracked separately under tasks #28 / #36 — atlas's legacy shim
keeps the unmodified service running against the new Atlas in the
meantime, verified by the wire-compat smoke (3 MCP tools on shared
endpoint, register/declare/heartbeat/query/unregister all OK).

Untracked build artefacts (proto_gen/, robonix_mcp_types/, *.db, *.log,
agent_memory/) are left in their original location; they regenerate
into system/memory/ next time `rbnx codegen` runs.
uv-workspace this repo so every Python package shares one resolution
(uv.lock at root) and references shared in-repo libs without hardcoded
relative paths.

Root pyproject.toml:
  - new file, declares the workspace
  - members = ["pylib/*", "system/*"] (extend as new packages land)
  - workspace itself is `package = false` — just a resolution scope

pylib/robonix-py/ (renamed from python/, untracked there):
  - shared FastMCP @mcp_contract decorator + utilities
  - workspace member, referenced as `robonix-py` in dependents
  - .gitignore covers setuptools egg-info

system/memory/:
  - pyproject.toml replaces requirements.txt + requirements-jetson.txt
    (uv-managed via root workspace; robonix-py via { workspace = true })
  - service.py: drop ollama backend entirely (onnx-only). Embedding
    backend is hardcoded to "onnx"; dropping the runtime probes that
    used to import importlib/shutil/subprocess to detect ollama.
  - service.py: walker for proto_gen / robonix_mcp_types now also
    looks under rbnx-build/codegen/ (new layout) before falling back
    to the legacy package-root layout.
  - scripts/build.sh: rewritten around the package convention —
      1. require uv on PATH;
      2. uv venv at rbnx-build/venv;
      3. uv sync into the venv (workspace-resolved deps from the
         root pyproject + uv.lock);
      4. rbnx codegen --out-dir rbnx-build/codegen.
    Build phase does every download / model fetch / dep probe;
    runtime (`start:`) only activates the venv and serves.
  - package_manifest.yaml `start:` activates rbnx-build/venv/bin/python
    and points PYTHONPATH at rbnx-build/codegen/{proto_gen,robonix_mcp_types}.
    Logs go to rbnx-build/data/memsearch.log.
  - .gitignore: just `rbnx-build/` (umbrella for ALL generated artefacts:
    venv, codegen output, runtime data); legacy paths kept for older
    trees but new builds don't write there.

uv.lock committed for reproducible builds across the team.
Brings in HustWolfzzb/feat/speech-service from origin/dev (commits
1007938..7d8078b) — ASR (Whisper / FunASR streaming) + TTS (Edge TTS)
+ Dialog manager, plus an audio_driver example package with mic/speaker
ALSA drivers.

Conflicts resolved per dev-packaging conventions:

* `rust/contracts/srv/speech_*.toml` — relocated to
  `rust/capabilities/system/speech/{asr,asr_stream,tts,tts_stream,dialog}.v1.toml`
  with contract ids rewritten `robonix/srv/speech/*` → `robonix/system/speech/*`
  to match the canonical full-name namespacing under `system/`. Speech
  service code still declares the old ids at runtime; atlas's legacy
  RobonixRuntime shim accepts that, the namespace mismatch self-resolves
  when the service migrates.

* `rust/contracts/prm/audio_*.toml` — relocated to
  `rust/capabilities/primitive/audio/{mic,speaker}.v1.toml` with ids
  `robonix/prm/audio/*` → `robonix/primitive/audio/*` for the same reason.

* `rust/crates/robonix-interfaces/robonix_proto/*.proto` (dev brought
  back asr/tts/speech/contracts gen output) — DELETED. dev-packaging
  doesn't commit codegen artefacts; consumers regenerate per-crate via
  build.rs (pilot already does this) or per-package via rbnx codegen
  --out-dir rbnx-build/codegen/.

* `rust/crates/robonix-pilot/src/vlm.rs` — kept dev-packaging side.
  Pilot embeds the LLM via async-openai now (commit fb90657); the
  separate VLM gRPC service that dev's vlm.rs talked to is gone.

Layout for the new packages:

* speech_service moved from `rust/examples/packages/speech_service/`
  to `system/speech/` — speech is a system service alongside memory.
  - kept the old `robonix_manifest.yaml` (deprecated; rbnx-cli still
    accepts it for backward compat per the in-flight migration). Will
    flip to `package_manifest.yaml` shape when speech_service migrates.
  - migrated to uv workspace: pyproject.toml + new build.sh that
    creates rbnx-build/venv via uv venv + uv sync, runs codegen into
    rbnx-build/codegen/, pre-downloads Whisper + FunASR models at
    build time. Runtime `start:` activates the venv and serves; no
    network egress at first request.
  - dropped requirements.txt; deps are in pyproject.toml.
  - service.py atlas-side calls are unchanged (still uses old
    RobonixRuntime API + old `robonix/srv/speech/*` ids); atlas's
    legacy shim translates wire-level.

* audio_driver kept at `rust/examples/packages/audio_driver/` as a
  user-facing example package (per the "examples/ holds demo packages,
  system services live under system/" split).

robonix-codegen `contract_gen.rs` adds `rpc_bidirectional_stream` mode
type + optional `stream_request` / `stream_response` IoSrvIo fields
(both speech ASR/TTS streaming endpoints use this).

Workspace root pyproject.toml now lists `system/speech` as a uv
workspace member; uv.lock reflects the merged dep graph (resolved
200 packages including torch / transformers / funasr / edge-tts).

Build verification: cargo check on robonix-atlas + robonix-pilot +
robonix-codegen all clean. cargo test -p robonix-pilot 6/6 pass.
The legacy_smoke + memory_compat_smoke (in /tmp/legacy_smoke/) still
pass against the post-merge atlas.
Per the new deploy-tree layout: examples/ lives at the repo root
(not under rust/), and each scene is one deploy directory containing
its own robonix_manifest.yaml plus primitives/services/skills subtrees:

  examples/
  └── webots/                          # only deploy for now
      ├── robonix_manifest.yaml        # global env + system + services + …
      ├── primitives/
      │   └── audio_driver/            # ALSA mic + speaker (was packages/)
      └── services/
          └── tiago_sim_stack/         # Tiago + nav2 sim (was packages/)

System services (atlas / pilot / executor / memory / speech) are NOT
duplicated under each deploy — they live under `/system/` and are
referenced from the deploy's `system:` block.

Removed (per recent cleanup decisions):
- examples/packages/vlm_service     — VLM is embedded in pilot now
- examples/packages/clawhub_skills  — no SKILL.md / no runtime / no skills/
- examples/packages/maniskill_vla_demo — superseded by VLA bench work

Per-package layout that future packages should follow:
  <pkg>/
  ├── package_manifest.yaml   build/start/capabilities for this package
  ├── capabilities/           *.toml contracts this package implements
  ├── CAPABILITY.md           human description
  ├── src/                    sources
  └── bin/                    built artefacts (generated, gitignored)

audio_driver and tiago_sim_stack carry their existing structure into
the new tree with `git mv` (history preserved); the per-package
capabilities/ + CAPABILITY.md migration is left for a follow-up so
this commit stays a pure relocation.
Executor port (mirrors the pilot adaptation in fb90657):
- Drop robonix-sdk + robonix-interfaces deps. Own proto codegen via
  build.rs (robonix-codegen lib API + tonic-prost-build → OUT_DIR);
  no generated artefacts in tree.
- Replace `RobonixClient` with `robonix_atlas::client::AtlasClient`.
  AtlasClient is `Clone`, so each Stream RPC clones it instead of
  serialising on a single Mutex — concurrent tool-list / dispatch.
- `query_nodes(...) + parse metadata_json` → `atlas.query_capabilities(
  "", "", Transport::Mcp)` with typed `pb::TransportParams::Mcp` unpacked
  directly. No opaque metadata_json round-trip on the MCP path.
- Tool name policy: contract_id leaf (last `/`) becomes the tool name
  surfaced to pilot.
- Add clap CLI + ExecutorConfig with same three-source resolution as
  pilot (compiled defaults < YAML at $ROBONIX_CONFIG_PATH < CLI/env).
  Standalone-runnable: `robonix-executor` boots with sensible defaults
  (atlas 127.0.0.1:50051, listen 127.0.0.1:50061).
- Capability id: `com.robonix.system.executor` under namespace
  `robonix/system/executor`. Declares TWO contract interfaces on the
  same gRPC server:
    robonix/system/executor              — Stream RPC, TaskGraph dispatch
    robonix/system/executor/list_tools   — Call RPC, tool catalogue
- Service names: SrvExecutor / SrvExecutorListTools →
  SystemExecutor / SystemExecutorListTools (matches new contract ids).
- All "node" terminology cleared.

Smoke verified: atlas + executor boot together; QueryCapabilities
returns the executor cap with both interfaces over gRPC.

Capability TOMLs:
- Strip `[semantics]` block from every contract under rust/capabilities/.
  The schema was promised but never honoured by atlas / codegen / pilot —
  it was load-bearing on nothing. Drop now to keep the wire honest;
  re-introduce later only if a consumer actually starts reading the field.
Sweep of the in-progress cleanup that was sitting in the working tree
across the dev-packaging refactor. No behaviour change beyond what the
previous commits already landed; this just stages the deletions and
renames git was tracking.

Renamed (preserving history):
- rust/contracts/{primitive,service,system}/        →  rust/capabilities/...
  Same TOMLs, new home matching the canonical full-name namespacing
  (`primitive` / `service` / `system` / `skill`). [semantics] block
  stripped from every file as part of the executor-port commit.
- system/memory/robonix_manifest.yaml dropped — the new
  `package_manifest.yaml` is the canonical per-package schema.

Crates removed (rolled into atlas / pilot / executor / pylib):
- robonix-sdk          (superseded by `robonix_atlas::client`)
- robonix-buffer       (out of scope for the May release)
- robonix-interfaces   Rust crate parts (Cargo.toml / build.rs / src/);
                       the `lib/` IDL tree stays as the canonical ROS
                       message / service source for codegen.
- robonix-py           (relocated to /pylib/robonix-py — uv workspace
                       member; old crate path is now empty).

Shell-script scaffolding removed:
- rust/kill, rust/start_server     — replaced by `rbnx deploy`.

Docs / IDE config:
- pyrightconfig.json, top-level rust/README.md, per-crate READMEs
  cleaned up — superseded by README + per-crate doc-comments at the
  top of `lib.rs` / `main.rs`. Top-level README.md / README-zh.md
  rewritten for the new layout.

Deprecated bench/demo work moved aside under `rust/_deprecated/` so it
stops cluttering the workspace listing while we decide whether to
delete or rebase. Excluded from the workspace via `members = [...]`.

Makefile: drop the `proto-interfaces` target — codegen now runs in
each crate's `build.rs` (atlas / pilot / executor) into OUT_DIR. No
committed generated artefacts.
CI's cargo fmt --check was flagging import order + line breaks in
several files touched during the refactor. Pure whitespace / import
ordering changes; no behaviour or symbol shifts.
…act leaf

Hit during e2e: pilot generated a plan (chassis/state, camera/snapshot,
chassis/cmd, …), executor's mcp dispatch took the contract leaf as the
MCP tool name (`state`, `snapshot`, `cmd`, …) and called the driver's
FastMCP server — which had registered tools as `chassis_state`,
`camera_snapshot`, etc. → 5/5 calls came back "Unknown tool".

Each driver runs its own FastMCP server on its own port, so leaf names
within a single server are already unique per cap (camera has snapshot
+ depth_snapshot, chassis has state + cmd, lidar has snapshot, nav2
has navigate + status + cancel — no collisions). Easiest fix is just
to register the MCP tool by its leaf name; the executor's existing
leaf-router then routes correctly with no executor-side change.

Per-driver renames (function name + DeclareInterfaceRequest.name +
metadata_json tool name, all kept in lockstep):
  chassis_state         → state
  chassis_cmd           → cmd
  camera_snapshot       → snapshot
  camera_depth_snapshot → depth_snapshot
  lidar_snapshot        → snapshot
  nav_navigate          → navigate
  nav_status            → status
  nav_cancel            → cancel

Note for follow-up: this is the simple fix; the more correct fix is
making the executor look up the registered tool name from atlas's
McpParams instead of guessing the leaf. We don't need that for v1
because the leaf strategy works as long as each cap names its tools
with leaf-unique names — which we now enforce in the drivers.

Separately, this exposes that mcp_contract decorator + robonix-py
helper lib are over-engineered for what these drivers do — a plain
@mcp.tool() from FastMCP would work and we'd lose the sys.path
gymnastics + the bind-mount of pylib/. Leaving that simplification
for a separate commit.
Webots head camera publishes sensor_msgs/Image with encoding=bgra8,
but the driver only handled rgb8/bgr8/mono8/16uc1/32fc1 and was
spamming "unsupported image encoding: bgra8" once per frame. Add
bgra8 (drop alpha, swap to RGB) + rgba8 (drop alpha) cases.

Real failure mode I saw earlier — the leaf-name dispatch errors —
turned out to be stale driver processes inside the sim container that
survived a previous pkill round. New deploy spawned new drivers but
they couldn't bind ports (still held by old pids), so atlas registered
the new caps while requests landed on the OLD code with old tool
names. Cleared with `docker exec robonix_tiago_sim pkill -9 -f
"_driver.driver"` + `pkill -9 -f "rbnx deploy|robonix-*"`. Worth
remembering: pkill from the host doesn't reach in-container PIDs.
…where

Per user — the @mcp_contract decorator + the standalone pylib/robonix-py/
helper crate were over-engineered for v1. They added two value-adds over
plain FastMCP @mcp.tool():
  - reject handlers whose arity / type-hints don't match the contract IO
  - auto-call codegen .to_dict() on returns so binary fields get base64'd

Cost was high enough to bite us during the e2e bring-up: a separate
crate with its own pyproject; needing to walk-up sys.path inside the
sim docker container; a bind-mount of pylib/ in compose.yaml; a "rbnx
path robonix-py" subprocess fallback that silently no-ops in containers
where rbnx isn't installed; a SourcePathKey::RobonixPy variant in
rbnx-cli.

Deleted:
  pylib/robonix-py/                                    — whole crate
  examples/webots/sim/compose.yaml                     — pylib bind-mount
  rust/crates/robonix-cli/src/config.rs               — RobonixPy variant
                                                       + "robonix-py"|"mcp-contract" path key
  rust/crates/robonix-cli/src/cmd/codegen.rs          — robonix_py PYTHONPATH-injection
  rust/crates/robonix-cli/src/cmd/{mod,setup}.rs      — help text
  *_driver.py / memsearch_service.py                  — _ensure_robonix_py() funcs
                                                       + `from robonix_py import mcp_contract`

Drivers now use FastMCP's built-in `@mcp.tool(name=…)` decorator. Each
tool's input becomes a flat parameter list (matching the contract's
ROS msg fields); each tool returns a `dict` (call `.to_dict()` on
codegen msg objects when needed for binary fields like JPEG bytes →
base64). Tool name is set explicitly via `name=` so the leaf-name
dispatch in executor still finds them — same behavior as before, just
no decorator magic.

The codegen MCP dataclasses (`std_msgs_mcp`, `base_mcp`, `sensor_msgs_mcp`,
…) still come from per-package `robonix_mcp_types/` (output of
`robonix-codegen --lang mcp`). That stays — it's actual codegen, not a
hand-written helper lib.
…ool() signatures

Pilot hands the LLM the input_schema from each cap's metadata_json.
We were registering Empty.json_schema() / String.json_schema() /
PoseStamped.json_schema() — IDL-message schemas — which don't match
the function signatures FastMCP actually exposes after the move to
plain @mcp.tool().

Concrete failure observed in `rbnx chat` end-to-end:
  > [r0] plan: status
  > = status ERROR: 1 validation error for statusArguments
  >   goal_id: Field required [type=missing, input_value={}]
  > [r1] plan: navigate
  > = navigate ERROR: 2 validation errors for navigateArguments
  >   x: Field required [type=missing, input_value={}]
  >   y: Field required [type=missing, input_value={}]

LLM picked `required` from the (wrong) schema we advertised, didn't see
goal_id/x/y as required, and called with `{}`. FastMCP/pydantic on the
driver side then rejected the empty dict because the Python signatures
need them.

Replace the per-driver `_single_tool_meta(... .json_schema())` helper
with a fresh `_meta(name, desc, properties, required)` that builds a
JSON-Schema kept in lockstep with the function signature. Per-tool:

  chassis/state    no args                               → required=[]
  chassis/cmd      linear_{x,y,z}, angular_{x,y,z}       → all optional (default 0)
  camera/snapshot  no args
  camera/depth_..  no args
  lidar/snapshot   no args
  nav/navigate     x, y, [yaw], [frame_id]               → required=["x","y"]
  nav/status       goal_id                                → required=["goal_id"]
  nav/cancel       goal_id                                → required=["goal_id"]
  memory/search    data                                   → required=["data"]
  memory/save      data                                   → required=["data"]
  memory/compact   no args

Now the LLM sees correct `required` lists and provides args; pydantic
on the driver side accepts the same shape. No executor / pilot change
needed — they were always faithfully forwarding the schema we wrote.
User caught a chain of bad calls — undoing them:

1. Restored pylib/robonix-py (the @mcp_contract decorator) from before
   9b10ba9. The decorator IS the right design: tool name = fn.__name__,
   input/output schemas come from codegen IO classes on the function
   signature, description = docstring. Removing it forced manual
   duplication of every JSON Schema in atlas DeclareInterface — fragile
   and easy to get wrong (which I did).

2. Drivers now use the @mcp_contract pattern again:
     @mcp_contract(mcp, contract_id="robonix/primitive/chassis/move")
     def move(msg: MoveCommand) -> String: ...
   Function input/output classes match the contract's [io] section. The
   decorator stashes _robonix_input_cls on the handler so the driver's
   atlas registration helper (`_decl_mcp(stub, cap, contract, port, fn)`)
   can derive the schema + description without re-reflecting.

3. Capability layout cleanup per user direction:
   - capabilities/primitive/base/  →  capabilities/primitive/chassis/
   - dropped capabilities/primitive/base/cmd.v1.toml (use `move` only)
   - dropped capabilities/primitive/robot/  (state is just chassis pose;
     full-robot snapshot was a legacy assumption)
   - moved primitive/robot/state.v1.toml  →  primitive/chassis/state.v1.toml

4. tiago_chassis driver now registers ONE cap (namespace
   robonix/primitive/chassis) with two interfaces: state + move. No
   more dual-cap robot/chassis split.

5. memsearch service migrated to the same pattern (contract types on
   signatures, _decl_mcp helper).

Other pieces preserved from the in-progress work:
- pilot system prompt now lists each cap's CAPABILITY.md path so the
  LLM lazy-loads docs via the read_file builtin (discovery::cap_md_index)
- per-package CAPABILITY.md files (chassis/camera/lidar/nav2)
- start.sh exports ROBONIX_PKG_HOST_DIR; driver registers
  capability_md_path = $ROBONIX_PKG_HOST_DIR/CAPABILITY.md
- chat.rs ToolResult is now one short line (≤80 chars, newlines collapsed)
- sim/stop.sh — kill-everything one-liner
- compose.yaml restored pylib bind-mount
- SourcePathKey::RobonixPy back, points at pylib/robonix-py
CI was failing on cargo fmt --check and -D warnings clippy:

- chat.rs/manifest.rs/planner.rs: collapse nested `if let` chains into
  `if let && let` form (clippy::collapsible_if).
- planner.rs: `#[allow(clippy::too_many_arguments)]` on run_turn (8 args).
- chat.rs: cargo fmt --all linewrap on send_message + suffix expr.
…odes, capabilities reorg

CLI:
- `rbnx deploy` → `rbnx boot`. `deploy` kept as a `#[command(alias)]`
  for back-compat. `rbnx-deploy/` runtime dir → `rbnx-boot/`.
- Boot's Driver(INIT) dispatch is now contract-id-driven: each
  per-area driver TOML emits its own gRPC service stub
  (`PrimitiveChassisDriver`, `ServiceNavigationDriver`, …); boot
  derives the path `/robonix.contracts.<svc>/Driver` from the cap's
  contract_id and calls it via tonic's low-level Grpc::unary with
  ProstCodec<DriverRequest, DriverResponse>. No master
  LifecycleDriverClient needed any more.
- chat TUI: scroll calc now counts wrapped rows instead of logical
  lines, so the last line is always visible (was clipped on long
  messages).

Capabilities reorg (under `capabilities/`):
- delete the two `{CAP_CLASS}` template TOMLs (primitive/driver.v1,
  service/driver.v1) and the master lifecycle/driver.v1 — replaced by
  concrete per-area driver TOMLs at:
    primitive/{audio,camera,chassis,imu,lidar}/driver.v1.toml
    service/{map,navigation,perception,slam}/driver.v1.toml
  All have kind="primitive"|"service" and io.srv =
  "lifecycle/srv/Driver".
- split primitive/sensor/* into separate primitive/lidar/* and
  primitive/imu/* areas (matches what the live tiago_lidar driver
  already does; sensor/ as catch-all is gone).
- chassis/move.v1.toml: srv was `chassis/srv/StreamMove` +
  rpc_client_stream — that contract didn't match the live driver and
  caused the schema mismatch the LLM ran into. Now points at the
  unary `chassis/srv/ExecuteMoveCommand` and mode = rpc.
- service/slam/* → service/map/* (the contracts are about map state,
  not just slam).

Webots tiago drivers — full lifecycle Driver implementation:
- Each driver (chassis/camera/lidar/nav2) now runs a small gRPC server
  on its own port (50211/50212/50213/50221) implementing
  Primitive{Chassis,Camera,Lidar}DriverServicer or
  ServiceNavigationDriverServicer. Driver(CMD_INIT) does an
  area-specific readiness check (chassis: ROS2 + cmd_vel pub up;
  camera: first RGB frame; lidar: first scan; nav2: ROS2 up + Nav2
  action server probe with degraded fallback).
- Drivers declare `robonix/<area>/driver` as a Transport.Grpc
  interface alongside their MCP interfaces, so `rbnx boot` blocks on
  Driver(INIT) → ok=true before the next package starts.
- Fixed long-standing camera _decl_mcp bug: was passing description
  string instead of fn, so MCP description + schema came out empty.

Chassis MoveCommand redesign (LLM was "偷工减料" on rotations):
- Add three fields to MoveCommand.msg: `duration_sec`, `forward_m`,
  `rotate_deg`. Driver picks whichever is non-zero in priority order
  (forward_m > rotate_deg > velocity), computes timing internally
  from configurable speeds (TIAGO_CHASSIS_SPEED_MPS,
  TIAGO_CHASSIS_ANG_SPEED_RPS).
- "transform 360°" / "go 0.5 m forward" now map to a single MCP call
  with deterministic completion, instead of the LLM having to chain
  N velocity bursts and re-snapshot.

Other:
- robonix-py / mcp_contract: signature-driven schema (input class
  from annotation, description from docstring) — no manual JSON
  schemas in drivers.
- Pilot: persistence prompt + cap_md_index lazy-load (LLM reads
  CAPABILITY.md via executor read_file when it actually needs it,
  keeping system prompt small).
- system/memory/memsearch_service migrated to new Atlas API
  (RegisterCapability + DeclareInterface(transport, endpoint,
  TransportParams)).
- rust/crates/robonix-cli/src/cmd/inspect.rs split out from
  runtime.rs (cosmetic).
- rust/crates/robonix-cli/src/manifest.rs: DependsRef now accepts
  `path:` / `url:` / `branch:` per the SOFT_DEPS-like model (lib /
  source dependency, NOT boot-order). Resolution / clone for url
  deps still TODO; field is parsed and validated only for now.
- IDL: drop primitive/base/* (migration to chassis/* completed).
…g boot

This commit introduces a TODO comment in the `execute` function of the deploy module, outlining the need to handle non-builtin `system:` entries (e.g., memory, liaison, nexus) by resolving their package directories via `rbnx path` and starting them during the system phase. This change aims to improve the lifecycle management of these entries in future implementations.
@enkerewpo
enkerewpo marked this pull request as ready for review April 27, 2026 09:43
@enkerewpo
enkerewpo requested review from Copilot and lhw2002426 April 27, 2026 09:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Implements the new Atlas control-plane API and updates system components + packaging workflows to the new capability/manifest spec, while retaining wire-compat via the legacy RobonixRuntime shim.

Changes:

  • Added executor configuration + proto codegen build scripts, and migrated CLI/runtime components to use robonix-atlas protos and client.
  • Refactored codegen and contract generation to support capability templates and MCP-oriented Python output (removed legacy Python codegen + e2e assets).
  • Introduced/relocated capability TOMLs under repo-root capabilities/ and updated example Webots deployment/package manifests and scripts accordingly.

Reviewed changes

Copilot reviewed 210 out of 528 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
rust/crates/robonix-executor/src/config.rs Adds executor config resolution (defaults + YAML + CLI/env).
rust/crates/robonix-executor/build.rs Generates executor gRPC stubs from IDL + capability TOMLs at build time.
rust/crates/robonix-executor/Cargo.toml Adds clap/serde_yaml + build-deps; switches to robonix-atlas crate.
rust/crates/robonix-codegen/tests/test_codegen.sh Removes legacy shell-based codegen integration test.
rust/crates/robonix-codegen/tests/assets/robonix_ridlc_e2e/setup.py Removes legacy ROS2 Python test asset.
rust/crates/robonix-codegen/tests/assets/robonix_ridlc_e2e/setup.cfg Removes legacy ROS2 Python test asset config.
rust/crates/robonix-codegen/tests/assets/robonix_ridlc_e2e/robonix_ridlc_e2e/stream_subscriber.py Removes legacy runtime/ROS2 e2e test node.
rust/crates/robonix-codegen/tests/assets/robonix_ridlc_e2e/robonix_ridlc_e2e/stream_publisher.py Removes legacy runtime/ROS2 e2e test node.
rust/crates/robonix-codegen/tests/assets/robonix_ridlc_e2e/robonix_ridlc_e2e/runtime_probe.py Removes legacy runtime e2e probe.
rust/crates/robonix-codegen/tests/assets/robonix_ridlc_e2e/robonix_ridlc_e2e/query_server.py Removes legacy query server test node.
rust/crates/robonix-codegen/tests/assets/robonix_ridlc_e2e/robonix_ridlc_e2e/query_client.py Removes legacy query client test node.
rust/crates/robonix-codegen/tests/assets/robonix_ridlc_e2e/robonix_ridlc_e2e/command_server.py Removes legacy command server test node.
rust/crates/robonix-codegen/tests/assets/robonix_ridlc_e2e/robonix_ridlc_e2e/command_client.py Removes legacy command client test node.
rust/crates/robonix-codegen/tests/assets/robonix_ridlc_e2e/robonix_ridlc_e2e/combined_runtime.py Removes legacy combined runtime test node.
rust/crates/robonix-codegen/tests/assets/robonix_ridlc_e2e/robonix_ridlc_e2e/init.py Removes legacy test package marker.
rust/crates/robonix-codegen/tests/assets/robonix_ridlc_e2e/resource/robonix_ridlc_e2e Removes legacy ROS2 resource marker.
rust/crates/robonix-codegen/tests/assets/robonix_ridlc_e2e/package.xml Removes legacy ROS2 package manifest asset.
rust/crates/robonix-codegen/tests/README.md Removes legacy test documentation.
rust/crates/robonix-codegen/tests/.gitignore Removes legacy test artifacts ignore.
rust/crates/robonix-codegen/test.sh Removes legacy ad-hoc test runner.
rust/crates/robonix-codegen/src/main.rs Updates supported languages (proto/mcp) and removes python mode.
rust/crates/robonix-codegen/src/codegen/mod.rs Removes python_gen module export.
rust/crates/robonix-codegen/src/codegen/mcp_python_gen.rs Clarifies MCP Python generator docs.
rust/crates/robonix-codegen/src/codegen/contract_gen.rs Adds template-id skipping and stable RPC method naming.
rust/crates/robonix-codegen/README.md Removes legacy codegen README.
rust/crates/robonix-cli/src/pb.rs Adds OUT_DIR-based proto include module for CLI.
rust/crates/robonix-cli/src/main.rs Switches CLI proto access to local pb module.
rust/crates/robonix-cli/src/install.rs Updates installed package info fields to capabilities/depends.
rust/crates/robonix-cli/src/database.rs Migrates stored package metadata to capabilities/depends fields.
rust/crates/robonix-cli/src/config.rs Updates source-path keys (capabilities, pylib) and removes legacy node/core fields.
rust/crates/robonix-cli/src/cmd/validate.rs Allows validating package from CWD and prints capabilities/depends.
rust/crates/robonix-cli/src/cmd/setup.rs Updates repo root markers and printed valid path keys.
rust/crates/robonix-cli/src/cmd/run_package.rs Refactors rbnx start to run package-level start, adds CWD package discovery, updates env injection.
rust/crates/robonix-cli/src/cmd/path.rs Updates examples to use rbnx path capabilities.
rust/crates/robonix-cli/src/cmd/launch_helpers.rs Removes now-inlined shell escaping helper.
rust/crates/robonix-cli/src/cmd/inspect.rs Adds new atlas introspection commands (caps/tools/channels/inspect).
rust/crates/robonix-cli/src/cmd/info.rs Displays installed package capabilities/depends.
rust/crates/robonix-cli/src/cmd/codegen.rs Stages protos into per-package build dir and updates proto generation pipeline.
rust/crates/robonix-cli/src/cmd/build.rs Changes build to execute manifest build shell body rather than a script path.
rust/crates/robonix-cli/build.rs Adds build-time proto generation for CLI.
rust/crates/robonix-cli/Cargo.toml Removes robonix-sdk/interfaces deps; adds robonix-atlas + proto build-deps.
rust/crates/robonix-buffer/src/shm.rs Removes robonix-buffer SHM implementation (crate appears removed).
rust/crates/robonix-buffer/src/error.rs Removes robonix-buffer error definitions.
rust/crates/robonix-buffer/Cargo.toml Removes robonix-buffer crate manifest.
rust/crates/robonix-atlas/tests/end_to_end.rs Removes legacy e2e tests and replaces with TODO note for new API.
rust/crates/robonix-atlas/src/main.rs Migrates atlas binary to new service registry + clap args.
rust/crates/robonix-atlas/src/lib.rs Defines new/legacy proto modules and exports client/service/shim modules.
rust/crates/robonix-atlas/build.rs Builds both new atlas + legacy runtime protos into one crate.
rust/crates/robonix-atlas/README.md Removes legacy atlas README.
rust/crates/robonix-atlas/Cargo.toml Updates atlas crate deps and drops robonix-sdk dev-dep.
rust/contracts/srv/vlm_chat.v1.toml Removes legacy contract TOML under rust/contracts.
rust/contracts/srv/perception_detect.v1.toml Removes legacy contract TOML under rust/contracts.
rust/contracts/srv/navigation_status.v1.toml Removes legacy contract TOML under rust/contracts.
rust/contracts/srv/navigation_navigate.v1.toml Removes legacy contract TOML under rust/contracts.
rust/contracts/srv/navigation_cancel.v1.toml Removes legacy contract TOML under rust/contracts.
rust/contracts/srv/memory_search.v1.toml Removes legacy contract TOML under rust/contracts.
rust/contracts/srv/memory_save.v1.toml Removes legacy contract TOML under rust/contracts.
rust/contracts/srv/memory_compact.v1.toml Removes legacy contract TOML under rust/contracts.
rust/contracts/srv/executor_list_tools.v1.toml Removes legacy contract TOML under rust/contracts.
rust/contracts/srv/executor.v1.toml Removes legacy contract TOML under rust/contracts.
rust/contracts/prm/sensor_lidar_snapshot.v1.toml Removes legacy primitive contract TOML under rust/contracts.
rust/contracts/prm/sensor_lidar3d.v1.toml Removes legacy primitive contract TOML under rust/contracts.
rust/contracts/prm/sensor_lidar.v1.toml Removes legacy primitive contract TOML under rust/contracts.
rust/contracts/prm/robot_state.v1.toml Removes legacy primitive contract TOML under rust/contracts.
rust/contracts/prm/camera_snapshot.v1.toml Removes legacy primitive contract TOML under rust/contracts.
rust/contracts/prm/camera_depth_snapshot.v1.toml Removes legacy primitive contract TOML under rust/contracts.
rust/contracts/prm/base_twist_in.v1.toml Removes legacy primitive contract TOML under rust/contracts.
rust/contracts/prm/base_move.v1.toml Removes legacy primitive contract TOML under rust/contracts.
rust/contracts/prm/base_cmd.v1.toml Removes legacy primitive contract TOML under rust/contracts.
rust/contracts/prm/audio_speaker.v1.toml Removes legacy primitive contract TOML under rust/contracts.
rust/contracts/prm/audio_mic.v1.toml Removes legacy primitive contract TOML under rust/contracts.
rust/_deprecated/provider/tiago_demo_package/rbnx_manifest.yaml Updates deprecated manifests from prm:: to primitive:: naming.
rust/_deprecated/provider/tiago_demo_package/rbnx/stop_camera_rgb.sh Updates comments for primitive:: naming.
rust/_deprecated/provider/tiago_demo_package/rbnx/stop_camera_depth.sh Updates comments for primitive:: naming.
rust/_deprecated/provider/tiago_demo_package/rbnx/stop_base_pose_amcl.sh Updates logs/comments for primitive:: naming.
rust/_deprecated/provider/tiago_demo_package/rbnx/stop_base_navigate.sh Updates comments for primitive:: naming.
rust/_deprecated/provider/tiago_demo_package/rbnx/start_camera_rgb.sh Updates comments for primitive:: naming.
rust/_deprecated/provider/tiago_demo_package/rbnx/start_camera_depth.sh Updates comments for primitive:: naming.
rust/_deprecated/provider/tiago_demo_package/rbnx/start_base_pose_amcl.sh Updates comments for primitive:: naming.
rust/_deprecated/provider/tiago_demo_package/rbnx/start_base_navigate.sh Updates comments for primitive:: naming.
rust/_deprecated/provider/tiago_demo_package/rbnx/build.sh Updates CLI wording from deploy→boot (deprecated area).
rust/_deprecated/provider/navigation_skills/navigation_skills_provider/wandering_skill.py Updates primitive query paths/strings for deprecated providers.
rust/_deprecated/provider/navigation_skills/navigation_skills_provider/move_to_object_skill.py Updates primitive query paths/strings for deprecated providers.
rust/_deprecated/provider/demo_service/rbnx_manifest.yaml Updates metadata strings for primitive:: naming (deprecated).
rust/_deprecated/provider/demo_service/rbnx/build.sh Updates CLI wording from deploy→boot (deprecated area).
rust/_deprecated/provider/demo_service/demo_service_provider/task_plan_service.py Updates prompt examples to primitive:: naming.
rust/_deprecated/provider/demo_service/demo_service_provider/semantic_map_service.py Updates primitive names used in deprecated service.
rust/_deprecated/provider/demo_service/README.md Updates docs from deploy→boot (deprecated).
rust/_deprecated/maniskill_vla_demo/scripts/build.sh Improves rbnx discovery and python selection before grpc_tools.protoc.
rust/_deprecated/maniskill_vla_demo/run.sh Adds optional startup toggles for disabled nodes.
rust/_deprecated/maniskill_vla_demo/robonix_manifest.yaml Adds legacy manifest for ManiSkill demo (deprecated folder).
rust/_deprecated/maniskill_vla_demo/pyproject.toml Updates ML deps and CUDA index sources for the demo.
rust/_deprecated/maniskill_vla_demo/maniskill_vla_demo/perception_node.py Ensures proto/mcp types paths are set before robonix_py import.
rust/_deprecated/maniskill_vla_demo/maniskill_vla_demo/grasp_node.py Restores CUDA_VISIBLE_DEVICES after YOLO setup to avoid GPU hiding.
rust/_deprecated/maniskill_vla_demo/maniskill_vla_demo/env_node.py Updates contract IDs to new namespaces and adds env-configurable settings.
rust/_deprecated/maniskill_vla_demo/experiments/resource_monitor.py Adds resource sampling tool for eval runs (deprecated demo area).
rust/_deprecated/maniskill_vla_demo/experiments/composite_tasks.json Adds eval task definitions (deprecated demo area).
rust/_deprecated/maniskill_vla_demo/experiments/README.md Adds eval harness documentation (deprecated demo area).
rust/README.md Removes legacy rust workspace README.
rust/Makefile Adjusts install/proto targets for new per-crate build.rs codegen.
rust/Cargo.toml Excludes liaison/interfaces crates and removes removed internal crates from workspace deps.
pyrightconfig.json Removes root pyright extraPaths config.
pyproject.toml Adds uv workspace root configuration for repo Python packages.
pylib/robonix-py/robonix_py/init.py Enhances mcp_contract decorator and updates contract-id docs.
pylib/robonix-py/.gitignore Adds Python build artifact ignores.
examples/webots/sim/stop.sh Adds teardown script for webots example stack.
examples/webots/sim/start.sh Adds launcher script with GPU auto-detection and X11 setup.
examples/webots/sim/compose.yaml Adds sim container compose stack with bind-mount layout and shm sizing.
examples/webots/sim/compose.gpu.yaml Adds NVIDIA GPU passthrough override for sim container.
examples/webots/sim/bridge/entrypoint.sh Adds sim container entrypoint to launch Webots and keep container alive.
examples/webots/sim/bridge/Dockerfile Reworks sim image build context and dependencies for docker-exec’d drivers.
examples/webots/sim/README.md Documents sim container usage and layout.
examples/webots/services/tiago_nav2/scripts/start.sh Adds docker-exec start script to run Nav2 + wrapper in sim container.
examples/webots/services/tiago_nav2/scripts/build.sh Adds codegen-only build script for tiago_nav2.
examples/webots/services/tiago_nav2/package_manifest.yaml Adds new package manifest for tiago_nav2 service.
examples/webots/services/tiago_nav2/nav2_driver/init.py Adds package marker.
examples/webots/services/tiago_nav2/nav2_config/config/my_navigate_w_replanning_and_recovery.xml Removes stray inline comment from BT XML.
examples/webots/services/tiago_nav2/CAPABILITY.md Adds capability documentation for nav2 wrapper.
examples/webots/robonix_manifest.yaml Adds deploy manifest for webots example (system + primitives + services).
examples/webots/primitives/tiago_lidar/scripts/start.sh Adds docker-exec start script for lidar driver.
examples/webots/primitives/tiago_lidar/scripts/build.sh Adds codegen-only build script for tiago_lidar.
examples/webots/primitives/tiago_lidar/package_manifest.yaml Adds new package manifest for tiago_lidar.
examples/webots/primitives/tiago_lidar/lidar_driver/init.py Adds package marker.
examples/webots/primitives/tiago_lidar/CAPABILITY.md Adds capability documentation for lidar.
examples/webots/primitives/tiago_chassis/scripts/start.sh Adds docker-exec start script for chassis driver.
examples/webots/primitives/tiago_chassis/scripts/build.sh Adds codegen-only build script for tiago_chassis.
examples/webots/primitives/tiago_chassis/package_manifest.yaml Adds new package manifest for tiago_chassis.
examples/webots/primitives/tiago_chassis/chassis_driver/init.py Adds package marker.
examples/webots/primitives/tiago_chassis/CAPABILITY.md Adds capability documentation for chassis.
examples/webots/primitives/tiago_camera/scripts/start.sh Adds docker-exec start script for camera driver.
examples/webots/primitives/tiago_camera/scripts/build.sh Adds codegen-only build script for tiago_camera.
examples/webots/primitives/tiago_camera/package_manifest.yaml Adds new package manifest for tiago_camera.
examples/webots/primitives/tiago_camera/camera_driver/init.py Adds package marker.
examples/webots/primitives/tiago_camera/CAPABILITY.md Adds capability documentation for camera.
examples/webots/primitives/audio_driver/robonix_manifest.yaml Updates legacy manifest strings to primitive naming.
examples/webots/primitives/audio_driver/audio_driver/node.py Updates docs/strings to robonix/primitive/* naming in legacy node.
examples/webots/primitives/audio_driver/audio_driver/init.py Updates module-level comments to primitive naming.
examples/webots/primitives/audio_driver/README.md Updates docs to primitive/service naming.
examples/webots/README.md Adds updated end-to-end Webots example instructions.
examples/webots/.gitignore Ignores per-package build artefacts and codegen directories.
docs Updates docs submodule pointer.
capabilities/system/speech/tts_stream.v1.toml Migrates speech contract IDs to new robonix/system/* namespace.
capabilities/system/speech/tts.v1.toml Migrates speech contract IDs to new robonix/system/* namespace.
capabilities/system/speech/dialog.v1.toml Migrates speech dialog contract ID and stream response type.
capabilities/system/speech/asr_stream.v1.toml Migrates ASR stream contract ID to new robonix/system/*.
capabilities/system/speech/asr.v1.toml Migrates ASR contract ID to new robonix/system/*.
capabilities/system/pilot.v1.toml Migrates pilot contract ID and kind to system.
capabilities/system/memory/search.v1.toml Adds new system memory search contract TOML.
capabilities/system/memory/save.v1.toml Adds new system memory save contract TOML.
capabilities/system/memory/compact.v1.toml Adds new system memory compact contract TOML.
capabilities/system/liaison.v1.toml Migrates liaison contract ID and kind to system.
capabilities/system/executor.v1.toml Adds new system executor contract TOML.
capabilities/service/navigation/status.v1.toml Adds navigation status service contract TOML.
capabilities/service/navigation/navigate.v1.toml Adds navigation navigate service contract TOML.
capabilities/service/navigation/driver.v1.toml Adds navigation lifecycle driver service contract TOML.
capabilities/service/navigation/cancel.v1.toml Adds navigation cancel service contract TOML.
capabilities/service/map/switch_mode.v1.toml Migrates map switch_mode contract ID to new namespace.
capabilities/service/map/status.v1.toml Migrates map status contract ID to new namespace.
capabilities/service/map/set_initial_pose.v1.toml Migrates map set_initial_pose contract ID to new namespace.
capabilities/service/map/scan_2d.v1.toml Migrates map scan_2d contract ID to new namespace.
capabilities/service/map/save_map.v1.toml Migrates map save_map contract ID to new namespace.
capabilities/service/map/pointcloud.v1.toml Migrates map pointcloud contract ID to new namespace.
capabilities/service/map/occupancy_grid.v1.toml Migrates map occupancy_grid contract ID to new namespace.
capabilities/service/map/load_map.v1.toml Migrates map load_map contract ID to new namespace.
capabilities/service/map/driver.v1.toml Adds map lifecycle driver service contract TOML.
capabilities/primitive/lidar/lidar_snapshot.v1.toml Adds lidar snapshot primitive capability TOML.
capabilities/primitive/lidar/lidar3d.v1.toml Adds lidar3d primitive capability TOML.
capabilities/primitive/lidar/lidar.v1.toml Adds lidar stream primitive capability TOML.
capabilities/primitive/lidar/driver.v1.toml Adds lidar lifecycle driver primitive capability TOML.
capabilities/primitive/imu/imu.v1.toml Migrates IMU capability ID to new primitive namespace.
capabilities/primitive/imu/driver.v1.toml Adds IMU lifecycle driver primitive capability TOML.
capabilities/primitive/chassis/twist_in.v1.toml Adds chassis twist_in primitive capability TOML.
capabilities/primitive/chassis/state.v1.toml Adds chassis state primitive capability TOML.
capabilities/primitive/chassis/odom.v1.toml Migrates chassis odom capability ID to new namespace.
capabilities/primitive/chassis/move.v1.toml Adds chassis move primitive capability TOML.
capabilities/primitive/chassis/driver.v1.toml Adds chassis lifecycle driver primitive capability TOML.
capabilities/primitive/camera/snapshot.v1.toml Adds camera snapshot primitive capability TOML.
capabilities/primitive/camera/rgb.v1.toml Migrates camera rgb capability ID to new namespace.
capabilities/primitive/camera/driver.v1.toml Adds camera lifecycle driver primitive capability TOML.
capabilities/primitive/camera/depth_snapshot.v1.toml Adds camera depth_snapshot primitive capability TOML.
capabilities/primitive/camera/depth.v1.toml Migrates camera depth capability ID to new namespace.
capabilities/primitive/audio/speaker.v1.toml Adds audio speaker primitive capability TOML.
capabilities/primitive/audio/mic.v1.toml Adds audio mic primitive capability TOML.
capabilities/primitive/audio/driver.v1.toml Adds audio lifecycle driver primitive capability TOML.
README.md Updates root quickstart and architecture descriptions to the new flow.
README-zh.md Replaces Chinese README content with placeholder TODO.
.github/workflows/commit.yml Removes commit message validation GitHub Action.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

));
}

let mut env = std::collections::HashMap::new();

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description states packages receive ROBONIX_ATLAS_ENDPOINT, but rbnx start injects ROBONIX_ATLAS. This will break packages that follow the new manifest spec and read ROBONIX_ATLAS_ENDPOINT at runtime. Prefer setting ROBONIX_ATLAS_ENDPOINT (and optionally also set ROBONIX_ATLAS as a backward-compat alias if needed).

Suggested change
let mut env = std::collections::HashMap::new();
let mut env = std::collections::HashMap::new();
env.insert("ROBONIX_ATLAS_ENDPOINT".to_string(), endpoint.clone());

Copilot uses AI. Check for mistakes.
Comment on lines 189 to +191
contract_id
Same string as ``[contract] id`` in the TOML under ``rust/contracts/`` (e.g.
``robonix/srv/memory/search``).
``robonix/system/memorysearch``).

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring is now out of sync with the repo layout and current contract IDs: contract TOMLs live under capabilities/ (not rust/contracts/), and the example robonix/system/memorysearch does not match the new IDs shown elsewhere in this PR (e.g. robonix/system/memory/search). Update the path and example so users copy a valid contract id.

Copilot uses AI. Check for mistakes.
Comment thread capabilities/primitive/imu/imu.v1.toml Resolved
Comment on lines +3 to +9
// TODO(task #31): rewrite end-to-end tests against the new Atlas API
// (RegisterCapability / DeclareInterface / QueryCapabilities) once the
// `robonix-proto` crate lands. The previous version drove Atlas through
// `robonix-sdk` — both the SDK and the old Node-* wire schema have been
// retired in this branch, so the tests would not compile against the new
// proto. Original logic preserved in git history before the commit that
// rewrote atlas.proto + service.rs.

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change removes the only end-to-end coverage for registry/discovery/channel-negotiation semantics, and replaces it with a TODO. Since Atlas is a central control-plane component, the new API surface should have at least minimal e2e tests (register capability, declare interface, query, connect/disconnect, heartbeat eviction behavior) tied to the new robonix_atlas::pb types.

Suggested change
// TODO(task #31): rewrite end-to-end tests against the new Atlas API
// (RegisterCapability / DeclareInterface / QueryCapabilities) once the
// `robonix-proto` crate lands. The previous version drove Atlas through
// `robonix-sdk` — both the SDK and the old Node-* wire schema have been
// retired in this branch, so the tests would not compile against the new
// proto. Original logic preserved in git history before the commit that
// rewrote atlas.proto + service.rs.
// Minimal regression coverage for the Atlas proto API surface. These tests
// intentionally bind to the generated `robonix_atlas::pb` request types so
// that capability registration, interface declaration, discovery/query,
// connect/disconnect, and heartbeat-related API messages remain exercised
// during the ongoing control-plane migration.
use prost::Message;
use robonix_atlas::pb;
fn assert_protobuf_roundtrip<M>(message: M)
where
M: Message + Default + PartialEq + core::fmt::Debug,
{
let encoded = message.encode_to_vec();
let decoded = M::decode(encoded.as_slice()).expect("protobuf decode should succeed");
assert_eq!(decoded, message, "protobuf roundtrip must preserve message");
}
#[test]
fn atlas_pb_supports_registry_and_discovery_requests() {
assert_protobuf_roundtrip(pb::RegisterCapabilityRequest::default());
assert_protobuf_roundtrip(pb::DeclareInterfaceRequest::default());
assert_protobuf_roundtrip(pb::QueryCapabilitiesRequest::default());
}
#[test]
fn atlas_pb_supports_channel_negotiation_requests() {
assert_protobuf_roundtrip(pb::ConnectRequest::default());
assert_protobuf_roundtrip(pb::DisconnectRequest::default());
}
#[test]
fn atlas_pb_supports_heartbeat_requests() {
assert_protobuf_roundtrip(pb::HeartbeatRequest::default());
}

Copilot uses AI. Check for mistakes.
Comment on lines +658 to +659
/// Convert an arbitrary identifier to UpperCamelCase. Splits on `_`/`-`/
/// digit-letter boundaries and capitalises each segment.

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says the conversion splits on digit-letter boundaries, but the implementation below only special-cases _ and -. Either update the doc comment to match current behavior, or extend upper_camel so it actually treats digit↔letter transitions as boundaries.

Suggested change
/// Convert an arbitrary identifier to UpperCamelCase. Splits on `_`/`-`/
/// digit-letter boundaries and capitalises each segment.
/// Convert an arbitrary identifier to UpperCamelCase. Splits on `_` and `-`
/// separators and capitalises each segment.

Copilot uses AI. Check for mistakes.
enkerewpo and others added 7 commits April 27, 2026 18:02
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
The liaison/voice contributor on `dev` is building on top of the
pre-packaging chat.rs (Rc<RefCell> message buffer, spawned-stream
tokio task with biased select for Esc-abort, audio_data/source on
Task, PageUp/PageDown scroll, "Pilot:" prefix). dev-packaging had
rewritten the whole shell, which would have caused painful merge
conflicts when liaison rebased.

Reset chat.rs to track dev's shape; only keep the changes that are
forced by the dev-packaging namespace migration:
  - SrvPilotClient -> SystemPilotClient
  - .stream(Task) -> .submit_task(Task)
  - robonix_sdk::query_nodes -> AtlasClient::connect_to_capability
    (sdk crate is gone in dev-packaging)
  - EVT_TASK_GRAPH/event.task_graph -> EVT_PLAN/event.plan
    (CapabilityCall has no tool_name, so leaf-strip contract_id)

Diff vs origin/dev: 74+/-111 (was 213+/-313). Every remaining hunk
is one of the four renames above, the file header doc block, or
threading `tonic::transport::Channel` instead of a `&str` endpoint.
system: keys other than atlas/executor/pilot are now resolved against
<robonix_source_path>/system/<key>/ and brought up via the same
spawn → wait-for-cap → Driver(INIT) flow as primitive/service.
Missing package directories are warned and skipped (not fatal), so
manifests can keep placeholders for services that aren't vendored
yet (e.g. liaison while it's still being ported off robonix-sdk).

Also: resolve_package_path_for_start now accepts the legacy
robonix_manifest.yaml file (previously only the new
package_manifest.yaml), so packages that haven't migrated their
inner manifest yet (e.g. system/speech) can still be launched by
`rbnx start -p <pkg>`. detect_and_load already handled both;
this just lifts the gate at the resolver level too.

End-to-end on examples/webots:
  9 components up — atlas + executor + pilot (system builtins),
  memory + speech (system packages, MCP/gRPC caps registered),
  chassis + camera + lidar (primitives, Driver(INIT) ok),
  nav2 (service, degraded as expected without nav stack),
  liaison + nexus skipped (no on-disk package).
Each child boot spawns now lives in its own PGID via process_group(0),
and boot writes <manifest_dir>/rbnx-boot/state.json incrementally as
components come up. Tear-down (Ctrl-C, error path, `rbnx shutdown`
from another shell) all funnel through one helper that SIGTERMs each
PGID, waits 3s, then SIGKILLs stragglers.

The error path was the worst offender: `?`-propagation out of
spawn_and_init returned execute() WITHOUT killing already-spawned
children, leaving atlas/pilot/executor as session-orphans on every
failed bring-up. Now the spawn loop is wrapped in an inner async
block; on Err we run teardown + delete state.json and re-raise.

`rbnx shutdown [-f manifest]` is new — defaults to
./robonix_manifest.yaml. Reads state.json, kills the PGIDs, removes
state.json. Useful when boot is in foreground in another window or
when boot itself died but children survived.

CLI scope is host-side process groups only — by design. Side-channel
children (`docker exec`, ssh, kubectl exec, …) are the package's
responsibility. The webots tiago_chassis / camera / lidar start.sh
now follow the same trap pattern nav2 already used: a TERM trap that
runs `docker exec robonix_tiago_sim pkill -9 -f <pkg>_driver` on
exit, so the container-side python actually dies when boot SIGTERMs
the host wrapper. Patterns are package-specific (`chassis_driver`,
not bare `_driver`) so we don't take Webots's own webots_ros2_driver
down with us. TODO in teardown.rs for an explicit `stop:` field on
package_manifest.yaml so this becomes a manifest schema concern
instead of relying on each package's trap discipline.

Other:
- audio_driver: replace stale `robonix_msg_pb2` (the pre-dev-packaging
  monolithic stub) with per-IDL-package modules (audio_pb2,
  std_msgs_pb2) and rename Prm* servicer classes to Primitive* to
  match new codegen output. Atlas registration still uses the
  deleted runtime proto monolith — keep audio_driver commented out
  in the example manifest until that's ported too.
- shutdown also SIGTERMs the original boot pid as a courtesy so a
  `rbnx boot` running in the foreground exits its signal-wait loop.
`rbnx boot` was echoing the full child argv to stdout / boot log,
including `--vlm-api-key sk-…`. Per-flag redaction: any --flag whose
name contains api-key / api_key / secret / password or ends in
-token / _token gets its trailing value replaced with `<redacted>`
in the log preview only. The child still receives the real value
on argv unchanged.

state.json doesn't carry argv at all so no change there.
Two follow-ups from PR #27 review:

1. `expand_env_in_str` walked the input one byte at a time with
   `bytes[i] as char`, which silently truncates every non-ASCII
   continuation byte to garbage. Any non-ASCII char in a YAML scalar
   (Chinese paths, en-dash from copy-paste, …) was being mutilated.
   Walk by UTF-8 chars now; ASCII fast-path stays unchanged.

2. The pre-PR-27 `RegisterNode` legacy gRPC accepted an empty
   `namespace`; the new `Atlas.register` requires it and returns
   `namespace required`. Anything still on the legacy SDK that
   omitted namespace (audio_driver, in-flight-on-dev callers) would
   break on first registration. Synthesise `robonix/legacy/<cap_id>`
   in the legacy shim when namespace is empty, with a deprecation
   warning so the migration is visible in the atlas log.
@enkerewpo
enkerewpo merged commit 2a8f4f7 into dev May 2, 2026
1 check passed
enkerewpo added a commit that referenced this pull request May 2, 2026
docs: README updates post-PR-#27 merge
@enkerewpo
enkerewpo deleted the dev-packaging branch May 16, 2026 03:10
Chenyantt pushed a commit to Chenyantt/robonix that referenced this pull request Aug 8, 2026
- examples/webots/README.md: I added this in the relocation commit;
  the deploy form is documented in PR syswonder#27 and in the manifest itself.
- rust/crates/robonix-interfaces/lib/system/{executor,liaison,pilot}/README.md:
  IDL READMEs that just paraphrased the corresponding .srv files.
  The .srv comment block is canonical; READMEs drift.
Chenyantt pushed a commit to Chenyantt/robonix that referenced this pull request Aug 8, 2026
Two follow-ups from PR syswonder#27 review:

1. `expand_env_in_str` walked the input one byte at a time with
   `bytes[i] as char`, which silently truncates every non-ASCII
   continuation byte to garbage. Any non-ASCII char in a YAML scalar
   (Chinese paths, en-dash from copy-paste, …) was being mutilated.
   Walk by UTF-8 chars now; ASCII fast-path stays unchanged.

2. The pre-PR-27 `RegisterNode` legacy gRPC accepted an empty
   `namespace`; the new `Atlas.register` requires it and returns
   `namespace required`. Anything still on the legacy SDK that
   omitted namespace (audio_driver, in-flight-on-dev callers) would
   break on first registration. Synthesise `robonix/legacy/<cap_id>`
   in the legacy shim when namespace is empty, with a deprecation
   warning so the migration is visible in the atlas log.
Chenyantt pushed a commit to Chenyantt/robonix that referenced this pull request Aug 8, 2026
feat: deploy + package manifest config spec implementation + system code update (atlas, pilot, executor; atlas API migration)
Chenyantt pushed a commit to Chenyantt/robonix that referenced this pull request Aug 8, 2026
Drop stale specifics from the bring-up section: don't enumerate the
component count (which depends on the manifest, and changes when
audio_driver/liaison get re-enabled) and don't pin the package list
("4 drivers + 3 system caps" — speech and memory now also register
as packages, but speech isn't fully wired into pilot yet so listing
it explicitly oversells what works today).

Add `rbnx shutdown` to the tear-down section in both the top-level
README and examples/webots/README — explain it reads the per-manifest
state file and SIGTERMs each PGID. Mention that the webots primitive
start.sh files install a trap that sweeps the in-container python so
ports 50111-50113 / 50211-50213 don't get stranded between boots.

system/speech/README: rename `RegisterNode` → `RegisterCapability`
(via legacy shim) and update namespace to robonix/system/speech to
match where speech actually lives in the new layout.

Submodule pointer (docs/) is unchanged — the per-page details there
are already in sync via 5b9e70d.
Chenyantt pushed a commit to Chenyantt/robonix that referenced this pull request Aug 8, 2026
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