5
0
Fork
You've already forked hark
0

SPEC-009: dialect distribution client — meta verbs, push intake, auto re-hello #3

Merged
hugooconnor merged 13 commits from spec-009/sketch into main 2026年05月11日 13:42:25 +02:00

Summary

Companion sketch to cbcl-router PR #4 (SPEC-009 dialect distribution). Lands the client-side surface so a connected agent can publish dialects, discover them, and accept push announcements from the router — with R1–R5 enforcement on every install and auto re-hello when a new dialect lands.

Two commits on this branch for review clarity:

  • af9e116chore: finish the dangling cbcl-router-clienthark rename in docs and test imports. No behaviour change; pulled in so the SPEC-009 commit stays focused.
  • 2d0e795feat(SPEC-009): the protocol work.

Wire protocol (canonical meta grammar)

Three new frame builders in src/router.rs mirror the meta grammar cbcl-rs already defines — no router-namespaced verbs:

build_meta_teach_frame(define)// (meta (teach @router <define>))
build_meta_query_frame(name)// (meta (query (speak? <name>)))
build_meta_subscribe_frame(pattern)// (meta (subscribe (speak? <pattern>)))

R1–R5 enforcement on install (non-negotiable)

src/dialect_cache.rs is the new DialectCache. Public install path is try_install/2, which runs cbcl_parser::run_pipeline on (meta <define>) before the body enters the cache. R1 (no recursion), R2 (resource bounds), R3 (core preservation), R4 (sigs), R5 (shape) are all checked end-to-end via the upstream pipeline; a rejected push is logged, not cached, but still forwarded to the consumer so the user can see what was offered and investigate.

install_unchecked is #[doc(hidden)] and only used for tests that seed fixture bytes that aren't full R1–R5-clean defines.

Receive-loop wiring + auto re-hello

src/router.rs collapses the inbound binary/text arms into a single process_inbound/4 helper returning InboundOutcome::{Continue, Exit, Installed(name)}. classify_inbound (new in src/cbcl_validation.rs) recognises (meta (teach @<self> (define ...))) and routes pushes to try_install.

When try_install succeeds, the loop appends the new name to the session's advertised Vec and emits a fresh hello on the same socket. The cbcl-router same-pid re-hello path preserves in-flight counts and monitor refs (SPEC-009 register-flavour 2), so no work is lost. Stale-pid terminals are already rejected upstream by cbcl-router's current-session check.

The cache is per-agent today: one DialectCache lives inside each spawn_receive_loop, dying with the WebSocket process on disconnect. Daemon-wide sharing is a follow-up.

Hello + CLI cleanup

SPEC-009 collapses capability ≡ dialect on the router. Matching client-side baggage is gone:

  • build_hello_frame no longer emits :capabilities. Wire payload is now (lang cbcl-router (tell @router "hello" :agent-id ... :dialects (...))).
  • InitArgs drops --capability; --dialect is required.
  • CreateAgentRequest, CreateAgentResponse, AgentStatus, AgentStatusSnapshot, AgentStore::validate_advertisement, insert_connected*, create_router_agent, CreatedRouterAgent — capabilities field removed.
  • AgentError: dropped MissingCapability / DuplicateCapability / InvalidCapability; added MissingDialect.
  • hark daemon status output no longer prints capabilities=[...].

New CLI subcommands

src/cli.rs adds hark dialect publish | query | subscribe. Handlers build the canonical frame and print to stdout — pipeable into hark reply today. Routing them through local_api → daemon → router so hark dialect query foo returns the teach-back payload directly is the next slice.

Tests

  • 89 lib unit tests (incl. 12 new SPEC-009: dialect_cache R1–R5 enforcement, classify_inbound, meta frame builders)
  • 31 integration tests across 6 suites (agent_workflow_cli, daemon_lifecycle, discovery, e2e_mvp, local_api, router_integration) — all touch points updated for the dialects-only API.

cargo test120/120 passing.

Test plan

  • cargo build --tests
  • cargo test — 120/120 across 9 suites
  • Manual smoke: run hark against the SPEC-009 cbcl-router branch, verify push install + auto re-hello over real WebSocket
  • Wire hark dialect query through local_api → daemon → router for inline round-trip

