Skip to main content
Takes a block of text (or, with image, an image: see Images), stores it as an artifact, uses an LLM to extract a small knowledge graph (entities and edges) whose edges are bi-temporal facts, stores everything in the database, and embeds the entities and edges into the dataset’s vector datasets. A text of more than one chunk (1024 tokens by default) is a document: it is chunked, and each chunk is extracted, stored and embedded on its own, and also summarized: the summary is an artifact of its own (a child of the chunk) that is embedded into a vector index of its own. The temporal model is described in Temporal facts.

Request

  • user_id (required): the user who owns the dataset. 400 if missing.
  • text (required unless image is given): non-empty string, at most 1 MiB. If it is larger than the chunk size (chunk_size, 1024 tokens by default) it is split into overlapping chunks: see Chunking.
  • image (optional): a base64-encoded image, instead of text. The request then goes through the separate image pipeline (OCR of the title and text blocks, and a theme such as plot or graph), with a different response; text and kind must not be given with it.
  • dataset (optional): name of the dataset to store into; created if it does not exist. Defaults to main. Entities and edges are deduplicated and reconciled only within the dataset.
  • kind (optional): the artifact kind, knowledge or episode (case-insensitive). Defaults to knowledge: a text that is not strictly episodic (a reference text, a note, …). Use episode for a Graphiti-style episode, a text anchored in time (a message, a news item). Any other value returns 400. The kind is stored on the artifact (gb_artifacts.kind) and echoed in the response; it does not change how the text is extracted or reconciled yet.
  • reference_time (optional): RFC 3339 timestamp of when the text was written or observed. Relative dates in the text (“yesterday”, “last March”) are resolved against it. Defaults to the current time.
  • session_id (optional): the user/agent session the call belongs to, a string you choose (at most 128 bytes, no control characters; 400 otherwise). It is stored on the artifact (gb_artifacts.session_id) and recorded against every entity and edge the artifact yields, in gb_entity_sessions and gb_edge_sessions, including entities and facts that already existed and so were not written again. See Sessions. Also accepted with image.
  • timeseries (optional): timeseries to store and anchor to what the text yields, {"series": [{"key": "AAPL.close", "points": [["2025-01-31T00:00:00Z", 222.0]], "anchors": [{"entity": {"name": "Apple"}}]}]}. A series without anchors is anchored to the artifact; entity and edge anchors are looked up after the text is extracted, so they may name entities that the text will yield. 400 if malformed (before any LLM call) or together with image, 422 if an anchor that must match finds nothing (nothing is stored). See Timeseries. timeseries.fetch ({"providers": ["prices"], "before": "30d", "after": "0"}, every field optional; alone or next to series) makes the server fetch series from the timeseries providers it was started with, for the entities of the text (503 if it has none): see Timeseries. timeseries.observations (true; with series or fetch) makes remember also derive a series_observation artifact from each series it stored, extracted into the graph like a text: see Timeseries.
  • models (optional): an object that picks the LLM model of each LLM-invoking stage of the pipeline; see Choosing the model of each stage. 400 for an unknown stage or a malformed model name.
  • reasoning (optional): an object that picks the reasoning (thinking) level (none, low, medium or high; case-insensitive) of the LLM stages: extract_graph, summarize_chunk, resolve_edges, resolve_series, detect_geo, extract_image. A stage left out runs with none: no thinking, so no thinking tokens are paid for by default. See Reasoning. 400 for another stage (web_search cannot think) or an unknown level.
  • web_search (optional, default false): after the text is stored, search the web (with Anthropic’s cloud-based web search tool) for the latest world events about its key entities, and remember what is found: each result becomes an artifact of its own, of kind web_search, which saves the query that was issued and hangs off the artifact the entity came from, and the entities and facts in it join the graph like any other. Only available with an Anthropic API key (503 otherwise); 400 with image. See Web search.

Response

