feat(dispatch): apply server-pushed agentconfig (VA-130) #135

Merged
longjacksonle merged 1 commit from feat/VA-130-agentconfig into main 2026-07-14 16:56:41 +02:00

What

Agent-side consumer for VS-67. The agent dropped the server's agentconfig control frame: dispatch.rs route_json had no arm for it, so on every connect it logged unhandled JSON action action="agentconfig" and ignored the server's operational config. The node kept registering with the all-zeros default group_id and built-in tunables. The server half (VS-67) shipped Done; this builds the missing consumer.

Surfaced in VA-129 and again in VA-133 macOS validation (envelope={"action":"agentconfig","group_id":"group//0000..."}). Not macOS-specific: dispatch.rs is one cross-platform tree.

How

New agentconfig arm parses the optional fields and persists them into the two stores the agent reads at startup:

  • group_id to the data store (GROUP_ID_KEY), where resolve_group_id reads it on the next launch.
  • connection tunables (backoffInitialMs, backoffMaxMs, backoffMultiplier, idleTimeoutSecs), enableMdns, and the self-update flags (disableUpdate, forceUpdate, logUpdate, fakeUpdate) to the config store settings blob, where settings::load + ConnectionSettings::from_config + the flag readers pick them up.

Absent fields are left untouched (the server only overrides what it sent). Parsing is a pure agentconfig::parse (wire JSON -> AgentConfig) split from agentconfig::apply (store I/O) so the mapping is unit-tested with no I/O. group_id decodes the server's group//<hex> form (bare and 0x-prefixed also accepted); backoff_multiplier arrives as a float and is rounded to the integer the config store holds. Wire field names/types verified against vervain-server's agent_config.rs struct.

The config store is threaded from main through run_reconnecting / run_connection into Session and onto the JSON DispatchContext via a new with_config_stores builder (ungated fields, independent of the vervain-core store).

Apply-scope note

group_id and the connection tunables are read once at process start, so applied values take effect on the next start/reconnect cycle, not live in the current session (the ticket flagged this as acceptable with a documented note). The dispatcher logs the applied field list so the change is observable, and the unhandled JSON action log is gone. A live in-session apply (mutating the running ConnectConfig / rebinding mDNS) is a possible follow-up.

Testing

  • Pure parse tests: full payload, group_id-only push, prefix handling and rejection of bad hex/length, empty payload.
  • apply round-trip against two real SQLCipher stores: group_id lands in the data store, tunables/flags land in the config blob under the expected camelCase keys, and an operator-set key the server did not send survives.
  • Dispatcher test: routes a representative agentconfig frame through route_json and asserts group_id + tunables persisted and no fall-through to the unhandled arm.
  • 396 lib tests pass; clippy --deny warnings and fmt clean; full workspace builds.

Closes VA-130.

## What Agent-side consumer for VS-67. The agent dropped the server's `agentconfig` control frame: `dispatch.rs route_json` had no arm for it, so on every connect it logged `unhandled JSON action action="agentconfig"` and ignored the server's operational config. The node kept registering with the all-zeros default `group_id` and built-in tunables. The server half (VS-67) shipped Done; this builds the missing consumer. Surfaced in VA-129 and again in VA-133 macOS validation (`envelope={"action":"agentconfig","group_id":"group//0000..."}`). Not macOS-specific: `dispatch.rs` is one cross-platform tree. ## How New `agentconfig` arm parses the optional fields and persists them into the two stores the agent reads at startup: - `group_id` to the **data** store (`GROUP_ID_KEY`), where `resolve_group_id` reads it on the next launch. - connection tunables (`backoffInitialMs`, `backoffMaxMs`, `backoffMultiplier`, `idleTimeoutSecs`), `enableMdns`, and the self-update flags (`disableUpdate`, `forceUpdate`, `logUpdate`, `fakeUpdate`) to the **config** store settings blob, where `settings::load` + `ConnectionSettings::from_config` + the flag readers pick them up. Absent fields are left untouched (the server only overrides what it sent). Parsing is a pure `agentconfig::parse` (wire JSON -> `AgentConfig`) split from `agentconfig::apply` (store I/O) so the mapping is unit-tested with no I/O. `group_id` decodes the server's `group//<hex>` form (bare and `0x`-prefixed also accepted); `backoff_multiplier` arrives as a float and is rounded to the integer the config store holds. Wire field names/types verified against vervain-server's `agent_config.rs` struct. The config store is threaded from `main` through `run_reconnecting` / `run_connection` into `Session` and onto the JSON `DispatchContext` via a new `with_config_stores` builder (ungated fields, independent of the vervain-core `store`). ## Apply-scope note `group_id` and the connection tunables are read once at process start, so applied values take effect on the **next start/reconnect cycle**, not live in the current session (the ticket flagged this as acceptable with a documented note). The dispatcher logs the applied field list so the change is observable, and the `unhandled JSON action` log is gone. A live in-session apply (mutating the running `ConnectConfig` / rebinding mDNS) is a possible follow-up. ## Testing - Pure `parse` tests: full payload, group_id-only push, prefix handling and rejection of bad hex/length, empty payload. - `apply` round-trip against two real SQLCipher stores: group_id lands in the data store, tunables/flags land in the config blob under the expected camelCase keys, and an operator-set key the server did not send survives. - Dispatcher test: routes a representative `agentconfig` frame through `route_json` and asserts group_id + tunables persisted and no fall-through to the unhandled arm. - 396 lib tests pass; clippy `--deny warnings` and fmt clean; full workspace builds. Closes VA-130.
feat(dispatch): apply server-pushed agentconfig (VA-130)
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 13m9s
Create release / Create release from merged PR (pull_request) Has been skipped
b090602265
The agent dropped the server's `agentconfig` control frame: `route_json` had no arm for it, so on every connect it logged `unhandled JSON action action="agentconfig"` and ignored the server's operational config. The node kept registering with the all-zeros default group_id and built-in tunables. This is the agent-side consumer of vervain-server's VS-67 producer (server half shipped Done); the consumer was never built.

Add an `agentconfig` arm that parses the optional fields and persists them into the two stores the agent reads at startup: `group_id` to the data store (`GROUP_ID_KEY`, where `resolve_group_id` reads it), and the connection tunables / `enableMdns` / self-update flags into the config-store settings blob (where `settings::load` + `ConnectionSettings::from_config` read them). Absent fields are left untouched, so the server only overrides what it sent. Because both group_id and the tunables are read once at process start, applied values take effect on the next start/reconnect cycle rather than live in the current session; the dispatcher logs what it applied so the change is observable, and the unhandled-action log is gone.

Parsing lives in a pure `agentconfig::parse` (wire JSON -> `AgentConfig`, all-optional fields) split from `agentconfig::apply` (store I/O), so the mapping is unit-tested with no I/O. group_id decodes the server's `group//<hex>` form (bare and `0x`-prefixed hex also accepted); backoff_multiplier arrives as a float and is rounded to the integer the config store holds. The config store is threaded from `main` through `run_reconnecting` / `run_connection` into the `Session` and onto the JSON `DispatchContext` via a new `with_config_stores` builder (ungated fields, independent of the vervain-core `store`).

Cross-platform (dispatch is one tree). Covered by pure parse/apply unit tests, a two-store apply round-trip, and a dispatcher test that routes a representative `agentconfig` frame and asserts group_id + tunables persisted and no fall-through.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFsMi3Vmf3pCc7mBdhZqoV
longjacksonle deleted branch feat/VA-130-agentconfig 2026-07-14 16:56:42 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
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
psa-systems/vervain-agent!135
No description provided.