Skip to content

fix(config): thread [relationship_detector] thresholds into ingest - #1323

Merged
github-actions[bot] merged 3 commits into
mainfrom
fix/issue-1299-relationship-config
Aug 4, 2026
Merged

github-actions[bot] merged 3 commits into
mainfrom
fix/issue-1299-relationship-config

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Closes #1299.

[relationship_detector] had a split personality at ingest: auto_detect was honoured, but jaccard_min — and the other thresholds — were silently ignored, because the ingest call site passed only new_belief_ids and let every threshold fall back to its module default. A user who set jaccard_min in .aelfrice.toml got the default 0.4 anyway, with no warning.

Direction

The issue framed this as a choice: thread the config through, or document the constants as deliberate. Threading was chosen (operator ruling recorded on the issue), because the alternative leaves a documented TOML key that demonstrably does nothing.

The trap this avoids

The naive fix — call load_relationship_detector_config() inside the enabled-flag branch — is not free on the paths that matter. is_auto_relationship_detection_enabled() already falls through env and kwarg to a TOML read, so a TOML-enabled install pays a filesystem walk; adding a second independent load_relationship_detector_config() call per ingested turn adds another walk on a hot path. That is precisely the cost #1289/#1298 exist to eliminate, and #1304 is open against the same class right now. The config is resolved once and reused; before/after walk counts are in the commit message as counts, not latency, since the perf gate is load-sensitive.

Scope, stated so the section is not oversold

This does not make [relationship_detector] mean one thing everywhere, and the docs now say which keys reach which path:

  • residual_overlap_min has no TOML parse at all — it is not a key, it is a constant.
  • max_edges_per_belief has no key by design (removed deliberately in 7040c22a).

Only the keys that are actually parsed are now threaded to ingest. docs/user/CONFIG.md documents the section with per-key reach rather than implying uniform coverage.

Verification

Reverting the ingest call site to the bare new_belief_ids form makes the new test fail — the mutation and result are in the commit message. Tests run with AELFRICE_DB pinned to a fresh temp path. CHANGELOG insert-only under [Unreleased].

Reviewer note

An audit pass flagged that cli.py's two relationships_audit call sites reconstruct RelationshipDetectorConfig without the new auto_detect field, so it defaults to False on a config the dataclass docstring now describes as "the resolved [relationship_detector] section". That is pre-existing and inert for the audit path (which does not consult auto_detect), but the docstring is now slightly ahead of the call sites. Worth a look during review; happy to tighten either the docstring or the call sites here rather than in a follow-up.

Summary by Sourcery

Thread relationship detector configuration thresholds from .aelfrice.toml into the ingest write path while preserving the single-config-walk behaviour on the ingest hot path.

Bug Fixes:

  • Ensure jaccard_min, confidence_min, and max_candidate_pairs from [relationship_detector] are honoured by ingest when auto-detect is enabled.

Enhancements:

  • Introduce resolve_ingest_relationship_config to resolve the auto-detect flag and thresholds in a single config read for ingest.
  • Parse and expose the auto_detect flag on RelationshipDetectorConfig to unify configuration resolution across consumers.

Documentation:

  • Document the [relationship_detector] section with per-key reach, defaults, and precedence between ingest and audit commands in CONFIG.md.

Tests:

  • Add ingest-path tests confirming TOML thresholds affect contradiction edge writes and that resolving ingest configuration performs only one .aelfrice.toml filesystem walk per turn.

…1299)

`ingest.py` resolved `auto_detect` from `.aelfrice.toml` and then called
`write_semantic_edges(store, new_belief_ids=...)` with no threshold
arguments, so `jaccard_min` / `confidence_min` / `max_candidate_pairs`
were honoured by the read-only `aelf doctor --relationships` audits and
silently ignored on the one path that mutates the graph. Adjacent keys
in one section with opposite reach, and the asymmetry ran the risky
direction: the audit was tunable, the writer was pinned at 0.4/0.5/5000.

Honoured at ingest after this change: `auto_detect` (as before),
`jaccard_min`, `confidence_min`, `max_candidate_pairs`. Still NOT
configurable anywhere: `residual_overlap_min` (dataclass field, no TOML
parse) and `max_edges_per_belief` (no key by design — the Exp-48
write-gate is a caller kwarg). So the section is still not uniform; it
is the three parsing keys that now reach the writer.

No second config walk. `is_auto_relationship_detection_enabled()` already
falls through to a `.aelfrice.toml` walk, so calling
`load_relationship_detector_config()` beside it would double the
filesystem probes on a per-turn hot path (#1289/#1298). Instead
`auto_detect` is parsed onto `RelationshipDetectorConfig` and handed to
the flag resolver as its `explicit` argument, which is decisive before
the resolver's own TOML step. Measured `.aelfrice.toml` `is_file` probes
during one `ingest_turn`, cwd four levels below the config root:

  before: 11 probes (no config file present) / 4 probes (config found)
  after:  11 probes                          / 4 probes

Precedence for `auto_detect` is unchanged (env > TOML > default-off), and
default-off keeps a fresh install byte-identical.

Distinguishing tests — each mutation was applied to the staged
implementation and the named tests failed:

1. Reverted the ingest call to `write_semantic_edges(store,
   new_belief_ids=inserted)`: test_ingest_honours_toml_jaccard_min,
   test_ingest_honours_toml_confidence_min and
   test_ingest_threads_max_candidate_pairs_from_toml FAILED.
2. Changed the resolver to call
   `is_auto_relationship_detection_enabled(start=start)` without the
   parsed flag (the second-walk defect):
   test_resolve_ingest_config_walks_the_tree_once FAILED, 8 probes vs 4.
3. Made the loader stop parsing `auto_detect` (`ad_obj = False`):
   test_config_loader_reads_auto_detect plus the four above FAILED.
)

The section was absent from docs/user/CONFIG.md — `jaccard_min` appeared
only in docs/design/dedup.md, and there in the `dedup` (0.8) context,
which is a different constant in a different module.

Adds the section to the schema block and a keys section with a table
stating, per key, which consumer honours it: the ingest write path, the
`aelf doctor` audits, or neither. `residual_overlap_min` and
`max_edges_per_belief` are recorded as having no TOML key at all, so the
doc does not imply a uniformity the section still lacks.
@robotrocketscience robotrocketscience added the author-garsecg PR coordination mutex label Aug 4, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @robotrocketscience, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@robotrocketscience, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 14 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 16bceb87-9cef-4cb0-ad87-329bae6f9db5

📥 Commits

Reviewing files that changed from the base of the PR and between b9bfc29 and ad206de.

📒 Files selected for processing (5)
  • CHANGELOG/v4.md
  • docs/user/CONFIG.md
  • src/aelfrice/ingest.py
  • src/aelfrice/relationship_detector.py
  • tests/test_relationship_detector_semantic_writer.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

PR-size soft cap

This PR is over the advisory size threshold:

  • 394 changed lines (limit: 200)
  • 5 changed files (limit: 3)

