Skip to content

feat(broker): serde mirrors + golden fixtures for fleet node-control wire protocol (Phase 0)#1106

Merged
willwashburn merged 5 commits into
mainfrom
feat/fleet-wire-serde
Jun 16, 2026
Merged

feat(broker): serde mirrors + golden fixtures for fleet node-control wire protocol (Phase 0)#1106
willwashburn merged 5 commits into
mainfrom
feat/fleet-wire-serde

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Summary:

Context:

Validation:

  • cargo fmt -- --check
  • cargo build
  • env -u RELAY_API_KEY cargo test
  • env -u RELAY_API_KEY cargo clippy -- -D warnings
  • env -u RELAY_API_KEY cargo test --release
  • npm run build:rust

@willwashburn willwashburn requested a review from khaliqgant as a code owner June 12, 2026 02:52
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@willwashburn, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 5 minutes and 50 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 024bd712-59d3-485d-8029-9e583c6dee7a

📥 Commits

Reviewing files that changed from the base of the PR and between b35ca37 and 7fd1765.

📒 Files selected for processing (12)
  • crates/broker/src/fleet_wire.rs
  • crates/broker/tests/fixtures/fleet-wire/action.invoke.json
  • crates/broker/tests/fixtures/fleet-wire/action.result.error.json
  • crates/broker/tests/fixtures/fleet-wire/action.result.output.json
  • crates/broker/tests/fixtures/fleet-wire/agent.deregister.json
  • crates/broker/tests/fixtures/fleet-wire/agent.register.json
  • crates/broker/tests/fixtures/fleet-wire/error.json
  • crates/broker/tests/fixtures/fleet-wire/inventory.sync.json
  • crates/broker/tests/fixtures/fleet-wire/node.register.json
  • crates/broker/tests/fixtures/fleet-wire/reply.agent_register.json
  • crates/broker/tests/fixtures/fleet-wire/reply.json
  • crates/broker/tests/fleet_wire_fixtures.rs
📝 Walkthrough

Walkthrough

This PR introduces the fleet_wire module, a new bidirectional protocol for broker-to-node communication. It defines message types for node/agent lifecycle events, action invocation, delivery, and inventory synchronization, with strict serde rules enforcing version compatibility and exclusive field constraints in action results.

Changes

Fleet Wire Protocol Implementation & Validation

