protocol: handshake, capabilities as placement, eval frontend generation (SPEC §7.1) #15

Manually merged
krisbuild merged 11 commits from lu/protocol-handshake into main 2026-09-27 22:42:41 +02:00
Owner

What

docs/live-upgrades.md §4.4 (version skew) and the Hello.leases / immediate LeaseAck part of §4.1.

  • Handshake. Hello (NodeAd) gains proto (absent = 0, legacy), build, caps, and leases (instances the agent runs right now). kb_core::build() reads KB_BUILD at runtime (fallback CARGO_PKG_VERSION); the flake's new deployed derivation wraps the unchanged cargo build with --set-default KB_BUILD <version>+<rev>, so only that trivial wrapper changes per commit and .#ci.* stays cached. Packages and the NixOS module default use deployed.
  • CP reply. Every admitted Hello gets Welcome { proto, build } (not sent to legacy agents, who would only log it as undecodable) and an immediate LeaseAck from the Hello's leases, via the same renew_leases the heartbeat uses.
  • Admission. MIN_AGENT_PROTO..=PROTO plus legacy 0; anything else is refused with close 1008 and a reason naming what to upgrade, shown on /api/nodes, /ui/nodes and kb nodes (which also show build/proto/caps).
  • Capabilities as placement. kb_core::caps: required_caps(&TaskDef, &SchedMeta) matches exhaustively on the whole vocabulary (all baseline today), plus resolved_ref() caps checked at dispatch; the matchmaker enforces caps as constraints and the queue page explains "agent lacks capability 'x'". Rule in kb-core docs and SPEC §7.1: the wire format grows only by fields an older peer ignores or by cap-gated variants.
  • Eval frontend generation. Bootstrap script is KB_EVAL_FRONTEND=1 exec kb-eval-nix --flake ".#<attr>"; eval jobs require eval-nix:1; nix agents advertise it; kb-eval-nix refuses a generation other than its own. An env var, not a flag, because today's clap CLI would reject an unknown flag — so legacy agents can safely be treated as eval-nix:1. One-time eval cache miss (script text changed), no kbN- bump.
  • Fix: a late disconnect of an old socket no longer removes a reconnected agent's registration.
  • Docs: SPEC §3.4, §5, §6.3, §7.1; forgejo-setup.md; TODO (drop legacy proto-0 admission later).

Compatibility

CP \ agent today's agent (proto 0) this agent (proto 1)
today's CP unchanged extra Hello fields ignored; no Welcome; watchdog as today
this CP admitted as legacy with {eval-nix:1}; no Welcome; immediate LeaseAck; new eval script works (env var ignored); new constraints.caps ignored full handshake

Tests

kb-core serde compat both directions, admission range, required_caps, has_cap; kb-agent Hello carries leases/proto/build, close reason surfaced; kb-control-plane required_caps_constrain_placement, new tests/handshake.rs (reconnect → Welcome + immediate ack; legacy admitted and runs eval; future proto refused and shown; cap-gated task waits for a capable agent); kb-eval-nix generation check; kb-cli golden eval hash updated.

🤖 Generated with Claude Code

