gralkor

OTP supervisor for Gralkor — a temporally-aware knowledge-graph memory service (Graphiti + FalkorDB) wrapped as a Python/FastAPI server.

Embed Gralkor.Server in your Jido (or any Elixir) supervision tree. The GenServer spawns the Python server as a Port, polls /health during boot, monitors it, and handles graceful shutdown. Your application talks to it over HTTP on a loopback port.

Prerequisites

The Python source ships inside the package (priv/server/); no separate clone or Docker image needed.

Auth: the server binds to loopback and expects its consumer to supervise it — so there is no authentication. All endpoints are mounted on a single router with no middleware. If a multi-host or shared-service deployment ever changes the threat model, add a bearer-token dependency on the Python side and attach Authorization: Bearer … on the client.

Install

def deps do
  [
    {:gralkor, "~> 0.1"}
  ]
end

During pre-release iteration, path-dep instead:

{:gralkor, path: "../gralkor/ex"}

Installing into a Jido agent (e.g. Susu2)

Jido consumers embed Gralkor in their own supervision tree and talk to it over loopback HTTP. The consumer side — Susu2.Gralkor.{Client, Plugin, Connection} + memory actions — is the canonical pattern; this package is what they supervise.

  1. Add the dep (see above).

  2. Let gralkor supervise its own server. The :gralkor application auto-starts Gralkor.Server under Gralkor.Supervisor whenever GRALKOR_DATA_DIR is set in the environment. Consumers do not list Gralkor.Server as a child — double-supervising raises already started. Add any consumer-side health gate (e.g. Susu2.Gralkor.Connection) as normal children after that:

    # lib/susu2/application.ex
    def start(_type, _args) do
      children = [
        Susu2.Users,
        Susu2.Gralkor.Connection,      # boot-readiness gate + health monitor
        Susu2.Jido,
        ExGram,
        {Susu2.Bot, [method: :polling, token: bot_token()]}
      ]
    
      Supervisor.start_link(children, strategy: :one_for_one, name: Susu2.Supervisor)
    end

    OTP guarantees the :gralkor app boots before the consumer app (dep order), so Gralkor.Server is already up by the time the consumer's children start. Gralkor.Server.init/1 is non-blocking ({:continue, :boot}) — the Python child comes up in parallel, so a consumer-side health poller is still the right place to wait for readiness. Gralkor.Server reads its config from env vars (Gralkor.Config.from_env/0).

  3. Set env vars (e.g. in a .env file sourced at boot, or via systemd/container config):

    export GRALKOR_DATA_DIR=/var/lib/susu2/gralkor
    export GOOGLE_API_KEY=<your-key>          # or ANTHROPIC/OPENAI/GROQ
    # optional:
    # export GRALKOR_URL=http://127.0.0.1:4000  # default

    The consumer reads GRALKOR_URL and writes it into its own app env (e.g. Application.put_env(:susu2, :gralkor, url: ...)) for the HTTP client.

  4. Wire the plugin + actions on your agent:

    # lib/susu2/chat_agent.ex
    use Jido.Agent,
      name: "susu2_chat",
      strategy:
        {Jido.AI.Reasoning.ReAct.Strategy,
         tools: [Susu2.Gralkor.Actions.MemorySearch, Susu2.Gralkor.Actions.MemoryAdd]},
      default_plugins: %{__memory__: false},
      plugins: [{Susu2.Gralkor.Plugin, %{}}]

    default_plugins: %{__memory__: false} disables Jido's built-in memory plugin so Susu2.Gralkor.Plugin owns the :__memory__ state slot. The plugin hooks ai.react.query (auto-recall) and ai.request.completed / ai.request.failed (auto-capture). The plugin and actions both call through Susu2.Gralkor.Client — swap the impl to Susu2.Gralkor.Client.InMemory in test config, keep Susu2.Gralkor.Client.HTTP for dev/prod.

    Session identity. Gralkor's capture buffer is keyed by session_id, which the plugin takes from agent.state.__strategy__.thread.id (the current Jido.AI.Thread). One Jido conversation thread per Gralkor session — concurrent agents for the same principal never collide on the buffer, and the session rotates naturally when the thread rotates. group_id is the sanitized agent.id (per-principal graph partition).

  5. Verify boot.iex -S mixcurl http://127.0.0.1:4000/health{"status":"ok",…}. Send a message through the bot; watch for POST /recall then (after the capture idle window) [gralkor] episode added … in the logs.

No Docker, no separate Gralkor service. mix deps.get + iex -S mix brings the whole memory stack up.

Usage

Add Gralkor.Server to your supervision tree and configure via env vars:

# application.ex
children = [
  # ... your other children
  Gralkor.Server
]

Required env vars:

Optional:

HTTP endpoints

Your application talks to Gralkor over HTTP:

All endpoints are unauthenticated — see the Auth note above.

Lifecycle

Gralkor.Server:

Running locally

From ex/:

export GRALKOR_DATA_DIR=/tmp/gralkor-dev
export GOOGLE_API_KEY=...           # or ANTHROPIC_API_KEY / OPENAI_API_KEY / GROQ_API_KEY
iex -S mix

curl http://127.0.0.1:4000/health should return 200.

License

MIT.