200 OK:
  • remember_run_id: the run log of the call (gb_remember_runs.id).
  • artifact_id, kind, valid_at, created_at: the artifact, its kind, and its event time (the reference_time) and ingestion time.
  • session_id: the session the artifact was attached to; omitted if the request had none.
  • entities are what the LLM extracted from this text (after normalization).
  • edges are the facts from this text in their stored state; if a new edge repeated a stored one, the stored edge is returned. Edge fields invalid_at, expired_at are omitted when null.
  • invalidated_edges lists IDs of edges closed because of this artifact (see Temporal facts). Always an array.
  • series: only with timeseries: one item per series (id, key, created, points, point_count, stats, anchors, …). See Timeseries.
  • series_fetches: only with timeseries.fetch: one item per (entity, provider) the LLM chose (entity, provider, query, reason, from, to, the series stored, and an error if the provider failed). See Timeseries.
  • series_observations: only with timeseries.observations: one item per series observed (series_id, key, parent_artifact_id, query, from, to, the generated text, the artifact_id stored, its entities and edges, and an error if it failed). See Timeseries.
  • geo: only when the server has a geo resolver: one item per entity of the text the LLM judged to be a place (entity, entity_type, entity_id, kind, query, resolved, latitude, longitude, display_name, country, country_code, region, provider, and an error if the resolver or the storage failed). See Geo.
  • web_searches: only with web_search: one item per key entity that was searched for (entity, query, parent_artifact_id, the web_search artifact_id, the sources, and the entities and edges the result yielded; entities and edges above stay those of the text). See Web search.
  • chunks, embedded_chunks, embedded_summaries: only for a text that was chunked. artifact_id is then the parent artifact (the whole text), chunks lists the chunk artifacts (artifact_id, index, tokens, overlap_tokens, start_offset, end_offset, entities, edges, and the summary of the chunk: {"artifact_id", "text", "model", "reasoning", "inference_id"}, the summary artifact with the inference, i.e. the model and reasoning level, that wrote it), entities, edges and invalidated_edges are those of all the chunks, embedded_chunks counts the chunk vectors written and embedded_summaries the summary vectors.
  • embedded_entities and embedded_edges are the number of vectors written to the dataset’s entity and edge indexes (see Embed). Both are omitted when nothing was embedded (no OPENAI_API_KEY). embedded_entities counts the entities the text mentions, including those that already existed, and embedded_edges the facts it states, including repeated ones; they can be more than the number of new rows.
Errors are returned as {"error": "..."}:

How it works