## What docs/live-upgrades.md §4.4 (version skew) and the `Hello.leases` / immediate `LeaseAck` part of §4.1. - **Handshake.** `Hello` (`NodeAd`) gains `proto` (absent = 0, legacy), `build`, `caps`, and `leases` (instances the agent runs right now). `kb_core::build()` reads `KB_BUILD` at runtime (fallback `CARGO_PKG_VERSION`); the flake's new `deployed` derivation wraps the unchanged cargo `build` with `--set-default KB_BUILD <version>+<rev>`, so only that trivial wrapper changes per commit and `.#ci.*` stays cached. Packages and the NixOS module default use `deployed`. - **CP reply.** Every admitted Hello gets `Welcome { proto, build }` (not sent to legacy agents, who would only log it as undecodable) and an immediate `LeaseAck` from the Hello's leases, via the same `renew_leases` the heartbeat uses. - **Admission.** `MIN_AGENT_PROTO..=PROTO` plus legacy 0; anything else is refused with close 1008 and a reason naming what to upgrade, shown on `/api/nodes`, `/ui/nodes` and `kb nodes` (which also show build/proto/caps). - **Capabilities as placement.** `kb_core::caps`: `required_caps(&TaskDef, &SchedMeta)` matches exhaustively on the whole vocabulary (all baseline today), plus `resolved_ref()` caps checked at dispatch; the matchmaker enforces caps as constraints and the queue page explains "agent lacks capability 'x'". Rule in kb-core docs and SPEC §7.1: the wire format grows only by fields an older peer ignores or by cap-gated variants. - **Eval frontend generation.** Bootstrap script is `KB_EVAL_FRONTEND=1 exec kb-eval-nix --flake ".#<attr>"`; eval jobs require `eval-nix:1`; nix agents advertise it; kb-eval-nix refuses a generation other than its own. An env var, not a flag, because today's clap CLI would reject an unknown flag — so legacy agents can safely be treated as `eval-nix:1`. One-time eval cache miss (script text changed), no `kbN-` bump. - Fix: a late disconnect of an old socket no longer removes a reconnected agent's registration. - Docs: SPEC §3.4, §5, §6.3, §7.1; forgejo-setup.md; TODO (drop legacy proto-0 admission later). ## Compatibility | CP \ agent | today's agent (proto 0) | this agent (proto 1) | |---|---|---| | today's CP | unchanged | extra Hello fields ignored; no Welcome; watchdog as today | | this CP | admitted as legacy with `{eval-nix:1}`; no Welcome; immediate LeaseAck; new eval script works (env var ignored); new `constraints.caps` ignored | full handshake | ## Tests kb-core serde compat both directions, admission range, `required_caps`, `has_cap`; kb-agent Hello carries leases/proto/build, close reason surfaced; kb-control-plane `required_caps_constrain_placement`, new `tests/handshake.rs` (reconnect → Welcome + immediate ack; legacy admitted and runs eval; future proto refused and shown; cap-gated task waits for a capable agent); kb-eval-nix generation check; kb-cli golden eval hash updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
protocol: handshake, capabilities as placement, eval frontend generation (SPEC §7.1)
Some checks reported errors
krisbuild/kris/krisbuild/nix/hello cached
krisbuild/kris/krisbuild/nix/workspace-deps cached
krisbuild/kris/krisbuild/nix/world cached
krisbuild/kris/krisbuild/nix/clippy failed on ares (exit-code)
krisbuild/kris/krisbuild/nix/build superseded
krisbuild/kris/krisbuild/nix/test superseded
krisbuild/kris/krisbuild/nix/kb-check superseded
krisbuild/kris/krisbuild krisbuild kris/krisbuild: superseded by 2fed6fad6f23e67827004279885a515acee25798
395e035abb
Version skew becomes explicit and safe for the mixed CP/agent versions a
staggered nuxbox/ares upgrade produces (docs/live-upgrades.md §4.4, and the
`Hello.leases` part of §4.1).

- `Hello` carries `proto` (absent = 0, legacy), `build` (`kb_core::BUILD`:
  `<version>+<rev>` from the flake, else the crate version), `caps`, and
  `leases`, the instances the agent is running. All additive: today's
  control plane ignores them.
- The control plane answers every admitted Hello with `Welcome { proto,
  build }` (not sent to legacy agents, which could only log it as
  undecodable) and an immediate `LeaseAck` that renews the claimed leases
  exactly as a heartbeat does. It admits proto cp-1..=cp plus legacy 0 and
  refuses others with a 1008 close naming what to upgrade; refusals are
  logged and shown on /ui/nodes, /api/nodes and `kb nodes`, which also
  gain the agent build, proto and caps.
- `kb_core::caps::required_caps(def, sched)`: an exhaustive map from every
  payload/input/isolation/effect/resolved-ref kind and SchedMeta enum value
  to a cap, empty for everything at proto 1, plus `constraints.caps`. The
  matchmaker requires them (legacy agents have exactly the frozen legacy
  set) and checks resolved refs against the chosen node. The skew rule is
  written into kb-core's docs and SPEC.
- The bootstrap eval script is `KB_EVAL_FRONTEND=1 exec kb-eval-nix ...`:
  the frontend generation is identity (every eval's def_hash changes once;
  no kbN- bump), the eval requires `eval-nix:1`, agents with the nix
  feature advertise it, and kb-eval-nix (and `kb` as kb-eval-nix) refuses
  another generation. An environment variable rather than a flag, because
  today's frontend ignores it where it would reject an unknown flag - so a
  legacy agent is safely treated as having `eval-nix:1`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
build id at runtime via a package wrapper; keep a reconnect's registration
Some checks reported errors
krisbuild/kris/krisbuild/nix/workspace-deps cached
krisbuild/kris/krisbuild/nix/hello cached
krisbuild/kris/krisbuild/nix/world cached
krisbuild/kris/krisbuild/nix/clippy failed on nuxbox (exit-code)
krisbuild/kris/krisbuild/nix/build succeeded
krisbuild/kris/krisbuild/nix/kb-check succeeded
krisbuild/kris/krisbuild/nix/test superseded
krisbuild/kris/krisbuild krisbuild kris/krisbuild: superseded by a58cdf2619731327be18a42d7b91e392fe1333aa
2fed6fad6f
`kb_core::build()` reads `KB_BUILD` at runtime (falling back to the crate
version) instead of compiling the rev in, so `.#ci.build`, clippy and test
no longer change with every commit and a local pre-warm matches CI. The
flake's packages (and so the NixOS module's default) are now a
symlinkJoin + wrapProgram around the unchanged cargo build that sets
`KB_BUILD=<version>+<rev>` by default; wrapProgram inherits argv0, so the
PATH-resolved `kb-eval-nix` and `kb` keep dispatching on their names.

