cognee.improve()
Description
improve() enriches an existing graph after ingestion.
- Without
session_ids, it runs the normal enrichment pass over the dataset. - With
session_ids, it can also apply feedback weights, persist session Q&A, persist agent traces, distill accepted session guidance intosession_learnings, and update the calling user’s preference subgraph. - With
build_global_context_index=True, it builds dataset-level summary buckets for graph completion retrieval. - With
build_truth_subspace=True, it builds truth-subspace anchors from distilledsession_learnings. - With
review_conflicts=True, it reviews each entity’s facts against the documents and dates they came from and records the facts that disagree.
improve() no longer pays a full-graph scan and O(graph size) memory cost on every run. The graph is still projected when you pass your own extraction_tasks or enrichment_tasks without data, because a caller-supplied task may need that projected fragment. Enrichment output is unchanged either way.
Each call runs the same ten 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 holds the improve lock 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"
Restricts the projected graph fragment to specific named entities or node sets. It only applies when the enrichment pass actually projects the graph — that is, when you supply custom
extraction_tasks/enrichment_tasks and no data. The default enrichment pass skips projection, so node_name has no effect there.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
default:"False"
Builds the global context index after enrichment. It also runs in background mode, as the last stage of the background task.
bool
default:"False"
Opt-in flag that builds the truth subspace from distilled
session_learnings — after distillation and before enrichment. 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.bool
default:"False"
Opt-in flag that reviews the facts of entities changed since its last run (every entity on the first run), in bounded LLM batches. It stores one
FactConflict per disputed attribute and marks each fact it covered current, superseded, or conflicting; 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). IMPROVE_REVIEW_CONFLICTS=true turns it on for every run, including the automatic improve after remember(). It replaces cognify’s removed contradiction detection.Additional keyword options
Return value
improve() returns an ImproveResult with one StageResult per stage, in this order:
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, orno_writes_since_last_improvefor enrichment on an unchanged dataset.skipped: the stage declined before doing any work.reasonsays why, for exampleno_session_ids,opt_in_disabled,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. Improves for one dataset run one at a time, so the call returns without doing anything, and every stage is
skippedwith reasonlock_held. 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 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, no further rerun pass starts, 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_tracescognifies each session’s oldest pending trace steps in windows ofIMPROVE_TRACE_WINDOW_STEPS, advancing that session’s trace watermark after each window, and stops afterIMPROVE_TRACE_WINDOWS_PER_RUNwindows in total, counted across all the sessions passed: each round takes one window from every session that still has pending steps. Later improves drain the rest. If a window fails, the stage stops and iserrored, but windows persisted earlier in the pass stay persisted, and the next run retries only the failed window. The stage’scountsincludewindowsalongsidesessions. - Code that used the old return value. Earlier releases returned the enrichment pipeline’s result directly, in the same shape as legacy
memify(). That value is now underresult.memify_run, which is{}when enrichment did not run.
Configuration
These environment variables configure the improve loop:
With the defaults there is no debounce, and every
remember(session_id=...) call runs an improve. 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.
improve() on a session returns lock_held
improve() on a session returns lock_held
Only one
improve() runs at a time for a given dataset or session; a call that finds its keys already claimed does nothing, and every stage of its result is skipped with reason lock_held.Check rerun_requested before retrying a session-keyed call (session_ids=[...]):True— the running improve holds one of your sessions, and your call asked it for one more pass. You do not need to retry. Before releasing the lock, the running improve checks for that request and runs its stages again — every stage is watermark-gated, so the extra pass only touches session entries that arrived in the meantime. That is how entries written while an improve is already running still reach the graph. The extra pass covers only the sessions the running improve holds, so any other sessions you passed are not bridged by it.False— the collision was on the dataset only: the holder is a dataset-only run, or a session-keyed run over different sessions in the same dataset. Nothing will cover your entries, so retry later.
stages stays the first pass, and rerun_passes holds one list of stage results per extra pass. status covers all passes, so an errored stage in an extra pass marks the whole run errored.A dataset-only call (no session_ids) makes no rerun request and always gets rerun_requested=False: a burst of calls on one dataset simply skips, and the running improve’s enrichment is the only one. The lock and the rerun request are in-process, so they only coordinate calls within one worker.