This is the pipeline for a text that fits one chunk. A longer text runs the same steps on each of its chunks, one after the other; see Chunking. All steps use the LLM from internal/ai (Anthropic Messages API, default model claude-haiku-5-5, unless the request picks another one for a stage: see Choosing the model of each stage). Code is in internal/memory.
  1. Classify complexity (classifyComplexity, 1 call to Jev, only if a Jev API key is configured: jev_api_key / JEV_API_KEY): Jev, TypeSafe’s system 1 model (package internal/sys/jev, POST /v1/systemone), answers one choice question: is the text simple (up to 5 entities), medium (6 to 20) or complex (more than 20) in the number of entities it will yield? This predicts how much compute the later steps need per entity and edge. The answer’s confidence (0 to 1) and the class probabilities are kept. For now the result is only logged (jev: remember text complexity: complexity, confidence, latency_ms). It is advisory (a failure is logged and the request goes on), it always runs on Jev (it is not in models), and it sees at most the first 32 KiB of the text. In an eval trajectory it is the classify_complexity step, whose extra has latency_ms and started_at.
  2. Extract the graph (Extract, 1 LLM call, max 4096 output tokens): given the reference time and the text, the model is asked to return only JSON of the form:
    Code fences or surrounding prose around the JSON are tolerated. An edge without valid_at gets the reference time. 1b. Summarize the chunk (summarizeChunk, 1 LLM call, max 512 output tokens; only for a chunk of a chunked text, right after the chunk’s extraction, before anything is stored): the model writes a two-to-four sentence summary of the chunk. It runs on its own model and reasoning level (summarize_chunk). The summary, with the inference that wrote it (the model as the API reports it, and the level), is stored in step 3 and embedded in step 4. The graph is not extracted from the summary. See Chunking.
  3. Resolve edges (resolveEdges, 1 LLM call only if needed): new edges are compared with the candidate facts: stored live edges that touch the same entities. Exact repeats are detected without the LLM; the model flags remaining duplicates and contradictions.
  4. Store (Store): everything is written in a single transaction:
    • the raw text and its reference time go into gb_artifacts (with its kind and session_id);
    • entities are upserted into gb_entities and linked to the artifact in gb_entity_artifacts, including stored entities that an edge names but the extraction did not list;
    • edges are inserted into gb_edges, anchored to the artifact that created them (artifact_id) and linked to it in gb_edge_artifacts; a duplicate of a stored edge is not inserted but the new artifact is added to that edge’s gb_edge_artifacts links; duplicates are merged and contradicted edges are closed (see Temporal facts);
    • with a session_id, every entity linked to the artifact is also linked to the session in gb_entity_sessions, and every edge linked to it in gb_edge_sessions (see Sessions). Both happen for duplicates too, so the sessions an entity or fact was discovered in are all kept.
    • if the text is a chunk of a longer one, its summary is written as an artifact of kind chunk_summary, a child of the chunk, with the inference_id of the call that wrote it.
  5. Embed (WithVectors, 1 embeddings request, only if an OpenAI key is configured): the entities this text mentions and the edges it states (and, if the text is a chunk of a longer one, the chunk’s own text, into a third vector dataset, and the chunk’s summary, into a fourth: see Chunking) are embedded together with text-embedding-3-small: an entity as Name (type): description, an edge as its description. This happens inside step 3, after the rows are written and before the transaction commits, so a failure stores nothing. All the texts of the artifact go to the embeddings API as one batch, in one request, in this order: the entities, the edges, then (for a chunk) the chunk’s text and its summary. The vectors come back in the same order and are told apart by position, so adding the chunk and its summary costs no extra HTTP round trip. Because it is one request, it is recorded as one gb_inferences entry (and one embed step in an eval trajectory) whose token count and cost are the total of all the texts: the share of the chunk or of its summary is not recorded separately (the summary’s own LLM call, summarize_chunk, is). Right after the commit the vectors are written, with Upsert, one call per vector dataset: dataset-<id>-entities (vector entity-<entity id>), dataset-<id>-edges (vector edge-<edge id>) and, for a chunk, dataset-<id>-chunks (vector chunk-<chunk artifact id>) and dataset-<id>-summaries (vector summary-<summary artifact id>). Vectors are keyed by ID, so remembering an entity or fact again overwrites its vector. Details, metadata and failure handling: Vector storage.
4b. Timeseries (only with timeseries): the supplied series are written inside step 3’s transaction, after the entities and edges, and anchored: to the artifact, and to the entities and edges that their selectors find. A series that cannot be anchored stores nothing. For a chunked text they are stored after the last chunk, anchored to the parent. See Timeseries. 4c. Geo (only when the server has a geo resolver, geo = true or serve --geo): the entities of the text that may be places are sent to an LLM (the detect_geo stage), which says which are a city, a country, a region or a landmark; the geo resolver (OpenStreetMap’s Nominatim by default) is asked for their coordinates, and they are stored in gb_entity_geo, keyed by the entity. Entities that already have geo metadata are not looked up again. It runs after the text was stored, so a failure in it never fails the request. See Geo.
  1. Web search (only with web_search, and only with an Anthropic API key): the key entities of the text are searched for on the web and the results are remembered as web_search artifacts, each with its query and its parent (the artifact the entity came from), and their own entities and edges. It runs after the text was stored, so a failure in it never fails the request. See Web search.
  2. Timeseries fetch (only with timeseries.fetch, and only when the server has timeseries providers): an LLM decides from the providers’ descriptions which provider fetches series for which entity of the text (only the entities of the text itself, not those a web search found), the providers’ callbacks run, and the series are stored and anchored to the entity and the artifact it came from. It runs after the text was stored, so a failure in it never fails the request.
  3. Series observations (only with timeseries.observations, and only for series that were stored in this request): a text is generated in Go from the stats of each series (at most 10) over the period the request wrote, extracted with the extract_graph model, reconciled and stored as a series_observation artifact, a child of the artifact that anchors the series. It runs last, after the text was stored, so a failure in it never fails the request. See Timeseries.
