Skip to content

feat: Presort folder pre-organization + JSON graph-model DSL (COG-6281) - #4630

Open
Vasilije1990 wants to merge 3 commits into
devfrom
Vasilije1990/pre_sort_task
Open

feat: Presort folder pre-organization + JSON graph-model DSL (COG-6281)#4630
Vasilije1990 wants to merge 3 commits into
devfrom
Vasilije1990/pre_sort_task

Conversation

@Vasilije1990

Copy link
Copy Markdown
Contributor

Description

Pointing cognee at a messy folder (e.g. ~/Downloads) today flattens it blindly into one dataset — no junk filtering, no duplicate or personal-data awareness, no reuse of what's already been cognified. This PR adds presort, a pre-ingestion organization step, plus a JSON graph-model DSL that both presort and end users can define relationship schemas with.

Fixes COG-6281.

Presort — rides remember(), no new top-level SDK function

Analyzeremember(folder, dry_run="presort") scans without touching files and returns a persisted PresortReport:

  • junk filtering (.DS_Store, partial downloads, hidden/build dirs)
  • exact-duplicate clusters with lazy content hashing (large unique-size files skipped)
  • version candidates (report (1), _v2, -final, trailing dates)
  • potential personal data: filename hints + content regexes (email, phone, IBAN mod-97, SSN-like, Luhn-validated cards, credential shapes) — findings carry redacted samples only
  • incremental status per file (new / staged / cognified) from content hashes joined with pipeline_status, so nothing is assumed fresh
  • proposed dataset groupings: code projects → folder structure → extension families

Applyremember(report) (object, dict, or saved *.presort.json) ingests each proposed group through add→cognify→improve with incremental_loading=True (idempotent re-apply), honoring the report's skip_duplicates / exclude_pii / apply_groups. auto_apply=True does both phases in one call; outcomes ride back on report.apply_results.

Automatic for folders — a plain local directory remembered with no explicit dataset/session/content_type presorts automatically; code-project directories keep the repo route; PRESORT_FOLDERS_ENABLED=false disables.

Degrades without an LLM key — deterministic scan always runs, use_llm downgrades with a warning, apply stages with add() only (new skip_connection_test passthrough on add()), apply_graph skipped with a warning.

CLIcognee-cli remember <folder> --dry-run presort [--apply] [--apply-graph] [--use-llm] [--allow-root] [-o report.json] and --from-report report.json to apply later. --allow-root appends to COGNEE_ALLOWED_LOCAL_FILE_ROOTS (never replaces).

JSON graph-model DSL (cognee.modules.graph_models, exported via cognee.low_level)

  • GraphSchemaSpec — entities, typed fields, relations with cardinality; snake_case with camelCase aliases so the frontend editor's JSON works unchanged. Validation is also the safety gate before the exec-based converter (identifier-only names, infra-field collision checks, size caps).
  • graph_spec_to_json_schema — Python port of the frontend toGraphModelSchema.ts, extended with identity_fields (default ["name"]) so DSL-built nodes actually merge across chunks/runs.
  • graph_model_from_spec — JSON in, DataPoint-derived Pydantic class out, via the existing graph_schema_to_graph_model.
  • Presort dogfoods it: the default relationship spec (duplicate_of, version_of, belongs_to_group, contains_pii) is a DSL document; report sections follow the spec, custom relations resolve via register_relation_detector() or an LLM fallback, and apply_graph=True writes the spec-shaped relationship graph into its own dataset.

Testing

  • 100+ new unit tests: DSL validation (incl. hostile names against the exec gate) and golden-schema parity, every presort task on tmp fixtures, relation registry/LLM fallback (mocked), graph instantiation from default and custom specs, remember routing matrix (auto-trigger, explicit-dataset skip, env kill switch, code-repo skip), LLM-less degradation paths. Full relevant suites: 523 passed.
  • Live verification on a real ~4k-file Downloads folder (scan + report) and end-to-end apply → recall → graph visualization on a subset; second runs correctly report staged/cognified and skip re-processing.

New docs-facing pieces: examples/guides/presort_downloads.py, examples/guides/graph_model_from_json.py, prompts detect_pii.txt / extract_presort_relation.txt.

🤖 Generated with Claude Code

Vasilije1990 and others added 3 commits August 23, 2026 15:28
Graph models can now be declared in plain JSON — entities, typed
fields, and relations with cardinality — instead of hand-written
Pydantic classes. GraphSchemaSpec validates the document (also the
safety gate before the exec-based converter) and compiles through the
existing graph_schema_to_graph_model. Mirrors the frontend editor's
shape (camelCase aliases), and adds identity_fields so DSL-built nodes
merge across chunks and runs, which the frontend compiler never emits.
Exported via cognee.low_level to keep the main SDK surface unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ingesting a messy folder today flattens it blindly: no junk filtering,
no duplicate or personal-data awareness, one dataset for everything.
Presort adds a read-only pre-step that scans a folder and produces a
PresortReport: junk skipped, exact-duplicate clusters (lazy hashing),
version candidates, potential personal data (redacted samples only),
per-file already-in-cognee status derived from content hashes and
pipeline_status, and proposed dataset groupings. Applying the report
ingests each group through the normal pipeline with incremental
loading, so re-applying is idempotent; without an LLM key it degrades
to add()-only staging with a warning instead of failing.

