Client drivers (CONCEPT:EG-KG.ingest.broker-streams-namespaces)¶
The Python package is the complete current client. JavaScript and Go are deliberately thin bindings for the native message broker, append-log streams, RBAC administration, online backup/restore, and NL→query surfaces.
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 — graph/vector/RDF/SQL/txn/broker plus governed modalities and native knowledge streaming. |
tests/test_pb_clients.py, tests/test_modality_stream_clients.py, and the gen_contract --check engine-contract gate. |
| JS / Node | clients/js |
Thin — ONLY the B1.7 methods, generated from the Method list. Not a full SDK. | Current eg2. binding; run the package tests before release. |
| Go | clients/go |
Thin — ONLY the B1.7 methods, generated from the Method list. Not a full SDK. | Current eg2. binding; run go test ./... before release. |
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.
Governed Python namespaces¶
The complete Python client binds the two served protocols directly:
| Namespace | Current operation | Result |
|---|---|---|
client.knowledge |
pull(query, batch_size=..., cursor=...) |
One arrow_ipc payload and an authority-/placement-/snapshot-bound cursor |
client.modalities |
authority() |
Opaque tenant, access-policy, and purpose references derived from the verified request |
client.modalities |
ingest(...) |
Atomic native decode/create/update outcome |
client.modalities |
query(...) |
Bounded page of active or authorized cold records |
client.modalities |
search_documents(...), query_image_region(...), query_similar_images(...), query_audio_window(...), query_video_window(...) |
Bounded native-posting query with exact policy and predicate filtering |
client.modalities |
delete(...), move_to_cold(...), restore(...) |
Versioned governed lifecycle outcome |
client.modalities |
events(...) |
Bounded, ordered, policy-filtered replay events |
client.modalities |
capabilities(modality) |
Exact 12 PASS / 0 N/A component result; release readiness comes from G-14/G-37 evidence |
batch = await client.knowledge.pull(
{"family": "graph", "label": "Capability", "limit": 100},
batch_size=32,
)
arrow_ipc = batch["payload"]
authority = await client.modalities.authority()
page = await client.modalities.query(
"document",
segment_kind="paragraph",
limit=100,
)
KnowledgeStream has one projection (arrow_ipc) and one pull method. The
binding does not expose an alternate projection or direct-family aliases. It validates
the complete query, cursor, and response shape, including matching family and batch
size. The modality binding likewise emits only the current tagged operation shapes and
rejects unknown fields, retired segment kinds, malformed opaque references, drifted
artifact-bundle tiers, and non-certified capability reports. The synchronous client
exposes the same namespaces and method names.
For create-once coordination, Python exposes
await client.nodes.create_if_absent(node_id, properties). It sends the sole
CreateNodeIfAbsent operation and returns True only to the inserting writer; it
does not compose has and add, so there is no client-side check-then-write race.
For ingest, bundle_msgpack is a certified ArtifactBundle encoded as MessagePack;
source_bytes is ephemeral request content. URLs, credentials, filesystem paths,
tenant names, and deployment profiles are not fields in either operation. Connection
and trust configuration remains outside these protocol payloads.
Wire contract (all three)¶
- Framing: a 4-byte big-endian length prefix + a MessagePack request
{ id, graph, auth_token, method, params }. - Auth: every driver requires complete verified-context claims and signs the
sole
eg2.identity/policy envelope. GraphOS binds those claims from the immutable authenticatedGraphSession. There is no anonymous or reduced-claim client mode. See Service mode. - 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 BrokerRenewTag SweepExpired |
EG-KG.compute.groups-qos-prefetch-honoring/276/284 | client.broker.consume/ack_tag/nack_tag/renew_tag/… |
✓ | ✓ |
| 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 |
✓ | ✓ |
Every served build requires security. Other ops remain feature-gated on the server:
broker (broker + streams), redb (backup/restore), and nl-query plus 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.knowledge / .modalities / .broker / .rbac / .admin"]
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 / BrokerRenewTag / SweepExpired"]
M_STREAM["StreamDeclare / StreamPublish /\nStreamRead / StreamTrim /\nStreamCommitOffset / StreamCommittedOffset"]
M_RBAC["RbacAdmin{op}"]
M_BAK["Backup / Restore"]
M_NL["NlQuery{text,graph}"]
M_MODAL["ServedModality{op}"]
M_KNOW["KnowledgeStream{request}"]
end
D --> M_BROKER
D --> M_STREAM
D --> M_RBAC
D --> M_BAK
D --> M_NL
D --> M_MODAL
D --> M_KNOW
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"]
M_MODAL --> H5["handlers/modality.rs\nverified governed serving"]
M_KNOW --> H6["handlers/knowledge_stream/{mod,families,stream}.rs\nsole Arrow IPC result stream"]
Parity gate¶
RF-RULING-003 made the contract itself the gate. crates/eg-capabilities declares one
MethodDescriptor per Method variant, and
cargo run -p eg-capabilities --features contract --bin gen_contract -- --check
regenerates contract/methods.json, contract/schemas/*.json, contract/receipt.json,
docs/capabilities.generated.md and epistemic_graph/generated/*.py in memory, then
byte-diffs the committed tree. "Does the Python client send this method" is now the
descriptor's consumer_profiles field, and a method with no consumer is
stability: internal on its own row — so the old
tests/protocol_unbound_baseline.txt ratchet is deleted rather than re-baselined.
crates/eg-capabilities/tests/contract_registry.rs proves the registry and the Method
enum are a bijection with no baseline, and tests/test_generated_client_contract.py
(pure stdlib) proves the generated Python surface is exactly the published profile.
The JS/Go thin clients are still hand-maintained; their ConsumerProfile values exist in
the descriptor but no generator emits them yet.
See also: Capabilities matrix · Connecting (per-wire guide) · SQL & pgwire · Messaging & Broker · KV-cache (vLLM/LMCache).