Steps 1, 1b and 2 each run on their own model. The request makes 1 LLM call, or 2 when stored edges need to be compared (for a chunked text, per chunk: one more for the summary), plus 1 embeddings request, or 2 when the candidate search also embeds the endpoints of the new facts (only for a dataset that already exists). If any step before the commit fails, nothing is stored. If the vector write after the commit fails, the request returns 502 but the artifact, entities and edges are already saved; the vectors are written again the next time the entities and facts are mentioned.

Choosing the model of each stage

The LLM-invoking stages of the pipeline can each run on a different model, for instance a stronger model to extract and a cheaper one to resolve. The caller picks them per request with models, a map from a stage name to a model name:
The rules:
  • models is optional, and so is every stage in it. A stage with no model (missing, or an empty string) uses the default model of the server’s LLM client (claude-haiku-5-5). So {"models": {"extract_graph": "m"}} changes only the extraction; resolve_edges stays on the default. A request without models runs everything on the default.
  • The choice is per request: nothing is stored server-side, and the next call starts from the defaults again.
  • A model name is any string of at most 128 bytes without whitespace or control characters; an unknown stage is a 400. gligabao does not check that the provider knows the model: one it does not know fails the LLM call (502, nothing stored). The price of a model that is not in the price table (see [prices] in gligabao.toml) is recorded as 0.
  • A stage in models that the request does not run is ignored (resolve_edges when there is nothing to compare with, web_search without the web_search flag, resolve_series without timeseries.fetch, detect_geo without a geo resolver, extract_image for a text, summarize_chunk for a text that is not chunked, extract_graph, summarize_chunk and resolve_edges for an image).
  • Every call is recorded in gb_inferences (Inferences) with the model that answered, the stage and the reasoning level, so the cost of each stage is known; an eval trajectory shows the model of each LLM step (model_name) and the requested models in extra.input.
The model of the knowledge. The model that runs the extract_graph stage is the model of what it extracts: every entity and edge of the extraction carries it as model_id (see Graph export), and it is registered in gb_models if it is new. It is the model name as the provider’s API reports it (a dated snapshot, say, when the request named an alias), never anything the LLM wrote in its output. The resolve_edges model only decides which new facts are duplicates or contradictions, so it does not appear on the knowledge: an edge’s model_id is the model that extracted it, even if another model decided that an older edge was contradicted. For a chunked text each chunk is extracted with the same extract_graph model, so all its entities and edges carry it. A fact that is merged into a stored edge keeps that edge’s model_id, and a stored entity keeps its own (see Graph export), so one dataset can hold knowledge from several models when the calls used different ones.

Reasoning

