Skip to main content

cognee.improve()

Description

improve() enriches an existing graph after ingestion.
  • With session_ids, it can apply feedback weights, persist session Q&A, persist agent traces, distill accepted session guidance into session_learnings, and update the calling user’s preference subgraph.
  • By default it builds the global context index: one summary bucket per document plus a dataset root for graph completion retrieval and dataset routing, without an LLM call. build_global_context_index=False turns that off.
  • With build_truth_subspace=True, it builds truth-subspace anchors from distilled session_learnings.
  • By default it also reviews each entity’s facts against the documents and dates they came from and records the facts that disagree; review_conflicts=False turns that off.
Triplet embeddings were removed, and with them the triplet_enrichment stage that used to run a default enrichment pass over the dataset. A call without session_ids runs only the graph stages: review_conflicts and global_context_index, both on by default. With both off, every stage is skipped. Each call runs the same nine named stages in a fixed order. A stage that cannot do anything under the current settings is skipped with a reason instead of running, and the returned ImproveResult reports what each stage did. For the full behavior walkthrough, see Improve.

Parameters

Union[str, UUID]
default:"'main_dataset'"
Dataset name or UUID to improve. Requires write permission — a dataset owned by another user must be given as a UUID.
bool
default:"False"
Runs every stage as one background task and returns immediately with an ImproveResult whose status is "running". The task waits for the dataset lock and its session claims, then holds them until the last stage finishes. await result.wait() blocks until it is done and returns the same, now finished, result.
Optional[List[str]]
default:"None"
Deprecated. Restricts the graph fragment passed to the deprecated custom extraction_tasks/enrichment_tasks to specific named entities or node sets. It has no effect without those tasks, and passing it anyway raises a FutureWarning. It will be removed in the next release.
Optional[List[str]]
default:"None"
Session IDs whose feedback, Q&A content, trace activity, and accepted distilled guidance should be bridged into the permanent graph.Passing session_ids also runs the user-preference stage, which folds rated turns and stated preferences from those sessions into the calling user’s per-dataset prefers weights and preference text. That stage is a no-op that writes nothing unless PERSONALIZATION_ENABLED is on, and it is best-effort — a failure is logged and never blocks the rest of improve(). See Personalized Memory per User.
bool | None
default:"None"
Builds the global context index, as the last registry stage. None follows IMPROVE_GLOBAL_CONTEXT_INDEX (default true); True or False overrides it for this call, and False skips the stage with reason turned_off. Against a remote server (after serve()), only True is forwarded: False is dropped and the server’s own IMPROVE_GLOBAL_CONTEXT_INDEX setting applies. It also runs in background mode, as the last stage of the background task. It makes no LLM call, so it runs without an LLM key: each document with extracted entities gets one bucket of its most distinctive entities (tf-idf over the dataset’s chunks) and the relations among them, and the dataset gets one root. Every build writes only the texts that changed and deletes the dataset’s index nodes it did not compute, so a deleted document loses its bucket. When only reads (search, recall, or an improve() that ran no stage) have run on the dataset since the last build, the stage reports already_completed with reason no_writes_since_last_improve.
bool
default:"False"
Opt-in flag that builds the truth subspace from distilled session_learnings — after distillation and before conflict review. Only runs when session_ids is provided, and is best-effort (a build failure is logged and never blocks the rest of improve()). Off by default means no behavior change.
Optional[bool]
default:"None"
Reviews the facts of entities changed since its last run (every entity on the first run). Only facts that could disagree are sent, in LLM calls of at most 25 entities and 8,000 input tokens (or half the model’s token limit, when that is lower); later runs send only what gained a fact since the last review. It stores one FactConflict per disputed attribute and marks each fact it covered current, superseded, or conflicting, and leaves entity descriptions as extracted. Chunk, hybrid, and graph-completion search then show those labels and a ## Fact conflicts block. Dates come only from each document’s effective_date metadata (see IMPROVE_EFFECTIVE_DATE_KEY), never from when cognee wrote the edge. Needs a configured LLM and EDGE_EVIDENCE_ENABLED (on by default); without an LLM the stage is skipped with no_llm_configured. Left as None, it follows IMPROVE_REVIEW_CONFLICTS (default true), so it runs on every improve, including the automatic one after remember(); against a remote server, None follows that server’s setting. True or False overrides the variable for this call, and a review turned off either way is skipped with turned_off. It replaces cognify’s removed contradiction detection. Cognee 1.6.5 and earlier keep it off unless you pass review_conflicts=True or set IMPROVE_REVIEW_CONFLICTS=true. Since 1.6.6, cognee.list_conflicts() lists the stored conflicts and cognee.resolve_conflict() records which fact a user accepts; later reviews keep that decision while the selected fact exists. See Resolve Conflicting Facts.

Additional keyword options

extraction_tasks and enrichment_tasks are deprecated and will be removed in the next release; passing them raises a FutureWarning. Run your own tasks with run_custom_pipeline() instead. data, node_type, vector_db_config, graph_db_config and node_name only scope those custom tasks. Without custom tasks they are ignored, also with a FutureWarning. None or an empty task list counts as not given. Over REST, extractionTasks, enrichmentTasks, data and nodeName on POST /api/v1/improve are marked deprecated the same way.

Return value

improve() returns an ImproveResult with one StageResult per stage, in this order: When you pass the deprecated extraction_tasks/enrichment_tasks, one more stage, custom_tasks, is appended after these and runs them. Each StageResult has stage, status, reason, error, counts, and duration_ms. status is one of:
  • completed: the stage did its work.
  • already_completed: there was nothing new since the last run, for example no_new_entries for Q&A that was already bridged.
  • skipped: the stage declined before doing any work. reason says why, for example no_session_ids, opt_in_disabled, turned_off, no_llm_configured, personalization_disabled, backend_unsupported, disabled_by_config, or budget_exhausted for a stage that was never attempted because an earlier one ran out of LLM budget.
  • errored: the stage failed. error holds the message and the remaining stages still run — unless the failure was an exhausted LLM budget, which stops the run.