Bigger PRs collide with more open work, which under the parallel-session workflow tends to produce repeated attn:merge-conflict cycles (see #602). When practical, split into smaller PRs that each touch a focused surface.

This is advisory only — nothing is blocked. If the size is intentional (large refactor, module removal, generated code), apply the size:override label and this comment will be removed on the next push.

@sourcery-ai

sourcery-ai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Reviewer's Guide

Threads [relationship_detector] config thresholds through the ingest path while preserving single-pass config resolution and clarifying configuration/documentation semantics.

Sequence diagram for ingest threading relationship_detector config thresholds

sequenceDiagram
    participant Ingest as _ingest_turn_ids
    participant RelConfig as resolve_ingest_relationship_config
    participant Loader as load_relationship_detector_config
    participant Flag as is_auto_relationship_detection_enabled
    participant Writer as write_semantic_edges

    Ingest->>RelConfig: resolve_ingest_relationship_config()
    RelConfig->>Loader: load_relationship_detector_config(start)
    Loader-->>RelConfig: RelationshipDetectorConfig
    RelConfig->>Flag: is_auto_relationship_detection_enabled(config.auto_detect, start)
    Flag-->>RelConfig: enabled
    RelConfig-->>Ingest: (enabled, config)
    Ingest->>Writer: write_semantic_edges(store, jaccard_min=config.jaccard_min, residual_overlap_min=config.residual_overlap_min, confidence_min=config.confidence_min, max_candidate_pairs=config.max_candidate_pairs, new_belief_ids=inserted)
    Note over Ingest,RelConfig: Config and flag resolved in one `.aelfrice.toml` walk
Loading

File-Level Changes

Change Details Files
Thread relationship-detector thresholds and config into ingest while preserving a single config walk.
  • Add resolve_ingest_relationship_config to load RelationshipDetectorConfig and auto_detect in one .aelfrice.toml walk while preserving env > TOML precedence.
  • Update ingest._ingest_turn_ids to call resolve_ingest_relationship_config and pass jaccard_min, residual_overlap_min, confidence_min, and max_candidate_pairs into write_semantic_edges.
  • Export resolve_ingest_relationship_config from relationship_detector for ingest and other consumers.
src/aelfrice/relationship_detector.py
src/aelfrice/ingest.py
Extend tests to cover TOML-driven thresholds and config-walk behaviour at ingest.
  • Add tests that ingest under different [relationship_detector] TOML values to assert jaccard_min and confidence_min gate CONTRADICTS edges.
  • Spy on write_semantic_edges to assert max_candidate_pairs and thresholds are threaded from TOML into the writer.
  • Introduce tests that count .aelfrice.toml Path.is_file probes to ensure resolve_ingest_relationship_config does not add an extra config walk.
  • Add tests confirming auto_detect is parsed into RelationshipDetectorConfig and that env precedence over TOML for auto_detect is preserved.
tests/test_relationship_detector_semantic_writer.py
Clarify and document [relationship_detector] config semantics and changelog entry for this fix.
  • Document the [relationship_detector] section, including per-key reach (ingest vs aelf doctor audits) and default/override semantics, in docs/user/CONFIG.md.
  • Clarify that residual_overlap_min and max_edges_per_belief have no TOML keys and remain module constants.
  • Add a v4 changelog entry describing the bug (thresholds ignored at ingest), the threading fix, and the unchanged precedence and probe-count guarantees.
docs/user/CONFIG.md
CHANGELOG/v4.md

Assessment against linked issues

Issue Objective Addressed Explanation
#1299 Make the ingest path’s use of [relationship_detector] explicit and consistent: either thread the resolved config thresholds (jaccard_min, confidence_min, max_candidate_pairs, etc.) into the ingest write_semantic_edges call so they are honoured there, or explicitly codify and rely on module constants instead.
#1299 Document the [relationship_detector] section in docs/user/CONFIG.md, including each key’s scope and which consumers (ingest vs audit commands) honour it.
#1299 Add tests that cover ingest behaviour with [relationship_detector] settings, in particular ensuring that jaccard_min (and related thresholds) from TOML are actually used when auto_detect = true on the ingest write path.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Comment thread tests/test_relationship_detector_semantic_writer.py Fixed
@robotrocketscience robotrocketscience added the attn:review Needs review (PR open, awaiting reviewer) label Aug 4, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Setr:2026-08-04T04:40:21Z]