Every LLM stage runs with a reasoning (extended thinking) level, which the caller picks per request with reasoning, a map from a stage to none, low, medium or high. The stages that take one are extract_graph, summarize_chunk, resolve_edges, resolve_series, detect_geo and extract_image (any other stage is a 400; web_search and Jev do not think). The default is none (no thinking) for every stage, to avoid the cost; a request turns it on per stage:
(query takes a level too, also none by default, see Query.) A level turns on the model’s extended thinking with a token budget (low 1024, medium 4096, high 16384), which is added on top of the answer’s own max_tokens; thinking tokens are billed as output tokens like any other. A model that has no extended thinking (the Claude 3 and 3.5 families) is called without it, and the run records none. Every call is a inference that records the level it ran with (gb_inferences.reasoning) next to the model, the stage, the tokens and the cost. The summary artifact of a chunk references its run (gb_artifacts.inference_id) and chunks[].summary returns model, reasoning and inference_id, so summaries written by different settings can be told apart and compared. The model is the one the API reports, as for the model of the knowledge above; it is registered in gb_models if it is new. From Go: memory_core.WithModels(memory_core.Models{memory_core.StageExtractGraph: "m"}) is an option of memory_remember.Remember and memory_image.RememberImage; memory_core.ResolveModels validates a user-supplied map (the HTTP handler does); memory_core.WithReasoning(memory_core.Reasoning{memory_core.StageSummarizeChunk: ai.ReasoningLow}) and memory_core.ResolveReasoning do the same for the reasoning levels. From an eval: see Evals and ATIF trajectories and evals/knowledge/README.md.

Candidate facts (load_candidates)

Before resolving, Remember loads the candidates: the facts already stored in the dataset that the new facts might repeat or contradict (loadCandidates). In an eval trajectory this is the load_candidates tool step, which lists them and how each was found. What qualifies. A stored edge is a candidate if it
  • belongs to the same user’s dataset (nothing is read across datasets),
  • is still live (expired_at IS NULL; a fact already superseded is never compared again),
  • touches (as source or target) an entity that is an endpoint of one of the new edges, found in either of two ways (below).
They are returned newest first, capped at 50 (maxCandidates). A dataset that does not exist yet has none. Example: the new fact “Alice lives in Berlin” has the endpoints Alice and Berlin, so every live fact about Alice or Berlin is a candidate, including “Alice lives in Paris”. What they are used for. They are the only stored facts the resolution looks at. Exact repeats are merged in Go; the other new facts and the candidates go to the LLM, which flags semantic duplicates and contradictions (see Temporal facts). With no candidates there is no resolution LLM call. Anything not loaded here can never be merged or invalidated by this call. Two lookups, merged by edge ID.
  1. By name (always): a SQL lookup by normalized entity name (lowercased, whitespace-collapsed) of the endpoints (graph.LoadCandidateEdges).
  2. By similarity (only with WithVectors, i.e. an OpenAI key): each endpoint is embedded as an entity is (Name (type): description; type other if the extraction did not list it) and searched in the dataset’s entity index (dataset-<id>-entities). The 3 closest stored entities per endpoint with a cosine similarity of at least 0.75 are kept, except one with the same normalized name as the endpoint (the name lookup has it). The live facts touching those entities are loaded the same way as in 1. This finds aliases: a text saying “Alicia” finds the stored “Alice Smith” and her facts.
The two result sets are merged by edge ID: an edge found by both appears once, the merged list is sorted newest first and cut to 50 (so an alias match can lose its place to newer facts). In the trajectory each fact has found_by: name, similar or name+similar. The similarity lookup costs 1 embeddings request (recorded as a call like any other embedding spend, and shown as the search_similar_entities step with its tokens and cost) and is only an aid: the vector store is not updated atomically with the database (see Vector storage) and its metadata has no validity, so the matched entities are only used to look up their facts in the database, which filters to live edges. If the embedding or the search fails, the request does not fail: a warning is logged, the step records the error and the candidates are those of the names alone. The threshold (0.75) and TopK (3) are first guesses to tune with the evals. Remaining limits:
  • Edges are not searched by similarity: a paraphrased fact about entities that match neither by name nor by similarity is not a candidate.
  • A busy entity can still push old facts out of the newest-first cap of 50.
  • The endpoints are embedded in a request of their own, and Store embeds the entities and edges again afterwards. Sharing one request is possible but not done yet.

Next step: similarity search over edges