A connection's disconnect cleanup now removes the node's registry entry
(and marks it offline) only while that entry is still its own, so an agent
that reconnected before its old socket's close was processed stays
registered. docs/TODO.md tracks dropping the legacy proto-0 admission.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- A legacy (proto 0) Hello renews nothing, so it no longer gets the
  immediate LeaseAck: that ack would stamp every held instance as confirmed
  while its lease ran out, letting the agent outlive a re-queue by up to a
  lease_ttl. Its first heartbeat renews and is acked as before; the
  handshake test now checks a reconnecting legacy agent gets no reply.
- Inputs are resolved again only once a node's semaphores are held (once
  per candidate); the resolved-ref cap check stays before dispatch and
  gives the holds back if the node cannot decode them.
- required_caps is computed once per candidate and passed to admissible /
  admit_reason instead of per (task, node).
- `ci.deployed` puts the NixOS module's wrapper package under CI's eval;
  corrected the argv0 comment (wrapProgram inherits it).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge origin/main into lu/protocol-handshake
Some checks failed
krisbuild/kris/krisbuild/nix/hello cached
krisbuild/kris/krisbuild/nix/workspace-deps cached
krisbuild/kris/krisbuild/nix/world cached
krisbuild/kris/krisbuild/nix/clippy failed on ares (exit-code)
krisbuild/kris/krisbuild/nix/build succeeded
krisbuild/kris/krisbuild/nix/deployed succeeded
krisbuild/kris/krisbuild/nix/kb-check succeeded
krisbuild/kris/krisbuild/nix/test succeeded
krisbuild/kris/krisbuild krisbuild kris/krisbuild: one or more tasks failed
krisbuild/queue head graph failed
a58cdf2619
Author
Owner
@krisbuild r+
Collaborator

Removed from the merge queue: head graph failed.

