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 intosession_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=Falseturns that off. - With
build_truth_subspace=True, it builds truth-subspace anchors from distilledsession_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=Falseturns that off.
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
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 exampleno_new_entriesfor Q&A that was already bridged.skipped: the stage declined before doing any work.reasonsays why, for exampleno_session_ids,opt_in_disabled,turned_off,no_llm_configured,personalization_disabled,backend_unsupported,disabled_by_config, orbudget_exhaustedfor a stage that was never attempted because an earlier one ran out of LLM budget.errored: the stage failed.errorholds 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.
- 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_qafails. 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 asexc.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 iserroredand the remaining stages run.- A stage runs out of LLM budget. A failure that is a provider or gateway
402ends the whole run, because every later stage would spend one more failing call on it. The stage stayserrored, the stages after it areskippedwith reasonbudget_exhausted, andImproveResult.statusiserrored. Whenpersist_session_qais the stage that hit it, the run still raises, with the partial result onexc.improve_resultas usual, and the stages it cut off carrybudget_exhaustedinstead ofaborted_by_fatal_stage. Ifpersist_session_qa’s pipeline only reported the budget failure without raising, the run still raisesLLMPaymentRequiredError, so the payment-required type reaches the caller either way. - A long agent trace backlog.
persist_agent_tracesdrains the backlog the run started with. By default each session’s pending steps become one window;IMPROVE_TRACE_WINDOW_STEPScaps a window at the session’s oldest pending steps, andIMPROVE_TRACE_WINDOWS_PER_RUNcaps 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 iserrored, 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 iserroredwith the count insessions_stuck. The stage’scountsincludewindowsalongsidesessions. - A long backlog of session learnings to distill. By default
distill_sessionscurates every new gated entry of each session in one run. WithIMPROVE_DISTILL_MAX_BATCHES_PER_RUNset, entries beyond the cap are marked as covered without curation, and the stage’scountsreport them asentries_uncuratedalongsidedocuments,sessions_completed, andsessions_failed. If a session runs out of LLM budget, the stage stops before the remaining sessions and iserrored. - Entities the conflict review did not finish. The
review_conflictsstage’scountsreportentities_failedandentities_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 acompletedreview 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_runis deprecated: reading it raises aFutureWarning, and it holds the raw return of thecustom_tasksstage’s first pass, or{}when no custom tasks ran. REST responses still includememify_run, marked deprecated. Readresult.stagesinstead.
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
Fixing 'PermissionDeniedError' on improve()
Fixing 'PermissionDeniedError' on improve()
The target dataset is resolved and authorized for Common causes:
write once, before any stage runs, so a bad dataset fails the whole call instead of silently enriching your default dataset:- No
writegrant on the UUID. Read access is not enough — every improvement stage writes. Ask the dataset owner for awritegrant; 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.
Concurrent improve calls queue
Concurrent improve calls queue
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()andforget()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=Truethe call returns at once withstatus == "running", and the background task does the waiting.