Querying the edge index (dataset-<id>-edges) with the new facts’ descriptions would rank candidates by relevance instead of recency, so the cap of 50 keeps the right facts, and would reach paraphrases that share no entity. Worth adding if the evals show the cap or the missing reach losing facts; it would join the same merge by edge ID (found_by would get a third value).

Normalization and deduplication

  • Entity type is lowercased (empty becomes other).
  • Edge relation is lowercased with spaces replaced by _.
  • Entities are unique on (lowercased, whitespace-collapsed name, type). Re-submitting an entity updates its description only if the new description is non-empty.
  • Edge endpoints resolve to an entity from this text, else a stored entity of any type with the same name, else a new entity of type other.
  • A new edge with the same source, target and relation as a stored edge that is still true is a duplicate and is not inserted again.
  • Sending the same text twice adds a second gb_artifacts row (linking the existing entities and edges to it, and to its session if it has one) but no duplicate entities or edges, and no duplicate vectors: the existing ones are overwritten.

Remember manager

Text is not remembered by the HTTP handler: one goroutine, the remember manager, runs every remember pipeline run, one at a time, and the handler waits for its run. See Remember manager.

Run log

Every call is recorded in gb_remember_runs: one row per execution of the pipeline, the authoritative record of how it was executed. The row is written when the call starts and updated at every step, so a call in progress can be watched, and a row that stays started belongs to a process that died. (A request that is rejected before the pipeline starts, a 400, has no row; one that the manager queued has its row from then on, and a queued run that never runs is failed.) It is kept by internal/memory/memory_remember/runlog.go; the pipeline only reports Start and Done for its stages. and, for each stage (<stage>_ prefix): The stages, in order: classify_complexity (Jev), chunk_text, extract_graph, summarize_chunk, load_candidates, resolve_edges, store, store_series, resolve_geo, web_search, fetch_series, observe_series (relational.RememberStages lists them; the ones that run a model are marked there). A stage the call does not use stays not_started.
  • load_candidates and store hold the embedding requests they made (embed runs); fetch_series holds the resolve_series run and resolve_geo the detect_geo run; web_search, fetch_series and observe_series hold all the inferences of their work, including the extraction, fact comparison and embeddings of the briefings and observations they store (their job gets j.run.Within(stage), a view of the log that files every run in that stage and leaves the stages of the text itself alone). So every remember inference is in exactly one stage of one run log.
  • When a stage fails, the call fails with the same error, and the stages after it stay not_started. The stages that never fail the call (classify_complexity, resolve_geo, web_search, fetch_series, observe_series) can be failed in a run that is succeeded: check the stage for them.
  • A text that is a single chunk stores the series it came with in the same transaction as the text, so store_series has the times of store.
From Go: relational.GetRememberRun, ListRememberRuns (filter by user, dataset and status). Writing is done by the pipeline: startRunLog returns a *runLog whose methods (Start, Done, AddInference, Artifact, Finish) do nothing on nil and never fail the call; a row that cannot be written is logged. The pipeline reports each inference explicitly, with the ID that recording it returned: j.run.AddInference(stage, id).

Admin UI

With the admin UI enabled, the remember runs view shows, for each database, how many runs there are in each status and the list of the runs, newest first (100 per page). Clicking a status filters the list by it (click again, or Clear filter, to remove it); the counts are never filtered. Clicking a run opens its parameters and a table of its stages (status, start, duration, inference IDs, error). A run whose call succeeded but that has a failed stage is flagged. The JSON behind it is GET /v1/gb/admin/api/remember-runs?db=<name>[&status=][&dataset=][&user_id=][&limit=][&offset=] (status is started, succeeded or failed; limit defaults to 100, at most 500): counts (total, started, succeeded, failed), stages (the stage names in pipeline order), and runs (as in the run log, with stages keyed by name) with total, limit, offset for the filters given.

Schema

