Repository navigation
refactor(cp): make openab-cp proto usable without the server - #1588
chaodu-agent wants to merge 6 commits into
Conversation
Split openab-cp into an always-compiled `proto` module and a default `server` feature (config, registry, policy, router, events, WS server, binary), so runtime clients can depend on the wire contract with default-features = false without linking axum/tokio. Adds proto::MIN_RUNTIME_FRAME_BYTES and rejects CP configs whose max_frame_bytes is below it. Carried from #1528 (commit 9455b50). Also fixes #1528 round-3 finding 7 (bail! string had a literal run of spaces; test now asserts the exact message) and gates tests/ws_lifecycle.rs on `server` so `cargo test --no-default-features` runs the proto tests. Part 1 of 3 re-landing #1528; see openabdev/openab-pty#77. Verified on macmini (M4, rustc 1.99.1), clean copy of this tree: - cargo metadata --locked: ok; Cargo.lock unchanged vs origin/main - cargo build -p openab-cp --locked: ok - cargo test -p openab-cp --locked: 142 unit + 9 ws_lifecycle passed - cargo check -p openab-cp --no-default-features --locked: ok - cargo test -p openab-cp --no-default-features --locked: 16 proto passed - cargo tree -p openab-cp --no-default-features -e normal: no axum/tokio/hyper - cargo clippy -p openab-cp --all-targets -- -D warnings: clean (default and --no-default-features) - cargo fmt -p openab-cp --check: clean - Docker dummy-layer sim (manifests + empty lib.rs, no openab-cp main.rs): cargo metadata --locked and root cargo check --locked ok - CI parity: cargo check --workspace, clippy --workspace [--features unified] -D warnings, test -p openab-gateway --features acp (417), test -p openab-core --features acp-mcp acp_mcp:: (5) / acp::pool:: (24), test --features acp (42), build --features unified: all ok - cargo test --workspace: all pass except openab-core secrets::tests::resolve_exec_nonzero_exit, which spawns /bin/false; macOS has only /usr/bin/false. openab-core is untouched here; CI is Linux.
Review opus5-F1: the max_frame_bytes floor ran before the max_outbound_queue_bytes <= max_frame_bytes check, so the case queue=4096/frame=8192 was rejected by the floor and the strictly-below branch of the queue rule went untested. Replace it with queue=1048576/frame=2097152 and assert the expected error text for every rejection case instead of a bare is_err(). Review opus5-F2: proto.rs and cp.toml.example now say the runtime client lands in a follow-up PR instead of describing it as already bundled. Verified on macmini (M4), clean copy of this tree: - cargo fmt -p openab-cp --check: clean - cargo build -p openab-cp --locked: ok - cargo test -p openab-cp --locked: 142 unit + 9 ws_lifecycle passed - cargo check -p openab-cp --no-default-features --locked: ok - cargo test -p openab-cp --no-default-features --locked: 16 proto passed - cargo clippy -p openab-cp --all-targets -- -D warnings: clean (default and --no-default-features) - cargo clippy --workspace -- -D warnings, cargo check --workspace: ok - Cargo.lock unchanged vs origin/main
This comment has been minimized.
This comment has been minimized.
…lient PR Keep MIN_RUNTIME_FRAME_BYTES in proto as the shared constant, but drop the CpConfig::validate() rejection and its test: nothing in this PR consumes the floor, and the error text promised a frame-size negotiation the protocol does not have. The check lands with the runtime client in PR B.
…to split The no-server guarantee holds only when nothing else in the same build enables the server feature; cargo build --workspace does, through the openab-cp binary. The tokio-tungstenite dev-dependency is test-only and never reaches downstream crates.
|
Important CHANGES REQUESTED What This PR Does
How It Works
Findings
Finding Details🟡 F1: The proto-only build is not covered by CIEvery cargo step in Requested change: add one step to the - name: openab-cp (proto only, no default features)
run: |
set -euo pipefail
cargo clippy -p openab-cp --no-default-features --all-targets -- -D warnings
cargo test -p openab-cp --no-default-features
leaked=$(cargo tree -p openab-cp --no-default-features -e normal --prefix none \
| grep -E '^(tokio|axum|hyper|tower|tracing|clap|toml) ' || true)
if [ -n "$leaked" ]; then
echo "::error::openab-cp proto-only build pulls in server dependencies:"
echo "$leaked"
exit 1
fi
🟢 F2: Round-1 findings resolvedThe startup-breaking 🟢 F3: Feature split is correctAll server modules are gated. The binary and 🟢 F4: Feature unification is documentedThe manifest and crate docs now state that the no-server guarantee holds only when nothing else in the same build enables 🟢 F5: Tests assert specific errorsEach rejection case now pairs its input with an expected error substring, which closes a false-pass gap where any error satisfied Baseline Check
What's Good (🟢)
Reviewer Verdicts
Addressing Previous Review FeedbackRound 1 (
|
--workspace always enables openab-cp's default server feature, so no CI step built the configuration runtime clients depend on. Add clippy and test for openab-cp with --no-default-features, and fail if a server dependency appears in its normal dependency graph (-e normal leaves out the test-only tokio-tungstenite dev-dependency).
What problem does this solve?
The runtime half of the Agent Control Plane (#1528) needs the CP wire types (
openab_cp::proto) insideopenab-core, but todayopenab-cpalways pulls in axum, tokio and the whole WebSocket server. This PR is part 1 of 3 re-landing #1528 (auto-closed by the stale-PR bot on 2026-09-13, not rejected), split so each piece can be reviewed in one sitting. All three are needed by openabdev/openab-pty#77 (Phase 0: openab-pty hosts as CP workers, Connect as primary).Refs #1528, openabdev/openab-pty#77
Discord Discussion URL: https://discord.com/channels/1491295327620169908/1491365158868619404/1532377620241449040
Review Contract
Goal
Make
openab_cp::protousable withdefault-features = false, without axum/tokio/the server, while keeping the default build ofopenab-cp(library +openab-cpbinary) unchanged. Add the sharedMIN_RUNTIME_FRAME_BYTESconstant (not enforced yet; CP config validation of the floor lands with the runtime client in PR B).Non-goals
[control_plane]config: that is PR B.openab agentCLI: that is PR C.openab-cpyet.Accepted Residual Risks
--no-default-featuresforopenab-cp. The proto-only build is covered by the explicit checks listed under Validation, not yet by a dependent crate.Acceptance Criteria
cargo test -p openab-cppasses (unit tests +ws_lifecycle)cargo check -p openab-cp --no-default-featuresandcargo test -p openab-cp --no-default-featurespass, andcargo tree -p openab-cp --no-default-features -e normalcontains no axum/tokio/hypercargo clippy -p openab-cp --all-targets -- -D warningsis clean, with and without default featuresCargo.lockis unchanged (--lockedbuilds pass)CI / checkjob is greenFollow-ups
feat(cp): runtime CP client and worker delegation serving (headless worker):[control_plane]config, client (register/heartbeat/reconnect, bounded writes), worker executor (PromptRunner),PromptExecutionseam,drop_idle_gaterebased onto fix(pool): keep the [[ws:]] workspace across hung eviction #1557, headless worker mode. Fixes feat(cp): land Agent Control Plane v1 on main #1528 round-6 F3 (client tick clamp), F10, F11 (Servecomment) and drops the round-1 ride-along edits.feat(cp): primary delegation state, owner-only local API and openab agent CLI:primary.rs,local.rs, the primary command arm,openab agent spawn|list|status|cancel. Fixes round-6 F1 (the red CI test), F3 (independent prune timer), F4, F5, F6, F7 (including CP-side empty-selector rejection), F9, F11 and F14.At a Glance
Prior Art & Industry Research
Not applicable: this is a Cargo feature split of an existing internal crate with no runtime, agent or scheduling behaviour change. The pattern (wire types always on, server behind a default feature) is the standard Cargo approach for a crate that is both a library contract and a binary.
OpenClaw: Not applicable (no equivalent crate boundary).
Hermes Agent: Not applicable (no equivalent crate boundary).
Proposed Solution
crates/openab-cp/Cargo.toml: serde, serde_json and chrono stay unconditional. tokio, axum, futures-util, toml, tracing, tracing-subscriber, anyhow, parking_lot, clap and subtle become optional behindserver(default).[[bin]] openab-cpand[[test]] ws_lifecyclerequireserver.src/lib.rs:protois always compiled. The other modules are#[cfg(feature = "server")]. A doc block explains the split.src/proto.rs:MIN_RUNTIME_FRAME_BYTES = 1 MiB, the frame ceiling the runtime client in PR B relies on. It is not enforced in this PR: thevalidate()floor and its test move to PR B with the client that needs them (review round 1 F1/F2).src/config.rstests: themax_outbound_queue_bytes <= max_frame_bytescase uses queue 1048576, frame 2097152, and every rejection case asserts its error text instead of a bareis_err().This is the code reviewed as part of #1528 (commit 9455b50, which round 6 F12 found clean), carried onto current main with no conflicts. The changes to it are the test tightening above, deferring the frame-floor validation to PR B, and doc wording that describes the runtime client as a follow-up PR rather than already bundled.
Why this approach?
It keeps one crate as the single source of the wire contract and avoids a separate
openab-cp-protocrate before a second consumer exists. The default build is unchanged, socargo run -p openab-cpand the docs keep working. Landing it first gives B and C a small, already-reviewed base.Alternatives Considered
openab-cp-protocrate: a cleaner boundary, but it adds a crate and a path move with no consumer that needs it yet. It can be extracted later if openab-pty embeds the client.#1528 review findings addressed here
max_frame_bytesbail!string and its substring-only test. Moves to PR B together with the floor validation itself; fix it there (ASCII message, exact-message test, no claim of frame-size negotiation).tests/ws_lifecycle.rsis gated onserversocargo test --no-default-featuresruns the proto tests instead of failing to compile.Validation
Run on macOS (M4, rustc 1.99.1) in a clean copy of this head:
cargo build -p openab-cp --lockedcargo test -p openab-cp --locked: 142 unit + 9 integration passedcargo check -p openab-cp --no-default-features --locked;cargo test -p openab-cp --no-default-features --locked: 16 proto tests passedcargo tree -p openab-cp --no-default-features -e normal: no axum/tokio/hypercargo clippy -p openab-cp --all-targets -- -D warnings(default and--no-default-features);cargo fmt -p openab-cp --checkcleancargo check --workspace,cargo clippy --workspace [--features unified] -- -D warnings,cargo test -p openab-gateway --features acp(417),cargo test -p openab-core --features acp-mcp {acp_mcp::,acp::pool::}(5 / 24),cargo test --features acp(42),cargo build --features unifiedcargo test --workspace: all pass on macOS exceptopenab-coresecrets::tests::resolve_exec_nonzero_exit, which spawns/bin/false(macOS only has/usr/bin/false). This PR does not touchopenab-core; Linux CI covers it.lib.rsfiles and nocrates/openab-cp/src/main.rs→cargo metadata --lockedand rootcargo check --lockedsucceedgit diff origin/main -- Cargo.lockis empty