Removed from the merge queue: head graph failed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge origin/main into lu/protocol-handshake
Some checks are pending
krisbuild/kris/krisbuild/nix/hello cached
krisbuild/kris/krisbuild/nix/workspace-deps cached
krisbuild/kris/krisbuild/nix/world cached
krisbuild/queue queued (#15 in line)
krisbuild/kris/krisbuild/nix/clippy succeeded
krisbuild/kris/krisbuild/nix/build succeeded
krisbuild/kris/krisbuild/nix/deployed succeeded
krisbuild/kris/krisbuild/nix/kb-check succeeded
krisbuild/kris/krisbuild/nix/test succeeded
krisbuild/kris/krisbuild krisbuild kris/krisbuild: all tasks succeeded
63bf38a6e6
Conflict only in ws.rs imports: kept this branch's handshake imports and
main's LogStream/Payload/TaskDef.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Owner
@krisbuild r+
Author
Owner
@krisbuild r-
tests: handshake suite joins the single integration binary (tests/it)
Some checks failed
krisbuild/queue the pull-request head changed
krisbuild/kris/krisbuild/nix/hello cached
krisbuild/kris/krisbuild/nix/workspace-deps cached
krisbuild/kris/krisbuild/nix/test-deps cached
krisbuild/kris/krisbuild/nix/world cached
krisbuild/kris/krisbuild/nix/test cached
krisbuild/kris/krisbuild/nix/clippy succeeded
krisbuild/kris/krisbuild/nix/build succeeded
krisbuild/kris/krisbuild/nix/kb-check succeeded
krisbuild/kris/krisbuild/nix/deployed succeeded
krisbuild/kris/krisbuild krisbuild kris/krisbuild: all tasks succeeded
49a4d9dc1d
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Owner
@krisbuild r+
Collaborator

The pull request head's own build failed (graph 602).

  • nix/build: exit-code on nuxbox, exit 1 — log
  • nix/clippy: exit-code on nuxbox, exit 1 — log

Push a fix, or comment @krisbuild retry to rerun the failed tasks and requeue.

The pull request head's own build failed ([graph 602](http://192.168.0.2:1337/ui/graphs/602)). - `nix/build`: exit-code on nuxbox, exit 1 — [log](http://192.168.0.2:1337/ui/instances/17409) - `nix/clippy`: exit-code on nuxbox, exit 1 — [log](http://192.168.0.2:1337/ui/instances/17412) Push a fix, or comment `@krisbuild retry` to rerun the failed tasks and requeue.
Merge remote-tracking branch 'origin/main' into lu/protocol-handshake
Some checks failed
krisbuild/kris/krisbuild/nix/hello cached
krisbuild/kris/krisbuild/nix/workspace-deps cached
krisbuild/kris/krisbuild/nix/test-deps cached
krisbuild/kris/krisbuild/nix/world cached
krisbuild/kris/krisbuild/nix/clippy succeeded
krisbuild/kris/krisbuild/nix/test succeeded
krisbuild/kris/krisbuild/nix/build succeeded
krisbuild/kris/krisbuild/nix/kb-check succeeded
krisbuild/kris/krisbuild/nix/deployed succeeded
krisbuild/kris/krisbuild krisbuild kris/krisbuild: all tasks succeeded
krisbuild/queue the merge conflicts in SPEC.md, crates/kb-agent/src/conn.rs, crates/kb-control-plane/src/http.rs, crates/kb-control-plan
7782ef75de
Collaborator

Removed from the merge queue: the pull-request head changed.

Removed from the merge queue: the pull-request head changed.
Author
Owner
@krisbuild r+
Collaborator

Removed from the merge queue: the merge conflicts in SPEC.md, crates/kb-agent/src/conn.rs, crates/kb-control-plane/src/http.rs, crates/kb-control-plane/src/scheduler/matchmaking.rs, crates/kb-control-plane/src/ui/mod.rs, crates/kb-control-plane/tests/it/common/mod.rs.

Removed from the merge queue: the merge conflicts in SPEC.md, crates/kb-agent/src/conn.rs, crates/kb-control-plane/src/http.rs, crates/kb-control-plane/src/scheduler/matchmaking.rs, crates/kb-control-plane/src/ui/mod.rs, crates/kb-control-plane/tests/it/common/mod.rs.
Merge origin/main into lu/protocol-handshake
Some checks reported errors
krisbuild/queue head failed: nix/test (exit-code on nuxbox)
krisbuild/kris/krisbuild/nix/workspace-deps cached
krisbuild/kris/krisbuild/nix/test-deps cached
krisbuild/kris/krisbuild/nix/hello cached
krisbuild/kris/krisbuild/nix/clippy cached
krisbuild/kris/krisbuild/nix/build cached
krisbuild/kris/krisbuild/nix/world cached
krisbuild/kris/krisbuild/nix/kb-check cached
krisbuild/kris/krisbuild/nix/deployed cached
krisbuild/kris/krisbuild/nix/test superseded
krisbuild/kris/krisbuild krisbuild kris/krisbuild: superseded by d09be7874de92b3b596b8960fc8335c587fae32d
975e0f4d67
Brings in agent enrollment (#10), node health / quarantine (#8) and the
NixOS credential fallbacks (#22). Conflicts resolved keeping both sides:
- matchmaking: required caps stay a hard constraint inside `eligible`, so
  infra-failure avoidance only counts nodes whose agent has the caps;
  admissible/admit_reason take both `caps` and `blocked`.
- load_nodes takes both the refusals and the config (health), and the
  nodes page shows refusal, health, agent build and caps rows.
- conn.rs / FakeAgent: enrollment headers and the proto-1 Hello with leases
  (dial_with_headers underneath both connect and dial).
- SPEC §6.3: caps and the health gate in the candidate predicate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Owner
@krisbuild r+
Collaborator

The pull request head's own build failed (graph 653).

  • nix/test: exit-code on nuxbox, exit 1 — log

Push a fix, or comment @krisbuild retry to rerun the failed tasks and requeue.

The pull request head's own build failed ([graph 653](http://192.168.0.2:1337/ui/graphs/653)). - `nix/test`: exit-code on nuxbox, exit 1 — [log](http://192.168.0.2:1337/ui/instances/18144) Push a fix, or comment `@krisbuild retry` to rerun the failed tasks and requeue.
Author
Owner
@krisbuild r+
Merge remote-tracking branch 'origin/main' into lu/protocol-handshake
All checks were successful
krisbuild/kris/krisbuild/nix/hello cached
krisbuild/kris/krisbuild/nix/workspace-deps cached
krisbuild/kris/krisbuild/nix/test-deps cached
krisbuild/kris/krisbuild/nix/world cached
krisbuild/kris/krisbuild/nix/clippy succeeded
krisbuild/kris/krisbuild/nix/test succeeded
krisbuild/kris/krisbuild/nix/build succeeded
krisbuild/kris/krisbuild/nix/deployed succeeded
krisbuild/kris/krisbuild/nix/kb-check succeeded
krisbuild/kris/krisbuild krisbuild kris/krisbuild: all tasks succeeded
krisbuild/queue merged
d09be7874d
krisbuild manually merged commit 264d8228b6 into main 2026-09-27 22:42:41 +02:00
Collaborator

Merged as 264d8228b6.

Merged as 264d8228b69b.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kris/krisbuild!15
No description provided.