All tables are prefixed with gb_ (see internal/db/relational/{sqlite,postgres}/db_schema.go for datasets and internal/db/graph/{sqlite,postgres}/db_schema.go for the graph tables). Foreign keys are enabled on every connection. Deleting an entity cascades to its edges and its gb_entity_artifacts links, and deleting an edge removes its gb_edge_artifacts links; deleting an artifact sets artifact_id to NULL on its edges and removes its gb_edge_artifacts and gb_entity_artifacts links. Deleting an entity or an edge also removes its gb_entity_sessions or gb_edge_sessions links; deleting an artifact leaves them (they record that the session discovered the entity or fact, not which artifact did). Why edges have a link table too. (More on artifacts and their links in Artifacts.) gb_edges.artifact_id is only the artifact that created the edge. When a later artifact states the same fact, the new edge is recognized as a duplicate (exactly, or by the LLM) and merged into the stored one instead of being inserted, so without a link table the fact that this artifact also supports it would be lost. gb_edge_artifacts holds the creating artifact and every artifact whose duplicate was merged in, so a fact’s full provenance is kept (how many sources back it, which texts to cite). Contradicted edges are not linked: a contradiction is not a restatement. Why entities have a link table. An edge is created by exactly one artifact, so it carries a plain artifact_id. An entity is not: entities are deduplicated per dataset on (name_key, type), so “Alice” mentioned in artifacts 1, 5 and 9 is one row, and one artifact_id column could only remember where she first appeared. The many-to-many gb_entity_artifacts table records every artifact an entity was inferred from, so you can ask which artifacts ground an entity and which entities an artifact contributes to, and an artifact can be removed without losing entities that other artifacts still support. Why sessions have link tables. A session is a label on a stream of artifacts, and an entity or fact can be discovered in many sessions. Because entities and edges are deduplicated, the session that merely repeats one writes no row for it; gb_entity_sessions and gb_edge_sessions keep that connection anyway. See Sessions. The vectors are not in these tables: they live in the vector store, isolated per dataset, one vector dataset per index (see Vector storage). They are keyed by the id of the entity or edge row. Existing database files are not migrated when the schema changes: delete the file and the server recreates it.

Configuration

  • ANTHROPIC_API_KEY: required for this endpoint. If missing, the server still starts and the endpoint returns 503.
  • chunk_size (default 1024 tokens) and chunk_overlap_percent (default 20): when and how a long text is chunked, see Chunking.
  • Web search (web_search) is available exactly when ANTHROPIC_API_KEY is set; there is no other setting. See Web search.
  • OPENAI_API_KEY (or openai_api_key in gligabao.toml): used to embed entities, edges, chunks and chunk summaries. Optional: without it the endpoint works and stores the graph, but no vectors, and the server logs a warning at startup.
  • embedding_dimensions (default 1536) is the size of the embeddings; vector_store selects where they go (chromem, or pinecone). See Vector storage.
  • gligabao serve --db <path> selects the SQLite file (default build/gligabao.db).

Example

Limitations

  • No authentication.
  • Web search is opt-in per call, searches at most 3 key entities, and is a heuristic and not free (a fee per search); see Web search.
  • Images are only read and stored (see Images); they add nothing to the graph yet, and an image the dataset already holds (same SHA-256) is answered from its stored extraction without an LLM call.
  • The database and the vector store are not updated atomically (see Vector storage), and there is no re-embedding of entities and edges stored before embedding was configured.
  • The embeddings are not used by query yet, but the entity embeddings do help pick the candidate facts in remember (aliases); edge embeddings do not, and at most 50 candidates are compared.
  • Chunk summaries are made only for the chunks of a chunked text (a short text has no chunk, so no summary), are not extracted into the graph, and do not affect query yet; if a summary fails, so does the request (nothing is stored yet at that point). Reasoning is available only as one of three fixed token budgets.
  • A very long text is chunked, so it no longer exceeds the LLM’s output limit as a whole; a single chunk whose extraction is truncated still fails the request with 502. For a chunked text a failure while storing a chunk leaves the chunks stored before it (see Chunking).