Semantic memory: retain what happened, recall what is relevant, reflect to answer.
This module is the public API. Every function takes a Foresight.Context naming
the tenant, isolation mode and bank, so no call can touch memory without saying
whose memory it is — there is no ambient state and no "current bank".
ctx = %Foresight.Context{tenant_id: "default", mode: :mode_a, bank: "notes"}
Foresight.retain(ctx, %{"items" => [%{"content" => "Maria leads the payments migration."}]})
Foresight.reflect(ctx, %{"query" => "who owns payments?"})The three verbs
retain/3 is not an insert. It extracts discrete dated facts from what you give
it, embeds them, and links entities into a graph. One paragraph usually becomes
several independently retrievable facts, and it costs an LLM call.
recall/3 retrieves and ranks. It does not interpret. Use it when your own code
decides what the results mean.
reflect/3 answers. A model searches memory itself, in a loop, choosing what to
look for next from what it has already found. Use it when the answer has to be
assembled from several places.
The gap between the last two is large: on the project benchmark, reflect scores 0.833 against a fixed reader's 0.467 over identical memories (McNemar p = 0.001).
Return values
Every function returns {:ok, result} or {:error, %Foresight.Error{}}. The
error struct carries a :reason atom you can match on, so a missing capability
is distinguishable from a bad request without parsing strings:
case Foresight.reflect(ctx, %{"query" => q}) do
{:ok, answer} -> answer
{:error, %Foresight.Error{reason: :unavailable}} -> :degraded
{:error, %Foresight.Error{reason: :invalid_request, detail: d}} -> {:bad, d}
endGuides
Start with Overview, then Getting started. How recall works explains the ranking and is worth reading before tuning anything.
Summary
Functions
Merge related facts into durable observations.
Retrieve memories relevant to a query, ranked.
Answer a question from memory, searching iteratively.
Store something that happened, extracting facts from it.
Functions
@spec add_bank_background(Foresight.Context.t(), String.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec audit_log_stats(Foresight.Context.t(), map(), keyword()) :: Foresight.Backend.result()
@spec bank_profile(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec bank_stats(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec cancel_operation(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec clear_bank_memories(Foresight.Context.t(), String.t(), map(), keyword()) :: Foresight.Backend.result()
@spec clear_memory_observations( Foresight.Context.t(), String.t(), String.t(), keyword() ) :: Foresight.Backend.result()
@spec clear_observations(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec consolidate(Foresight.Context.t(), term(), keyword()) :: Foresight.Backend.result()
Merge related facts into durable observations.
Runs during retain/3; call it directly to consolidate a bank on demand.
Observations are what search_observations reads during reflect/3 — the
difference between assembling an answer from twenty fragments and drawing it
from one settled statement.
@spec create_directive(Foresight.Context.t(), String.t(), map(), keyword()) :: Foresight.Backend.result()
@spec create_mental_model(Foresight.Context.t(), String.t(), map(), keyword()) :: Foresight.Backend.result()
@spec create_webhook(Foresight.Context.t(), String.t(), map(), keyword()) :: Foresight.Backend.result()
@spec delete_bank(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec delete_directive(Foresight.Context.t(), String.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec delete_document(Foresight.Context.t(), String.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec delete_mental_model(Foresight.Context.t(), String.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec delete_webhook(Foresight.Context.t(), String.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec entity_graph(Foresight.Context.t(), map(), keyword()) :: Foresight.Backend.result()
@spec export_bank_template(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec get_bank_config(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec get_chunk(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec get_directive(Foresight.Context.t(), String.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec get_document(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec get_entity(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec get_memory(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec get_memory_history(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec get_mental_model( Foresight.Context.t(), String.t(), String.t(), map(), keyword() ) :: Foresight.Backend.result()
@spec get_mental_model_history( Foresight.Context.t(), String.t(), String.t(), keyword() ) :: Foresight.Backend.result()
@spec get_operation(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec import_bank_template(Foresight.Context.t(), String.t(), map(), keyword()) :: Foresight.Backend.result()
@spec list_audit_logs(Foresight.Context.t(), map(), keyword()) :: Foresight.Backend.result()
@spec list_banks( Foresight.Context.t(), keyword() ) :: Foresight.Backend.result()
@spec list_directives(Foresight.Context.t(), String.t(), map(), keyword()) :: Foresight.Backend.result()
@spec list_document_chunks(Foresight.Context.t(), String.t(), map(), keyword()) :: Foresight.Backend.result()
@spec list_documents(Foresight.Context.t(), map(), keyword()) :: Foresight.Backend.result()
@spec list_entities(Foresight.Context.t(), map(), keyword()) :: Foresight.Backend.result()
@spec list_memories(Foresight.Context.t(), map(), keyword()) :: Foresight.Backend.result()
@spec list_mental_models(Foresight.Context.t(), String.t(), map(), keyword()) :: Foresight.Backend.result()
@spec list_operations(Foresight.Context.t(), map(), keyword()) :: Foresight.Backend.result()
@spec list_tags(Foresight.Context.t(), map(), keyword()) :: Foresight.Backend.result()
@spec list_webhook_deliveries( Foresight.Context.t(), String.t(), String.t(), map(), keyword() ) :: Foresight.Backend.result()
@spec list_webhooks(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec memories_timeseries( Foresight.Context.t(), String.t(), String.t(), String.t(), keyword() ) :: Foresight.Backend.result()
@spec memory_graph(Foresight.Context.t(), map(), keyword()) :: Foresight.Backend.result()
@spec migrate_bank(Foresight.Context.t(), map(), keyword()) :: Foresight.Backend.result()
@spec not_implemented(String.t()) :: {:error, Foresight.Error.t()}
@spec patch_bank(Foresight.Context.t(), String.t(), map(), keyword()) :: Foresight.Backend.result()
@spec probe_bank_migration(Foresight.Context.t(), map(), keyword()) :: Foresight.Backend.result()
@spec put_bank(Foresight.Context.t(), String.t(), map(), keyword()) :: Foresight.Backend.result()
@spec recall(Foresight.Context.t(), term(), keyword()) :: Foresight.Backend.result()
Retrieve memories relevant to a query, ranked.
{:ok, %{"results" => results}} = Foresight.recall(ctx, %{"query" => "payments migration"})Four searches run concurrently — semantic, keyword, graph and temporal — and are merged into one ranking. See How recall works for the merge, which matters more than most of the searches.
Useful keys: "max_tokens" to budget the result set, "types" to restrict to
e.g. ["observation"], and "trace" => true to get a full explanation of the
ranking including per-stage timings_ms.
@spec recover_consolidation(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec reflect(Foresight.Context.t(), term(), keyword()) :: Foresight.Backend.result()
Answer a question from memory, searching iteratively.
{:ok, answer} = Foresight.reflect(ctx, %{"query" => "why did the migration slip?"})Requires an LLM. The model is given tools — recall, search_observations,
search_mental_models, expand — and decides for itself what to look for next.
Check trace["mode"] when the results disappoint: "agentic" means the loop
ran, "fixed" means the configured model cannot call tools and Foresight fell
back to a single-pass pipeline. The fallback is silent by design, which makes it
easy to mistake for the loop underperforming. See Reflect.
@spec refresh_mental_model(Foresight.Context.t(), String.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec reprocess_document(Foresight.Context.t(), String.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec reset_bank_config(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec retain(Foresight.Context.t(), term(), keyword()) :: Foresight.Backend.result()
Store something that happened, extracting facts from it.
payload carries an "items" list; each item needs "content" and may carry
"context", "type", "tags" and "metadata".
Foresight.retain(ctx, %{
"items" => [
%{"content" => "Deployed v2.3 on Tuesday; the migration took 40 minutes.",
"context" => "ops log",
"tags" => ["deploy"]}
]
})Pass "async" => true to queue the work instead of running it inline — this
requires Oban, and returns an operation id you can poll with
get_operation/3. Without Oban you get {:error, %Foresight.Error{reason: :unavailable}} rather than a crash.
Extraction quality dominates answer quality downstream. If answers are poor,
inspect what was actually stored with list_memories/3 before tuning retrieval.
@spec retain_files(Foresight.Context.t(), term(), keyword()) :: Foresight.Backend.result()
@spec retry_operation(Foresight.Context.t(), String.t(), keyword()) :: Foresight.Backend.result()
@spec update_bank_config(Foresight.Context.t(), String.t(), map(), keyword()) :: Foresight.Backend.result()
@spec update_bank_disposition(Foresight.Context.t(), String.t(), map(), keyword()) :: Foresight.Backend.result()
@spec update_directive( Foresight.Context.t(), String.t(), String.t(), map(), keyword() ) :: Foresight.Backend.result()
@spec update_document(Foresight.Context.t(), String.t(), String.t(), map(), keyword()) :: Foresight.Backend.result()
@spec update_mental_model( Foresight.Context.t(), String.t(), String.t(), map(), keyword() ) :: Foresight.Backend.result()
@spec update_webhook(Foresight.Context.t(), String.t(), String.t(), map(), keyword()) :: Foresight.Backend.result()