Install

# mix.exs
def deps do
  [{:foresight, "~> 0.1", hex: :foresight_memory}]
end

That is the whole dependency line. The hex: option is there because the package is published as foresight_memory — the plain name belongs to an unrelated library on hex. Nothing else is affected: the application is :foresight, the modules are Foresight.*, and you write alias Foresight as you would expect.

Foresight compiles and boots with no configuration at all — you can verify that before writing any config:

{:ok, _pid} = Foresight.Supervisor.start_link(name: :foresight)

This is worth stating explicitly because for a long time it was not true. The HTTP modules referenced plug while declaring it optional, so a bare install failed with a CompileError, and the default reranker demanded the whole Nx/Bumblebee stack, so even after compiling it would not start. Both are fixed, and there is a test in the default suite that fails if either regresses.

With no configuration you get an in-memory-free, LLM-free skeleton: it starts, but it cannot embed, rerank, or store anything. Useful for confirming the wiring and not much else.

A working configuration

Memory needs somewhere to live. Foresight uses PostgreSQL with pgvector.

# config/config.exs
config :foresight,
  repo: [enabled: true],
  engine: [
    embedder: Foresight.Embedders.Bumblebee,
    reranker: Foresight.Rerankers.Passthrough,
    llm: Foresight.LLMs.LlmCore
  ]

config :foresight, Foresight.Repo,
  url: "postgresql://postgres:postgres@localhost:5432/my_app_memory"
# mix.exs — capabilities you enable, you must also depend on
{:bumblebee, "~> 0.7"}, {:nx, "~> 0.12"}, {:exla, "~> 0.12"},  # local embeddings
{:llm_core, "~> 0.5"}                                          # LLM access

Then run the migrations:

mix ecto.create
mix ecto.migrate

The one trap

embedder and reranker live under engine:. Set them at the top level and nothing happens — silently. No error, no warning; your configuration is simply ignored and the defaults stand.

config :foresight, embedder: MyEmbedder          # ✗ silently ignored
config :foresight, engine: [embedder: MyEmbedder] # ✓

The failure surfaces much later and points somewhere else entirely — usually as a preflight complaint about :nx, because the ignored setting left the default in place. If a config change appears to have had no effect, check the nesting first.

The preflight is on your side

Enable a capability without its dependency and Foresight refuses to start, by name:

** (RuntimeError) Foresight capability preflight failed:
   optional dependency :nx is not available.
   Add/fetch the dependency before enabling the related capability.

This is deliberate. The alternative is booting successfully and failing on the first request that happens to need embeddings — at which point the error appears somewhere far from its cause.

Your first memories

alias Foresight.Context

ctx = %Context{tenant_id: "default", mode: :mode_a, bank: "notes"}

{:ok, _} = Foresight.put_bank(ctx, "notes", %{"name" => "Notes"})

{:ok, _} =
  Foresight.retain(ctx, %{
    "items" => [
      %{"content" => "Deployed v2.3 on Tuesday. The migration took 40 minutes.",
        "context" => "ops log"}
    ]
  })

{:ok, result} = Foresight.recall(ctx, %{"query" => "how long did the migration take?"})

A bank is an isolated namespace of memory — one per agent, per user, per project, whatever your unit of "a memory" is. Banks do not share retrieval; searching one never returns another's contents.

The context carries tenant, isolation mode and bank on every call. There is no ambient state and no "current bank": if a call touches memory, it says whose. This is tedious for exactly as long as it takes to appreciate that no request can accidentally read another tenant's data.

Retain is not an insert

retain extracts. One paragraph typically becomes several dated facts, each independently retrievable, with entities pulled out and linked.

That means it costs an LLM call and takes real time — and it is where quality is won or lost. When fact extraction was subtly wrong in this library (it dropped the When: clause from every stored fact), end-to-end answer accuracy sat at 0.467. Fixing extraction alone took it to 0.711. Nothing about retrieval or reasoning changed.

If you are debugging poor answers, look at what retain actually stored before you look at anything else. Foresight.list_memories/3 shows you.

Then reflect

{:ok, answer} = Foresight.reflect(ctx, %{"query" => "what happened with v2.3?"})

reflect needs an LLM configured. See Reflect for the loop it runs and how to confirm it did.