ImproveResult.status summarises the run: running for a background run that has not finished, errored if any stage errored, skipped if every stage was skipped, and completed otherwise. Use result.stage("distill_sessions") to look up one stage and result.stage_summary() for a compact name=status string.
A few outcomes to know about:
  • Another improve is already running on the same dataset or on one of the same sessions. The call waits until that improve finishes and then runs; see Concurrent improve calls queue.
  • persist_session_qa fails. This is the only stage whose ordinary failure stops the run, because dropping session Q&A would lose data. The exception is re-raised, and the partial result is attached to it as exc.improve_result. A failure in the step after the Q&A’s completed cognify run (AfterRunHookError) does not stop the run: the Q&A is stored, so the stage is errored and the remaining stages run.
  • A stage runs out of LLM budget. A failure that is a provider or gateway 402 ends the whole run, because every later stage would spend one more failing call on it. The stage stays errored, the stages after it are skipped with reason budget_exhausted, and ImproveResult.status is errored. When persist_session_qa is the stage that hit it, the run still raises, with the partial result on exc.improve_result as usual, and the stages it cut off carry budget_exhausted instead of aborted_by_fatal_stage. If persist_session_qa’s pipeline only reported the budget failure without raising, the run still raises LLMPaymentRequiredError, so the payment-required type reaches the caller either way.
  • A long agent trace backlog. persist_agent_traces drains the backlog the run started with. By default each session’s pending steps become one window; IMPROVE_TRACE_WINDOW_STEPS caps a window at the session’s oldest pending steps, and IMPROVE_TRACE_WINDOWS_PER_RUN caps the windows per run, counted across all the sessions passed. Each pass takes one window from up to 10 sessions that still have pending steps, those with the fewest windows so far, and builds them in a single cognify run. If that build fails, the whole pass is rolled back: no watermark in the pass moves, the stage stops and is errored, and the next run retries every window of the pass. Windows from earlier passes stay persisted. A session whose trace watermark doesn’t advance, for example because its trace context could not be read, is set aside for the rest of the run while the others keep draining, and the stage is errored with the count in sessions_stuck. The stage’s counts include windows alongside sessions.
  • A long backlog of session learnings to distill. By default distill_sessions curates every new gated entry of each session in one run. With IMPROVE_DISTILL_MAX_BATCHES_PER_RUN set, entries beyond the cap are marked as covered without curation, and the stage’s counts report them as entries_uncurated alongside documents, sessions_completed, and sessions_failed. If a session runs out of LLM budget, the stage stops before the remaining sessions and is errored.
  • Entities the conflict review did not finish. The review_conflicts stage’s counts report entities_failed and entities_skipped. A failed entity is reviewed again on the next run, with all of its facts. A skipped entity has facts that do not fit one review call, and it is not retried. Only a completed review moves the stage’s watermark.
  • Code that used the old return value. Earlier releases returned the enrichment pipeline’s result directly, in the same shape as legacy memify(). result.memify_run is deprecated: reading it raises a FutureWarning, and it holds the raw return of the custom_tasks stage’s first pass, or {} when no custom tasks ran. REST responses still include memify_run, marked deprecated. Read result.stages instead.

Configuration

These environment variables configure the improve loop: With the defaults there is no debounce, and every remember(session_id=...) call runs an improve. Those improves queue one after another on the dataset, so a burst of remember() calls runs one improve per call; raise IMPROVE_DEBOUNCE_ENTRIES or IMPROVE_DEBOUNCE_SECONDS to run fewer. Entries held back by the debounce are not dropped: recall still reads them from the session cache, and the next improve that runs bridges them. An application that embeds Cognee can also decline a single automatic improve at runtime — when the tenant it serves has no LLM budget left, say — by registering an admission check. See Declining the automatic improve. Explicit improve() calls are never gated by it.

Troubleshooting

The target dataset is resolved and authorized for write once, before any stage runs, so a bad dataset fails the whole call instead of silently enriching your default dataset:
Common causes:
  • No write grant on the UUID. Read access is not enough — every improvement stage writes. Ask the dataset owner for a write grant; see Access Control Lists.
  • The UUID does not exist. Unknown and unauthorized UUIDs are reported identically, so dataset existence is never confirmed to an unauthorized caller. Double-check the UUID against your datasets.
  • You passed another user’s dataset name. Names are owner-scoped: improving a name that another user happens to own does not raise — it resolves (or creates) your own dataset with that name and leaves theirs untouched. To improve a dataset owned by someone else, pass its UUID.
An improve() run holds the dataset’s lock from start to finish, the same per-dataset lock that pipeline runs and deletes take (see Per-dataset serialization). On top of it, the run claims each session it bridges, scoped to the calling user.
  • A second improve() on the same dataset, or on a session a running improve has claimed, waits for that improve to finish and then runs. It is not skipped.
  • cognify(), add() and forget() on that dataset wait for a running improve, including its LLM stages, and an improve waits for them. Improves on different datasets still run in parallel, unless they bridge the same session.
  • A foreground call blocks until it holds the lock and its session claims. With run_in_background=True the call returns at once with status == "running", and the background task does the waiting.
The lock and the session claims are in-memory and process-local, so they only coordinate calls within one worker.

Examples

See also Global Context Index for how to use the generated summaries during search, and Custom pipelines to run your own tasks over a dataset (memify() is deprecated).