Real Gralkor.Client implementation over HTTP using Req.
Reads config from Application.get_env(:gralkor, :client_http):
:url— required. Base URL of the Gralkor server (e.g."http://127.0.0.1:4000").:plug— optionalReq.Testplug tuple for stubbing in tests. Unset in production so Req hits the network directly.
No auth: Gralkor is expected to run under the consumer app's supervision tree, bound to loopback. The consumer owns the trust boundary.
Per-endpoint receive_timeouts, calibrated to the workload:
/health(2 s) — cheap liveness check; tight soGralkor.Connectiondoesn't flap when the server is under LLM load./recall(5 s) — fast graph search (graphiti.search()— RRF, edges only) plus one small LLM interpretation call. Typical ~1–2 s; 5 s means something's actually wrong./tools/memory_search(10 s) — slow graph search (graphiti.search_()withCOMBINED_HYBRID_SEARCH_CROSS_ENCODER— cross-encoder reranking + BFS, facts + entity summaries) plus LLM interpretation. The cross-encoder dominates; 5 s drops legitimate results and the LLM hallucinates "no memory found". 10 s is the working ceiling./capture(5 s) — server returns 204 immediately after buffering./session_end(5 s) — server returns 204 immediately after scheduling the flush./tools/memory_add(60 s) — Graphiti entity/edge extraction is slow; only reached from a backgroundTaskin the consumer, so the agent never waits.
Outgoing JSON bodies are normalised before encoding — in particular,
Elixir tuples are converted to lists. ReAct strategy event traces
(shipped in /capture) contain {:ok, _} / {:error, _} tool
results that Jason cannot encode natively; without normalisation
every capture would raise.
Returns {:error, reason} on non-2xx or transport failure; raises on
missing config or blank session_id. Callers let those surface.