Known follow-ups (deferred)

  • Daemon-wide dialect cache — cache is per-agent today; sharing across sessions in the same daemon is the natural next slice.
  • CLI round-trip wiring — subcommands print frames; next slice routes through local_api so they actually call the router.
  • Client-side digest verification of teach-backs — the cache hashes locally; matching against the digest the router supplies in its teach reply (when present) gives end-to-end content-addressed identity.
  • Client-side SPEC-009 companion spec — the trust model, cache eviction policy, and digest verification deserve their own short spec in specs/.
## Summary Companion sketch to [cbcl-router PR #4](../cbcl-router/pulls/4) (SPEC-009 dialect distribution). Lands the client-side surface so a connected agent can publish dialects, discover them, and accept push announcements from the router — with R1–R5 enforcement on every install and auto re-hello when a new dialect lands. Two commits on this branch for review clarity: - `af9e116` — `chore`: finish the dangling `cbcl-router-client` → `hark` rename in docs and test imports. No behaviour change; pulled in so the SPEC-009 commit stays focused. - `2d0e795` — `feat(SPEC-009)`: the protocol work. ## Wire protocol (canonical meta grammar) Three new frame builders in `src/router.rs` mirror the meta grammar cbcl-rs already defines — no router-namespaced verbs: ```rust build_meta_teach_frame(define) // (meta (teach @router <define>)) build_meta_query_frame(name) // (meta (query (speak? <name>))) build_meta_subscribe_frame(pattern) // (meta (subscribe (speak? <pattern>))) ``` ## R1–R5 enforcement on install (non-negotiable) `src/dialect_cache.rs` is the new `DialectCache`. Public install path is `try_install/2`, which runs `cbcl_parser::run_pipeline` on `(meta <define>)` *before* the body enters the cache. R1 (no recursion), R2 (resource bounds), R3 (core preservation), R4 (sigs), R5 (shape) are all checked end-to-end via the upstream pipeline; a rejected push is logged, **not cached**, but still forwarded to the consumer so the user can see what was offered and investigate. `install_unchecked` is `#[doc(hidden)]` and only used for tests that seed fixture bytes that aren't full R1–R5-clean defines. ## Receive-loop wiring + auto re-hello `src/router.rs` collapses the inbound binary/text arms into a single `process_inbound/4` helper returning `InboundOutcome::{Continue, Exit, Installed(name)}`. `classify_inbound` (new in `src/cbcl_validation.rs`) recognises `(meta (teach @<self> (define ...)))` and routes pushes to `try_install`. When `try_install` succeeds, the loop appends the new name to the session's `advertised` Vec and emits a fresh hello on the same socket. The cbcl-router same-pid re-hello path preserves in-flight counts and monitor refs (SPEC-009 register-flavour 2), so no work is lost. Stale-pid terminals are already rejected upstream by cbcl-router's current-session check. The cache is **per-agent** today: one `DialectCache` lives inside each `spawn_receive_loop`, dying with the WebSocket process on disconnect. Daemon-wide sharing is a follow-up. ## Hello + CLI cleanup SPEC-009 collapses capability ≡ dialect on the router. Matching client-side baggage is gone: - `build_hello_frame` no longer emits `:capabilities`. Wire payload is now `(lang cbcl-router (tell @router "hello" :agent-id ... :dialects (...)))`. - `InitArgs` drops `--capability`; `--dialect` is required. - `CreateAgentRequest`, `CreateAgentResponse`, `AgentStatus`, `AgentStatusSnapshot`, `AgentStore::validate_advertisement`, `insert_connected*`, `create_router_agent`, `CreatedRouterAgent` — capabilities field removed. - `AgentError`: dropped `MissingCapability` / `DuplicateCapability` / `InvalidCapability`; added `MissingDialect`. - `hark daemon status` output no longer prints `capabilities=[...]`. ## New CLI subcommands `src/cli.rs` adds `hark dialect publish | query | subscribe`. Handlers build the canonical frame and print to stdout — pipeable into `hark reply` today. Routing them through `local_api` → daemon → router so `hark dialect query foo` returns the teach-back payload directly is the next slice. ## Tests - **89 lib unit tests** (incl. 12 new SPEC-009: dialect_cache R1–R5 enforcement, classify_inbound, meta frame builders) - **31 integration tests** across 6 suites (agent_workflow_cli, daemon_lifecycle, discovery, e2e_mvp, local_api, router_integration) — all touch points updated for the dialects-only API. `cargo test` → **120/120 passing**. ## Test plan - [x] `cargo build --tests` - [x] `cargo test` — 120/120 across 9 suites - [ ] Manual smoke: run hark against the SPEC-009 cbcl-router branch, verify push install + auto re-hello over real WebSocket - [ ] Wire `hark dialect query` through local_api → daemon → router for inline round-trip ## Known follow-ups (deferred) - **Daemon-wide dialect cache** — cache is per-agent today; sharing across sessions in the same daemon is the natural next slice. - **CLI round-trip wiring** — subcommands print frames; next slice routes through `local_api` so they actually call the router. - **Client-side digest verification of teach-backs** — the cache hashes locally; matching against the digest the router supplies in its teach reply (when present) gives end-to-end content-addressed identity. - **Client-side SPEC-009 companion spec** — the trust model, cache eviction policy, and digest verification deserve their own short spec in `specs/`.
Picks up the dangling references left over from the crate / parent-repo
rename (cbcl-lfe-router-client → hark, cbcl-lfe-router → cbcl-router):
 - README, prompts/elf-ticket.md, specs/config.md, specs/router-protocol.md:
 update mentions of the old repo name.
 - src/main.rs and tests/{daemon_lifecycle,discovery,local_api}.rs:
 update crate imports.
No behaviour change; pulled in here so the SPEC-009 diff that follows
stays focused on the protocol work.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Companion sketch to cbcl-router's SPEC-009 work. Lands the client-side
surface so an agent can:
 - publish a dialect to the router via (meta (teach @router (define ...)))
 - discover one via (meta (query (speak? <name>)))
 - subscribe to push announcements via (meta (subscribe (speak? <pattern>)))
 - install a pushed dialect locally and have hark re-hello automatically
 with the agent's expanded dialects list
## Wire protocol
Three frame builders in src/router.rs mirror the meta grammar cbcl-rs
already defines (no router-namespaced verbs):
 build_meta_teach_frame(define)
 build_meta_query_frame(name)
 build_meta_subscribe_frame(pattern)
## R1–R5 enforcement on install (NON-NEGOTIABLE)
src/dialect_cache.rs is the new DialectCache. Public install path is
try_install/2, which runs cbcl_parser::run_pipeline on (meta <define>)
before the body enters the cache. R1 (no recursion), R2 (resource bounds),
R3 (core preservation), R4 (sigs), R5 (shape) are checked end-to-end via
the upstream pipeline; a rejected push is logged, not cached, but still
forwarded to the consumer so the user can see what was offered.
install_unchecked is #[doc(hidden)] and only used in tests that seed
fixture bytes that aren't full R1–R5-clean defines.
## Receive-loop wiring
src/router.rs splits the inbound branch into a single process_inbound/4
helper returning InboundOutcome::{Continue, Exit, Installed(name)}.
classify_inbound (new in src/cbcl_validation.rs) recognises
(meta (teach @<self> (define ...))) and routes pushes to try_install.
When try_install succeeds, the loop appends the new name to the
session's `advertised` Vec and emits a fresh hello on the same socket
— the cbcl-router same-pid re-hello path preserves in-flight counts and
monitor refs, so no work is lost. Stale-pid terminals are already
rejected upstream (SPEC-009 P2 fix).
The cache is per-agent today: one DialectCache lives inside each
spawn_receive_loop, dying with the WebSocket process on disconnect.
Daemon-wide sharing is a follow-up.
## Hello + CLI cleanup
SPEC-009 collapses capability ≡ dialect on the router; dropping the
matching client-side baggage:
 - build_hello_frame no longer emits :capabilities. The wire payload
 is `(lang cbcl-router (tell @router "hello" :agent-id ... :dialects (...)))`.
 - InitArgs drops --capability; --dialect is required.
 - CreateAgentRequest/Response, AgentStatus, AgentStatusSnapshot,
 AgentStore::validate_advertisement, insert_connected*,
 create_router_agent, CreatedRouterAgent: capabilities field removed.
 - AgentError: MissingCapability/DuplicateCapability/InvalidCapability
 dropped; MissingDialect added.
 - `hark daemon status` output no longer prints capabilities=[...].
## CLI subcommands
src/cli.rs adds `hark dialect publish|query|subscribe`. The current
handlers build the frame and print to stdout — pipeable into
`hark reply` for round-tripping today. Routing them through local_api
→ daemon → router so `hark dialect query foo` returns the teach-back
payload directly is the next slice.
## Tests
 - 89 lib unit tests (incl. 12 new SPEC-009: dialect_cache R1–R5
 enforcement, classify_inbound, meta frame builders)
 - 31 integration tests across 6 suites (agent_workflow_cli,
 daemon_lifecycle, discovery, e2e_mvp, local_api, router_integration)
All 120 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Capability and dialect collapsed on the router, but README examples,
the local-api error code list, and a CLI match arm still referenced
--capability and missing_capability/duplicate_capability/invalid_capability.
Remove the dead surface and rename one stale test fn name.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shows the CLI ↔ daemon ↔ router topology, producer ingress side of
cbcl-router, and cbcl-rs as the shared validation library used by both
the CLI and the daemon.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Producers reach cbcl-router only over HTTP at /ingress/v1/messages; the
WebSocket surface at /agent/v1 is for agents. Name both paths explicitly
in the prose.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Standard targets — build/test/check/lint/clippy/fmt/run/install/uninstall/
clean/doc — wrapping the underlying cargo commands. PREFIX defaults to
$HOME/.local.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Defines the router's protocol surface (speaks?, list-dialects,
subscribe-dialects, unsubscribe-dialects) as a regular dialect that
expands to core ask/tell verbs. Passes R1/R2/R3/R5 through the cbcl-rs
pipeline; the meta-teach wire form round-trips end-to-end. Sibling copy
lives in cbcl-router's repo (drift risk noted; sync manually for now).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier `(ask/tell @router ...)` templates implied a new wire
form and a dispatcher refactor on the router side. Switching the
templates to expand to the existing `(meta ...)` shapes
(`(meta (query (speak? name)))`, `(meta (subscribe (speak? pattern)))`,
etc.) makes the dialect a documentation/validation overlay on the
already-deployed protocol instead — no wire break, no router refactor.
Validates through cbcl-rs's R1/R2/R3/R5 pipeline. Companion change in
cbcl-router PR #5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The cbcl-router dialect was a documentation/validation overlay over
the existing meta wire forms with no current consumer — neither hark
nor the router actually checked anything against it. Drop it and let
it come back together with its first real use (e.g. a hark-side
structural validator over outbound frames, or dialect-driven dispatch
on the router).
Companion change in cbcl-router: keep the list query + unsubscribe op
protocol additions; drop the dialect file + boot teach.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P1: LOCAL_API_VERSION 1 → 2
SPEC-009 changed the /v1/agents schema (capabilities → dialects) and
dropped the capabilities field from responses without bumping the
version constant. The local-API handshake compares CLI and daemon
versions and rejects mismatches, so the old value let a new CLI
silently send incompatible bodies to an old daemon (or vice versa).
Bumping to 2 lets the handshake refuse incompatible peers and emit
daemon_api_incompatible instead of producing a corrupt request.
P2: remove `hark dialect publish | query | subscribe`
These were sketch commands that only printed a meta frame to stdout
and returned success without ever contacting the daemon or router.
There is no working pipe-through-hark-reply path either (the send
path rejects meta performatives), so users got success with no
effect — actively misleading.
Drop the subcommand, its enum, arg structs, and the three handlers.
The `build_meta_teach_frame` / `build_meta_query_frame` /
`build_meta_subscribe_frame` builders in router.rs stay (still
exercised by tests, ready for future daemon plumbing). When real
publish/query/subscribe flows ship, they re-enter via local_api →
daemon → router rather than as one-shot frame-printers.
cargo test: 120 passed (9 suites).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hugooconnor deleted branch spec-009/sketch 2026年05月11日 13:42:25 +02:00
Sign in to join this conversation.
No reviewers
Labels
Clear labels
No items
No labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
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
anuna/hark!3
Reference in a new issue
anuna/hark
No description provided.
Delete branch "spec-009/sketch"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?