Gralkor.Client.HTTP (gralkor v1.1.0)

Copy Markdown View Source

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 — optional Req.Test plug 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 so Gralkor.Connection doesn'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_() with COMBINED_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 background Task in 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.