…t off (#1299)

resolve_ingest_relationship_config loaded the [relationship_detector]
config before consulting AELFRICE_AUTO_RELATIONSHIPS, but the flag-only
resolver it replaced checked env first and returned without touching the
filesystem. An install that sets the var to a falsy value therefore went
from 0 to 11 .aelfrice.toml probes per ingested turn, for a config whose
only consumer -- write_semantic_edges -- does not run. That is the
#1289/#1298 class the threading was specifically designed to avoid, and
the PR's "probe count unchanged" claim was measured env-unset only.

Env-off now returns before the walk. Env-on still walks, because the
thresholds are then actually used; that cost is the point of the fix.
Measured on a 4-deep tree: env=0 is 0 probes before and after; env unset
is 11 before and after; env=1 is 0 -> 11.

New test asserts both arms (env-off 0, env-unset non-zero) so neither
passes vacuously; removing the guard fails it. The existing env-precedence
test is updated for the documented contract that the returned config is
meaningful only when enabled is True.

Also switches the test module's one `import aelfrice.relationship_detector`
to the `from` form so the module is not imported both ways, clearing the
open CodeQL thread that would otherwise bounce the merge-train.
@robotrocketscience

Copy link
Copy Markdown
Owner Author

Review — one defect found and fixed on the branch (ad206de2)

The threading is correct and the precedence analysis holds: I read load_relationship_detector_config against _read_auto_detect_toml case by case (missing file, missing section, non-dict section, malformed TOML, non-bool value) and every one of them collapses to the same answer, so handing config.auto_detect in as explicit genuinely does preserve env > TOML > default-off. write_semantic_edges' parameter defaults are identical to the dataclass defaults, so a no-config install is byte-identical as claimed. The CHANGELOG entry is under [Unreleased] / ### Fixed, and both doc claims check out — nothing outside relationship_detector.py reads auto_detect, and there is no AELFRICE_RELATIONSHIP* env var for the thresholds.

The defect: the probe-count invariant was measured on only one side of the precedence

resolve_ingest_relationship_config loads the config before anything consults AELFRICE_AUTO_RELATIONSHIPS. The flag-only resolver it replaced checked env first and returned without touching the filesystem. Measured on a 4-deep tree:

env unset AELFRICE_AUTO_RELATIONSHIPS=0 =1
before (is_auto_relationship_detection_enabled()) 11 probes 0 0
after (this PR, as pushed) 11 probes 11 11

The =1 column is a real and intended cost — the thresholds have to come from somewhere, that is the whole fix. The =0 column is not: it is a full walk to root, on a path that runs every ingested turn, resolving a config whose only consumer is a writer that will not run. That is exactly the #1289/#1298 class the design note in the docstring exists to avoid, and #1304 is open against it right now.

It went unnoticed because the invariant was only ever exercised env-unset — test_resolve_ingest_config_walks_the_tree_once opens with monkeypatch.delenv(ENV_AUTO_RELATIONSHIPS), and the CHANGELOG's "11 with no config file, 4 with one four directories up, before and after" is the same regime. Neither is wrong; they just do not reach the branch that regressed.

What I pushed

  • resolve_ingest_relationship_config returns before the walk when env is decisively falsy. Env-on still walks.
  • New test_resolve_ingest_config_env_off_probes_nothing asserts both arms — env-off is 0, env-unset over the same tree is non-zero — so it cannot pass vacuously. Removing the guard fails it (and fails the precedence test too).
  • The returned config is now documented as meaningful only when enabled is True; on the short-circuit it is the module defaults rather than the file's values. test_resolve_ingest_config_env_still_wins_over_toml asserted jaccard_min == 0.55 on the env-off arm, which was reading a value no caller consumes — updated to assert the default and say why. If you would rather the contract stay "the config is always the file's values", the alternative is to keep the walk and drop the guard, but then the =0 row above stands.
  • CHANGELOG: the probe sentence now states the env-unset measurement as env-unset and gives the env-set numbers separately, so the claim covers the whole precedence rather than half of it.
  • Test module imported aelfrice.relationship_detector both as import x and from x import y; switched line 323 to the from form. That clears the open CodeQL thread, which merge-train counts and would otherwise bounce the label.

Your reviewer note

cli.py:6490 and :6558 reconstruct RelationshipDetectorConfig with keyword arguments only, so the new field takes its default and nothing breaks. The audit path never reads auto_detect, so it is inert as you said. I left it alone — tightening it here would put an unrelated surface in a PR that is already over the size cap, and the docstring you added already scopes the field to the ingest resolver. Worth a follow-up only if a second consumer ever reads the flag off a cli.py-built config.

Verification

Full suite: 6980 passed, 69 skipped, 71 xfailed. Two failures, both ..._when_fastmcp_missing, are a local dependency-state artifact from the mcp bump on main and are green in CI on this same commit. Discretion grep on added lines: clean. Commit is signed.

Not labelling ready-to-merge yet — waiting for CI on ad206de2 and for the CodeQL thread to clear.

@robotrocketscience robotrocketscience added the ready-to-merge Trigger merge-train: FF main to this PR's head label Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

merge-train: merged ad206demain via FF push.

@github-actions github-actions Bot removed the ready-to-merge Trigger merge-train: FF main to this PR's head label Aug 4, 2026
@github-actions
github-actions Bot merged commit ad206de into main Aug 4, 2026
30 checks passed
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Setr:2026-08-04T04:51:59Z]

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

Labels

attn:review Needs review (PR open, awaiting reviewer) author-garsecg PR coordination mutex

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(config): [relationship_detector] auto_detect is honoured at ingest but jaccard_min is silently ignored there

2 participants