Client drivers (CONCEPT:EG-KG.ingest.broker-streams-namespaces)¶
B1.7 — thin multi-language client drivers binding the Program-B engine
Methods that had no client surface: the native message broker + append-log streams (EG-275..284/314), RBAC admin (EG-KG.compute.feature), online backup/restore (EG-090), and NL→query (EG-080).
There is no PyO3 / FFI between a client and the engine — the boundary is
out-of-process framed MessagePack over UDS/TCP. So the wire IS the API: every client
(Python, JS, Go) hand-mirrors the serde-tagged Method enum in
crates/eg-types/src/protocol.rs by sending the
variant name + its exact param fields.
Full vs thin, per language¶
| Language | Location | Scope | Tested |
|---|---|---|---|
| Python | epistemic_graph/client.py |
Full — the canonical client agent-utilities uses. B1.7 adds the broker/rbac/admin namespaces + query.nl_query on top of the existing graph/vector/RDF/SQL/txn/stream surfaces. |
Yes — tests/test_pb_clients.py (fake-client wire-shape + live E2E vs the ephemeral engine) + the tests/test_protocol_parity.py drift gate. |
| JS / Node | clients/js |
Thin — ONLY the B1.7 methods, generated-from-the-Method-list. Not a full SDK. | Reference binding (no Node CI harness yet); framing/auth mirror the verified Python transport. |
| Go | clients/go |
Thin — ONLY the B1.7 methods, generated-from-the-Method-list. Not a full SDK. | Reference binding (no Go CI harness yet); framing/auth mirror the verified Python transport. |
The thin clients are honest reference bindings: they carry the transport (framing + HMAC auth + result decode) and the B1.7 method surface, and each README states plainly that the full graph/vector/RDF/SQL API is Python-only. No faked SDK.
Wire contract (all three)¶
- Framing: a 4-byte big-endian length prefix + a MessagePack request
{ id, graph, auth_token, method, params }. - Auth:
auth_token = hex(HMAC_SHA256(auth_secret, str(id)))(empty when no secret). This is the legacy v0 envelope and is what every client here actually speaks today. The server also accepts an opt-in v1 signed envelope (EG-P0-5) — see Service mode — but no client driver signs v1 yet. - Correlation: each response carries the request
id. The Python client demuxes out-of-order responses on one pipelined connection (EG-043); the thin JS client does the same byid; the thin Go client serializes one round-trip at a time (in-order). - Compact results: a top-level MessagePack
binresult is a secondRawlayer and is decoded once more.
Methods covered (B1.7)¶
| Domain | Engine Methods |
Concept | Python | JS | Go |
|---|---|---|---|---|---|
| Broker admin | DeclareExchange DeleteExchange DeclareQueue BindQueue UnbindQueue |
EG-275/276/277/278 | client.broker.* |
✓ | ✓ |
| Broker publish | Publish PublishEx PublishConfirmed PublishIdempotent |
EG-275/279/284/314 | client.broker.publish* |
✓ | ✓ |
| Broker consume | BrokerConsume BrokerAck BrokerReject BrokerAckTag BrokerNackTag SweepExpired |
EG-KG.compute.groups-qos-prefetch-honoring/276/284 | client.broker.consume/ack/reject/… |
✓ | ✓ |
| Streams | StreamDeclare StreamPublish StreamRead StreamTrim StreamCommitOffset StreamCommittedOffset |
EG-283 | client.broker.stream_* |
✓ | ✓ |
| RBAC admin | RbacAdmin (AddRole/RemoveRole/AddGrant/RemoveGrant/List) |
EG-KG.compute.feature | client.rbac.* |
✓ | ✓ |
| Ops | Backup Restore |
EG-090 | client.admin.* |
✓ | ✓ |
| NL→query | NlQuery |
EG-080 | client.query.nl_query |
✓ | ✓ |
Each op is feature-gated on the SERVER: broker (broker + streams), security (RBAC),
redb (backup/restore), nl-query + a configured planner (NL). A build/deploy without one
returns a clear "not available in this build" / "no planner configured" error — never a
panic. NL→query also needs a configured NlPlanner (an OpenAI-compatible endpoint, e.g.
agent-utilities' LLM); the client just carries the text.
Client → Method → engine¶
flowchart LR
subgraph clients["Client drivers (CONCEPT:EG-KG.ingest.broker-streams-namespaces)"]
PY["Python (full)\nepistemic_graph/client.py\n.broker / .rbac / .admin / query.nl_query"]
JS["JS thin\nclients/js"]
GO["Go thin\nclients/go"]
end
PY -- "framed msgpack\n(4B len + {id,graph,auth_token,method,params})\nHMAC-SHA256 auth" --> T
JS -- "framed msgpack" --> T
GO -- "framed msgpack" --> T
T["Transport\nsrc/server/transport.rs\n(UDS / TCP, pipelined, id-demux)"] --> D["dispatch\nsrc/server/dispatch.rs"]
subgraph methods["Method enum · crates/eg-types/src/protocol.rs"]
M_BROKER["Publish* / DeclareExchange /\nDeclareQueue / BindQueue /\nBrokerConsume / BrokerAck / BrokerReject /\nBrokerAckTag / BrokerNackTag / SweepExpired"]
M_STREAM["StreamDeclare / StreamPublish /\nStreamRead / StreamTrim /\nStreamCommitOffset / StreamCommittedOffset"]
M_RBAC["RbacAdmin{op}"]
M_BAK["Backup / Restore"]
M_NL["NlQuery{text,graph}"]
end
D --> M_BROKER
D --> M_STREAM
D --> M_RBAC
D --> M_BAK
D --> M_NL
M_BROKER --> H1["handlers/graph_ops.rs\ncrate::broker (eg-core)\nEG-275..284/314"]
M_STREAM --> H1
M_RBAC --> H2["dispatch.rs → isolation.rbac\neg-core acl · EG-KG.compute.feature"]
M_BAK --> H3["handlers/admin.rs\nredb backup/restore · EG-090"]
M_NL --> H4["handlers/query.rs → NlPlanner\n→ UQL → run_unified · EG-078/080"]
Parity gate¶
tests/test_protocol_parity.py enforces the Python client ⇄ Method enum contract:
every method the client sends must be a real variant, and the set of UNBOUND variants must
equal tests/protocol_unbound_baseline.txt. B1.7 removed the broker/streams/NL/RBAC/backup
entries from that baseline (they now have Python senders) and baselined the remaining
un-bound EG-KG.memory.eg-batch-decay-caller memory/scene/trajectory + CEP ops (deferred to roadmap B3.16 / B3.14).
The JS/Go thin clients are generated from the same Method list by hand and kept in sync
by review against protocol.rs.
See also: Capabilities matrix · Connecting (per-wire guide) · SQL & pgwire · Messaging & Broker · KV-cache (vLLM/LMCache).