Layer / File(s) Summary
Fleet Wire Versioning Primitives
crates/broker/src/fleet_wire.rs (lines 1–44)
FleetWireVersion wrapper enforces exact version 1 on deserialization, rejecting incompatible wire versions.
Node & Agent Lifecycle Events with Module Export
crates/broker/src/fleet_wire.rs (lines 46–113), crates/broker/src/lib.rs (line 9)
NodeRegister, NodeHeartbeat, NodeDeregister, AgentRegister, AgentDeregister, and DeliveryAck structs with serde field constraints; module publicly exported.
Action Result with Exclusive Payload Validation
crates/broker/src/fleet_wire.rs (lines 114–234)
ActionResult enforces mutually exclusive output or error fields via internal wire struct and custom deserializer; deserialize_optional_presence helper treats absent fields differently from explicit nulls.
Delivery, Inventory & Action Message Types
crates/broker/src/fleet_wire.rs (lines 235–288)
Deliver, InventorySync, InventoryAgent, ActionInvoke, and Ping structs with serde constraints and protocol-specific field mapping for sequencing and delivery modes.
Message Routing Enums & Type Aliases
crates/broker/src/fleet_wire.rs (lines 290–324)
NodeToServer and ServerToNode serde-tagged enums route messages bidirectionally; type aliases BrokerToRelaycast and RelaycastToBroker mark directional protocol boundaries.
Protocol Serde Unit Tests
crates/broker/src/fleet_wire.rs (lines 325–440)
Unit tests validate optional field omission, action result exclusivity, explicit-null rejection, unsupported version rejection, and delivery payload round-trips.
Test Fixtures & Round-trip Integration Test
crates/broker/tests/fixtures/fleet-wire/*, crates/broker/tests/fleet_wire_fixtures.rs
JSON fixtures for all message types; integration test deserializes by type tag, re-serializes, and asserts exact match with originals.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • khaliqgant

Poem

🐰 A wire protocol hops through the brush,
Node heartbeats ping with purposeful rush,
Messages tagged, their payloads kept true,
Actions invoke and results come through,
The fleet now speaks in serde's sweet song!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: introducing serde mirrors and golden fixtures for the fleet node-control wire protocol (Phase 0), which aligns with the actual changeset.
Description check ✅ Passed The PR description includes a Summary section with clear details about what was added, Context explaining the relationship to other issues, and a Validation section documenting testing steps. However, the template specifies a Test Plan with checkboxes and optional Screenshots section, which are absent from the provided description.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/fleet-wire-serde

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/broker/tests/fleet_wire_fixtures.rs`:
- Around line 31-34: The test currently only asserts fixture_paths is non-empty;
instead collect the observed fixture types (e.g., by mapping fixture_paths to
their type identifier or file stem) and compare against a canonical set of
required types (define a CANONICAL_FIXTURE_TYPES constant or vec) and assert
coverage equality (or that canonical ⊆ observed) so any missing fixture type
fails; update the assertions around fixture_paths and the round-trip checks (in
fleet_wire_fixtures.rs where fixture_paths and FIXTURE_DIR are used) to include
this seen-type coverage assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: db9aebc0-e845-4a05-abcc-14a6f4354960

📥 Commits

Reviewing files that changed from the base of the PR and between fe2f9f6 and b35ca37.

📒 Files selected for processing (14)
  • crates/broker/src/fleet_wire.rs
  • crates/broker/src/lib.rs
  • crates/broker/tests/fixtures/fleet-wire/action.invoke.json
  • crates/broker/tests/fixtures/fleet-wire/action.result.json
  • crates/broker/tests/fixtures/fleet-wire/agent.deregister.json
  • crates/broker/tests/fixtures/fleet-wire/agent.register.json
  • crates/broker/tests/fixtures/fleet-wire/deliver.json
  • crates/broker/tests/fixtures/fleet-wire/delivery.ack.json
  • crates/broker/tests/fixtures/fleet-wire/inventory.sync.json
  • crates/broker/tests/fixtures/fleet-wire/node.deregister.json
  • crates/broker/tests/fixtures/fleet-wire/node.heartbeat.json
  • crates/broker/tests/fixtures/fleet-wire/node.register.json
  • crates/broker/tests/fixtures/fleet-wire/ping.json
  • crates/broker/tests/fleet_wire_fixtures.rs

Comment thread crates/broker/tests/fleet_wire_fixtures.rs Outdated

@willwashburn willwashburn left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

NO-GO. I attempted to submit this as a request-changes review, but GitHub rejected it with: Review Can not request changes on your own pull request. Findings below.

Validation: clean worktree at /tmp/review-Rev0C1; compared crates/broker/tests/fixtures/fleet-wire against the current relaycast#191 head (b3640f5, v2 stable fixtures). Local validation passed: cargo fmt -- --check, cargo build, env -u RELAY_API_KEY cargo test, env -u RELAY_API_KEY cargo clippy -- -D warnings, env -u RELAY_API_KEY cargo test --release, npm run build:rust.

  1. [blocker] crates/broker/src/fleet_wire.rs:87, crates/broker/src/fleet_wire.rs:153, crates/broker/src/fleet_wire.rs:241, crates/broker/src/fleet_wire.rs:278 still serialize invocation_id fields as camelCase invocationId, and the mirrored fixtures carry that casing at crates/broker/tests/fixtures/fleet-wire/action.invoke.json:4, crates/broker/tests/fixtures/fleet-wire/agent.register.json:5, crates/broker/tests/fixtures/fleet-wire/inventory.sync.json:8, and crates/broker/tests/fixtures/fleet-wire/action.result.json:4. The canonical relaycast v2 schemas/fixtures use strict snake_case invocation_id; relaycast will reject these broker-emitted messages as missing invocation_id plus unknown invocationId. Change the serde rename values to invocation_id, remove the camelCase alias unless backward compatibility is explicitly intended and tested, and re-mirror the v2 fixtures.

  2. [major] crates/broker/src/fleet_wire.rs:110 models agent.deregister as only { v }, and crates/broker/tests/fixtures/fleet-wire/agent.deregister.json:3 mirrors that shape. The canonical v2 fixture and schema require name on agent.deregister, so the broker currently cannot emit the required identity for deregistration. Add pub name: String to AgentDeregister and update the fixture to include the deregistered agent name.

  3. [major] crates/broker/tests/fleet_wire_fixtures.rs:31 only asserts the fixture directory is non-empty and then self-round-trips whatever local files happen to exist. That missed the v2 fixture inventory: canonical relaycast now has 12 fixtures, including action.result.output.json and action.result.error.json, while this PR has one stale action.result.json. Add an explicit expected fixture list matching relaycast#191 and mirror both action-result variants so missing/renamed fixtures fail the Rust test instead of silently passing.

  4. [minor] crates/broker/src/fleet_wire.rs:63 and crates/broker/src/fleet_wire.rs:70 do not fully mirror relaycast's strict schema constraints. resume_cursor is required-but-nullable in relaycast, but a plain Option<String> accepts the field being omitted; load is z.number().finite().nonnegative() in relaycast, but Rust accepts negative f64 values. If this module is the serde contract check, add presence/value validation tests and custom deserializers/newtypes so Rust rejects the same invalid wire input as the zod schemas.

@willwashburn willwashburn left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Verdict: NO-GO. GitHub rejected a formal request-changes review from this authenticated identity because it owns the PR, so this is posted as a blocking review comment instead. The round-1 fixture fixes are present, and the 12 broker fixtures match relaycast feat/fleet-wire-types byte-for-byte, but the Rust mirror still has schema-parity gaps that the happy-path fixtures do not catch.

  1. [major] crates/broker/src/fleet_wire.rs:63 makes node.register.resume_cursor an Option<String>. In serde, an absent Option field deserializes as None, so Rust accepts a node.register message that omits resume_cursor. The zod source contract is resume_cursor: z.string().nullable(), meaning the field is required and only the value may be null. This breaks the Phase 0 mirror contract and lets Rust accept a shape Relaycast rejects. Change this to a required-nullable deserializer/wrapper that distinguishes missing from null, and add a regression test for missing resume_cursor.

  2. [major] crates/broker/src/fleet_wire.rs:70 models node.heartbeat.load as a raw f64. The relaycast zod schema is z.number().finite().nonnegative(), but the Rust mirror currently accepts negative loads when deserializing and can construct invalid outgoing heartbeats without any validation. Add a validated nonnegative finite type or manual serde for NodeHeartbeat, and cover negative and non-finite load cases in tests.

Validation run locally: cargo fmt -- --check, cargo build, env -u RELAY_API_KEY cargo test, npm run build:rust, and env -u RELAY_API_KEY cargo clippy -- -D warnings all pass.

@willwashburn willwashburn left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Verdict: GO — clean.

Fresh round-3 pass over the full diff found no blocker/major issues. Checked against fleet-implementation-plan.md Phase 0 item 4 and the current relaycast#191 head (8438b47). The Rust fixtures are byte-for-byte identical to packages/types/fixtures/fleet-wire, and the serde mirror matches the current Zod contract.

Prior findings verified fixed:

  1. r1 invocationId casing: fixed; wire fields and fixtures use invocation_id.
  2. r1 agent.deregister.name: fixed.
  3. r1 stale/vacuous fixture inventory: fixed; the test asserts the exact 12 fixture filenames and observed message types.
  4. r2 load finite+nonnegative: fixed on deserialize and serialize.
  5. r2 resume_cursor explicit-null serialization: fixed; Rust serializes None as null, and current relaycast schema explicitly accepts absent or null.

Validation run from fresh worktree /tmp/review-Rev0C3:

  • cargo fmt -- --check: pass
  • cargo build: pass
  • env -u RELAY_API_KEY cargo test: one default-parallel run failed in unrelated snippets::tests::configure_agent_relay_mcp_public_reads_env_fallback; the failing test passes isolated and the full debug suite passes with -- --test-threads=1
  • env -u RELAY_API_KEY cargo test -- --test-threads=1: pass
  • env -u RELAY_API_KEY cargo clippy -- -D warnings: pass
  • env -u RELAY_API_KEY cargo test --release: pass
  • npm run build:rust: pass

@willwashburn willwashburn left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Round 4 v3 parity review: NO-GO. I attempted --request-changes, but GitHub rejected it because the authenticated account owns this PR.

  1. [major] crates/broker/src/fleet_wire.rs:403 / crates/broker/tests/fixtures/fleet-wire/reply.json:6: agent.register success replies are not typed as carrying token. The fixture shows the required payload includes data.token, but the serde mirror stores successful reply payloads as plain serde_json::Value, so a successful reply frame for req_agent_register_001 with data: {"agent_id":"agt_1","name":"codex-builder-1"} deserializes successfully. I confirmed that with a temporary integration probe. That leaves the Phase 0 token-authority path without a contract-enforced token and lets broker-side consumers accept a successful registration reply that cannot seed the spawned harness token. Add a typed serde struct for the agent-register reply data requiring at least agent_id, name, and token with invocation_id and session_ref optional as in the fixture, plus tests that deserialize reply.json data through that type and reject missing/non-string token. If the top-level Reply parser is meant to be the contract boundary, add enough response discrimination/correlation to reject agent-register replies whose data lacks token.

Validation run: diff -ru between the relaycast and relay fixture directories produced no output; npm ci; cargo test fleet_wire; cargo build; cargo test; npm run build; npm run test. Existing hostile checks reject wrong ok, unsupported v, explicit null optionals, and ambiguous action.result; the missing-token reply is the accepted hostile case above.

@willwashburn willwashburn left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Round 5 v3.1 parity review: GO. No findings. Validated relay tip 7fd1765 with env -u RELAY_API_KEY cargo build -p agent-relay-broker, env -u RELAY_API_KEY cargo test -p agent-relay-broker fleet_wire, and diff -ru fixture parity against relaycast. reply.agent_register.json is present and byte-identical across repos; Rust typed validation rejects token-less reply.data.

@willwashburn willwashburn merged commit f2f53b7 into main Jun 16, 2026
39 checks passed
@willwashburn willwashburn deleted the feat/fleet-wire-serde branch June 16, 2026 01:12
willwashburn added a commit that referenced this pull request Jun 16, 2026
Reconcile the fleet control plane onto main after the #1105 (harness-driver
local protocol) and #1106 (broker serde mirrors + golden wire fixtures)
foundations merged. The frozen fleet wire contract (fleet_wire.rs serde
mirrors and tests/fixtures/fleet-wire/*.json) is taken byte-identical from
main; this commit adds only the control-plane delta on top of it.

- node_control: client that drives the harness-driver sidecar over the local
  protocol (node/handler register, deregister, broker handler invocation,
  handler results, handler-attributed spawns, inventory resync on reconnect).
- runtime/fleet: wires fleet control into the broker runtime — registers nodes
  and handlers, dispatches broker handler invocations, attributes spawns.
- Harden node/handler registration timing to close registration races.
- protocol/listen_api/runtime wiring and the futures-util + tokio-tungstenite
  deps the sidecar transport requires.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
willwashburn added a commit that referenced this pull request Jun 16, 2026
Reconcile the fleet control plane onto main after the #1105 (harness-driver
local protocol) and #1106 (broker serde mirrors + golden wire fixtures)
foundations merged. The frozen fleet wire contract (fleet_wire.rs serde
mirrors and tests/fixtures/fleet-wire/*.json) is taken byte-identical from
main; this commit adds only the control-plane delta on top of it.

- node_control: client that drives the harness-driver sidecar over the local
  protocol (node/handler register, deregister, broker handler invocation,
  handler results, handler-attributed spawns, inventory resync on reconnect).
- runtime/fleet: wires fleet control into the broker runtime — registers nodes
  and handlers, dispatches broker handler invocations, attributes spawns.
- Harden node/handler registration timing to close registration races.
- protocol/listen_api/runtime wiring and the futures-util + tokio-tungstenite
  deps the sidecar transport requires.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
willwashburn added a commit that referenced this pull request Jun 16, 2026
…+spawn

Adds the TypeScript fleet node layer on top of the merged broker control
plane (#1107), wire serde mirrors (#1106), and harness-driver local
protocol (#1105).

- @agent-relay/fleet: defineNode/action/spawn/onMessage declare a node's
  typed capabilities and channel-message triggers. Trigger match regexes
  must be flag-free — a flagged regex (e.g. /ship/i) is rejected at
  defineNode rather than silently matched case-sensitively.
- agent-relay fleet serve|nodes|status runs a fleet node sidecar and
  inspects registered nodes; broker MCP surface adds query_nodes and spawn
  tools (with MCP legacy resources restored) and clean node deregistration
  on shutdown.
- SDK messaging gains nodes (list/get) and triggers (list/create/update/
  delete) facades over the relaycast backend; bump @relaycast/sdk to
  ^3.1.1 for the node/trigger API.

Reconciled onto main: the broker, wire fixtures, and harness-driver local
protocol are taken canonical from main (#1105/#1106/#1107); this commit is
purely the TS SDK/CLI/MCP delta adapted to compile and test against them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
willwashburn added a commit that referenced this pull request Jun 16, 2026
…/spawn + §5 resource cleanup (Phase 4) (#1108)

* feat(fleet): @agent-relay/fleet SDK, fleet serve CLI, MCP query_nodes+spawn

Adds the TypeScript fleet node layer on top of the merged broker control
plane (#1107), wire serde mirrors (#1106), and harness-driver local
protocol (#1105).

- @agent-relay/fleet: defineNode/action/spawn/onMessage declare a node's
  typed capabilities and channel-message triggers. Trigger match regexes
  must be flag-free — a flagged regex (e.g. /ship/i) is rejected at
  defineNode rather than silently matched case-sensitively.
- agent-relay fleet serve|nodes|status runs a fleet node sidecar and
  inspects registered nodes; broker MCP surface adds query_nodes and spawn
  tools (with MCP legacy resources restored) and clean node deregistration
  on shutdown.
- SDK messaging gains nodes (list/get) and triggers (list/create/update/
  delete) facades over the relaycast backend; bump @relaycast/sdk to
  ^3.1.1 for the node/trigger API.

Reconciled onto main: the broker, wire fixtures, and harness-driver local
protocol are taken canonical from main (#1105/#1106/#1107); this commit is
purely the TS SDK/CLI/MCP delta adapted to compile and test against them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(fleet): namespace-import @agent-relay/fleet & harnesses for bun standalone build

The standalone bun --compile bundle (Standalone macOS Smoke) failed with
"No matching export in <pkg>/dist/index.d.ts" for value imports from
@agent-relay/fleet (and transitively @agent-relay/harnesses) — bun's bundler
mis-validates named value imports from these workspace packages against their
generated .d.ts, even though both the .js and .d.ts export the symbols. sdk and
harness-driver are unaffected; this surfaced only now because fleet-sidecar is
the first code to pull fleet/harnesses into the standalone bundle.

Switch the affected value imports to a namespace import + destructure (call
sites unchanged; type-only imports kept as `import type`). Verified the
standalone bun build now bundles (1045 modules) and produces the binary.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant