Background
PR #3 (SPEC-009 sketch branch) sketched hark dialect publish | query | subscribe as CLI subcommands that only printed canonical (meta ...) frames to stdout and returned success without contacting the daemon or router. Review feedback flagged this as misleading: users got a "successful" no-op, and there is no working pipe-through-hark reply path either because the send path rejects meta performatives. The sketch commands were removed in ec565d8 to stop shipping that misleading surface.
The protocol itself is end-to-end-capable. After PR #3 plus cbcl-router PR #5, the router accepts:
(meta (teach @router (define ...))) — publish
(meta (subscribe (speak? <pattern>))) — subscribe
(meta (unsubscribe)) — drop subscription
(meta (query (speak? <name>))) — query a single dialect
(meta (query (list))) — enumerate
And hark's daemon already handles the receive side: DialectCache::try_install runs incoming teach-pushes through cbcl-rs's R1–R5 pipeline, installs them, and triggers auto re-hello. What's missing is any CLI surface to initiate publish / subscribe / query from a hark agent.
Scope
Wire three CLI commands through local_api → daemon → router, matching the shape of the existing hark reply / error / progress plumbing.
hark dialect publish
- Reads a complete
(define <name> ...) form from --define flag or stdin.
- POSTs to a new daemon endpoint (e.g.
/v1/agents/<handle>/meta/teach).
- Daemon validates the inner define through
cbcl_parser::run_pipeline (R1–R5) before sending — same defense-in-depth as the router does on receipt.
- Daemon writes
(meta (teach @router <define>)) to the router socket and waits for the reply.
- Returns the router's
:ok :digest "..." :name "..." reply to the CLI; CLI prints it.
- Error cases: invalid CBCL (exit 8
cbcl_validation_failed), router connection failure (exit 9), no agent handle (exit 6).
hark dialect subscribe <pattern>
- Pattern is positional; supports the router's
* / <prefix>* / <exact> grammar.
- POSTs to
/v1/agents/<handle>/meta/subscribe.
- Daemon writes
(meta (subscribe (speak? <pattern>))) to the router socket. No reply is expected (fire-and-forget per the existing subscribe path).
- Subsequent matching teach-pushes flow into the existing receive queue and get installed by
DialectCache::try_install (already working).
- Companion
hark dialect unsubscribe writes (meta (unsubscribe)).
hark dialect query <name> (and hark dialect list)
- For
query <name>: writes (meta (query (speak? <name>))), waits for the router's teach-back, installs the define into the local cache, and prints :digest + :name on success or router-does-not-speak on miss.
- For
list: writes (meta (query (list))), prints the joined :names "..." response. (Slice-3 router PR ships the list query type.)
Implementation notes
- Daemon API version: each new endpoint is a schema addition. Bump
LOCAL_API_VERSION again on landing (currently 2).
- The existing meta-frame builders in
src/router.rs (build_meta_teach_frame, build_meta_query_frame, build_meta_subscribe_frame) are already in place and exercised by tests; the wiring reuses them.
- Reuse the existing send path's plumbing: validation, agent handle resolution, error mapping.
send_message_command in src/cli.rs is the closest template.
- New local-api error codes:
dialect_router_error (router refused), possibly dialect_validation_failed if we surface R1–R5 violations distinctly from generic cbcl_validation_failed.
- Tests follow the pattern in
tests/router_integration.rs (mock router, assert outbound frame, assert reply handling).
Non-goals
- Persistent agent-side state for "which dialects have I subscribed to." Subscriptions are router-side; restart = resubscribe.
- A richer
query result type beyond what the router currently returns (:names "a b c" for list; teach-back for speak?).
- A
hark dialect describe <name> UX — depends on the cbcl-router dialect or :description clauses landing, neither of which exists today.
Related
- PR #3 — initial SPEC-009 sketch (this scope was carved out of it)
- cbcl-router PR #5 —
(meta (query (list))) + (meta (unsubscribe)) handlers; the slice-3 protocol additions this issue's CLI commands target.
## Background
PR #3 (SPEC-009 sketch branch) sketched `hark dialect publish | query | subscribe` as CLI subcommands that only printed canonical `(meta ...)` frames to stdout and returned success without contacting the daemon or router. Review feedback flagged this as misleading: users got a "successful" no-op, and there is no working pipe-through-`hark reply` path either because the send path rejects `meta` performatives. The sketch commands were removed in `ec565d8` to stop shipping that misleading surface.
The protocol itself is end-to-end-capable. After PR #3 plus cbcl-router PR #5, the router accepts:
- `(meta (teach @router (define ...)))` — publish
- `(meta (subscribe (speak? <pattern>)))` — subscribe
- `(meta (unsubscribe))` — drop subscription
- `(meta (query (speak? <name>)))` — query a single dialect
- `(meta (query (list)))` — enumerate
And hark's daemon already handles the *receive* side: `DialectCache::try_install` runs incoming teach-pushes through cbcl-rs's R1–R5 pipeline, installs them, and triggers auto re-hello. What's missing is any CLI surface to *initiate* publish / subscribe / query from a hark agent.
## Scope
Wire three CLI commands through `local_api → daemon → router`, matching the shape of the existing `hark reply / error / progress` plumbing.
### `hark dialect publish`
- Reads a complete `(define <name> ...)` form from `--define` flag or stdin.
- POSTs to a new daemon endpoint (e.g. `/v1/agents/<handle>/meta/teach`).
- Daemon validates the inner define through `cbcl_parser::run_pipeline` (R1–R5) before sending — same defense-in-depth as the router does on receipt.
- Daemon writes `(meta (teach @router <define>))` to the router socket and waits for the reply.
- Returns the router's `:ok :digest "..." :name "..."` reply to the CLI; CLI prints it.
- Error cases: invalid CBCL (exit 8 `cbcl_validation_failed`), router connection failure (exit 9), no agent handle (exit 6).
### `hark dialect subscribe <pattern>`
- Pattern is positional; supports the router's `*` / `<prefix>*` / `<exact>` grammar.
- POSTs to `/v1/agents/<handle>/meta/subscribe`.
- Daemon writes `(meta (subscribe (speak? <pattern>)))` to the router socket. No reply is expected (fire-and-forget per the existing subscribe path).
- Subsequent matching teach-pushes flow into the existing receive queue and get installed by `DialectCache::try_install` (already working).
- Companion `hark dialect unsubscribe` writes `(meta (unsubscribe))`.
### `hark dialect query <name>` (and `hark dialect list`)
- For `query <name>`: writes `(meta (query (speak? <name>)))`, waits for the router's teach-back, installs the define into the local cache, and prints `:digest` + `:name` on success or `router-does-not-speak` on miss.
- For `list`: writes `(meta (query (list)))`, prints the joined `:names "..."` response. (Slice-3 router PR ships the `list` query type.)
## Implementation notes
- Daemon API version: each new endpoint is a schema addition. Bump `LOCAL_API_VERSION` again on landing (currently `2`).
- The existing meta-frame builders in `src/router.rs` (`build_meta_teach_frame`, `build_meta_query_frame`, `build_meta_subscribe_frame`) are already in place and exercised by tests; the wiring reuses them.
- Reuse the existing send path's plumbing: validation, agent handle resolution, error mapping. `send_message_command` in `src/cli.rs` is the closest template.
- New local-api error codes: `dialect_router_error` (router refused), possibly `dialect_validation_failed` if we surface R1–R5 violations distinctly from generic `cbcl_validation_failed`.
- Tests follow the pattern in `tests/router_integration.rs` (mock router, assert outbound frame, assert reply handling).
## Non-goals
- Persistent agent-side state for "which dialects have I subscribed to." Subscriptions are router-side; restart = resubscribe.
- A richer `query` result type beyond what the router currently returns (`:names "a b c"` for list; teach-back for `speak?`).
- A `hark dialect describe <name>` UX — depends on the cbcl-router dialect or `:description` clauses landing, neither of which exists today.
## Related
- PR #3 — initial SPEC-009 sketch (this scope was carved out of it)
- cbcl-router PR #5 — `(meta (query (list)))` + `(meta (unsubscribe))` handlers; the slice-3 protocol additions this issue's CLI commands target.