The relationships presort checks for are data, not code: a JSON
graph-model spec drives which sections exist, custom relations resolve
via registered detectors or an LLM fallback, and apply_graph writes
the report's spec-shaped relationship graph into its own dataset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Presort rides remember() rather than adding a new SDK function:
dry_run="presort" analyzes a folder, passing the report (object, dict,
or saved *.presort.json) back applies it, and auto_apply=True does
both in one call. Plain local-directory inputs presort automatically
when no explicit dataset, session, or content_type was given —
code-project directories keep the repo route, and
PRESORT_FOLDERS_ENABLED=false disables the routing. add() gains a
skip_connection_test passthrough so the LLM-free staging path does
not trip the first-run connection probe.

CLI: cognee-cli remember grows --dry-run presort, --apply,
--apply-graph, --from-report, --allow-root and related flags.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
if text.startswith(("s3://", "http://", "https://", "file://")):
return False
try:
path = Path(text).expanduser()
if looks_like_presort_report(data):
return PresortReport.from_json(data) if isinstance(data, dict) else data
if isinstance(data, (str, Path)) and str(data).endswith(REPORT_FILE_SUFFIX):
candidate = Path(data).expanduser()
if dry_run is False and _should_auto_presort(
data, dataset_name, dataset_id, session_id, kwargs
):
logger.info(f"remember: folder input detected — presorting {str(data)!r} automatically")
except ValueError:
raise ValueError(
f"Path {data_path!r} is outside the allowed local file roots. Set "
f"{ALLOWED_LOCAL_FILE_ROOTS_ENV}={Path(data_path).expanduser()} "
text = str(source)
candidate = Path(text)
try:
if candidate.is_file():
candidate = Path(text)
try:
if candidate.is_file():
text = candidate.read_text(encoding="utf-8")
@NMZivkovic

Copy link
Copy Markdown
Collaborator

The part I'd want changed before this merges is that presort isn't opt-in. remember.py:979 reroutes any local directory into the presort path whenever dataset_name is still the default — and main_dataset is also the parameter's default, so passing dataset_name="main_dataset" explicitly gets rerouted too:

if dry_run is False and _should_auto_presort(data, dataset_name, dataset_id, session_id, kwargs):
    dry_run = "presort"
    kwargs.setdefault("auto_apply", True)

Today remember("./docs") rglobs every file into main_dataset and returns a RememberResult. After this it returns a PresortReport, writes into several group-derived datasets with node_set=["presort", <group>], and drops duplicate copies, symlinks, hidden files, zero-byte files and junk extensions that used to be ingested. Anything reading result.dataset_id or result.pipeline_run_id now raises AttributeError, and PRESORT_FOLDERS_ENABLED=false is the only way out. For this release I'd require an explicit dry_run="presort" and keep the auto-route behind an env flag defaulting to off.

Second thing: the new tests fail deterministically on 3.10 — 12 failures in "OS and Python Tests Ubuntu / Unit tests 3.10.x", and 3.10 is a supported version per requires-python. It isn't flakiness or import order. mock.patch("cognee.modules.presort.run_presort._report_destination") resolves through _dot_lookup up to 3.10 and pkgutil.resolve_name from 3.11 on, so the getattr walk lands on the re-exported function from __init__.py instead of the module and raises AttributeError: <function run_presort ...> does not have the attribute '_report_destination'. Same shadowing on cognee.tasks.presort.detect_pii.LLMGateway, cognee.api.v1.add.add.add and cognee.api.v1.remember.remember._remember_inner. Fix is to patch the module object: import cognee.modules.presort.run_presort as m; patch.object(m, "_report_destination", ...). Two more fail on Windows because the tests do path.rsplit("/", 1)[-1], which never splits a backslash — use Path(...).name.

Three smaller ones while you're in there. A folder of only dotfiles or .DS_Store now hits apply_presort.py:112 and raises ValueError: No groups to apply on a path the caller never opted into, where dev ingested those files fine; return the empty report instead when the groups weren't explicitly requested. When a folder auto-presorts, every kwarg except run_in_background/self_improvement/user is dropped, including node_set, vector_db_config and graph_db_config — so data lands in the default databases with no error. And --dry-run as nargs="?" next to the nargs="*" positional breaks cognee-cli remember --dry-run notes.txt; that now parses as data=[], dry_run='/tmp/notes.txt' and aborts with "Unsupported --dry-run value". Keeping store_true and adding a separate --presort avoids that.

The DSL and the test volume are good — my objection is entirely about the default and the 3.10 gate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants