Skip to content

fix(report): stop LLM title clustering from over-merging unrelated sessions - #755

Merged
junhoyeo merged 3 commits into
mainfrom
fix/report-726-clustering
Jun 22, 2026
Merged

junhoyeo merged 3 commits into
mainfrom
fix/report-726-clustering

Conversation

@junhoyeo

@junhoyeo junhoyeo commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Problem

The Rust title-clustering fallback in crates/tokscale-cli/src/commands/report.rs (used by apple-fm and other non-LLM backends, PR #726) over-merges: unrelated sessions collapse into one giant group.

Three root causes:

  • (a) Over-merge via absolute token count + union growth. tokens_overlap returned true whenever two titles shared >= 2 significant tokens regardless of set size, and cluster_titles grew each cluster signature to the union of all member tokens. A cluster could therefore transitively absorb unrelated sessions that merely shared a couple of incidental words.
  • (b) Stopword-only titles lumped together. Titles reducing to no significant tokens were all forced into one arbitrary group.
  • (c) ASCII-only case folding. to_ascii_lowercase left non-ASCII capitals untouched while tokens kept Unicode, so Café/café clustered apart.

Fix

  • (a) Replace the shared >= 2 rule with the overlap coefficient shared / min(|a|, |b|) >= 0.6 (CLUSTER_SIMILARITY_THRESHOLD). Clusters now keep per-member token sets instead of a grown union; a candidate joins only if it overlaps an actual member. Genuine near-duplicates still merge (a short title fully contained in a long one scores 1.0), but incidental two-token overlaps no longer chain unrelated titles.
  • (b) Token-empty titles group by exact normalized title (normalized_title): each distinct stopword-only title gets its own group, identical ones still collapse.
  • (c) Use full to_lowercase() for both tokenization and normalization.

Only the clustering functions were touched (a sibling report-date PR also edits this file).

Tests

Added regression tests that fail without the fix:

  • tokens_overlap_uses_ratio_not_absolute_count
  • cluster_titles_does_not_transitively_absorb_unrelated
  • cluster_titles_separates_distinct_stopword_only_titles
  • significant_tokens_folds_non_ascii_case
  • cluster_titles_merges_non_ascii_case_variants

All existing clustering tests (near-duplicate / unrelated / exact-duplicate / label) still pass. cargo test -p tokscale-cli green (122 + unit tests); cargo clippy -p tokscale-cli --tests introduces no new warnings (3 pre-existing useless_vec warnings on main remain untouched).

Residual concern

Interaction with the sibling report-date PR was not tested (only the clustering functions were modified, so conflict risk is low).

🤖 Generated with Claude Code


Summary by cubic

Fixes over-merging in the Rust title-clustering fallback and unifies Unicode titles so unrelated sessions don’t collapse together. Adds NFC normalization and a singleton-token guard so generic one-word titles (e.g., “API”) don’t over-cluster.

  • Bug Fixes

    • Use overlap coefficient (shared / min(|a|, |b|) ≥ 0.6) with per-member token sets to prevent transitive absorption.
    • Guard single-token titles: require at least two shared tokens when the smaller set has one.
    • Normalize to NFC before lowercasing and stripping combining marks; merges NFC/NFD equivalents and non-ASCII case variants.
    • Token-empty titles group only by identical normalized title; identical ones merge, others stay separate.
    • Consolidation merges clusters only on member-to-member overlap; order-independent.
  • Dependencies

    • Add unicode-normalization for NFC normalization.

Written for commit 715c070. Summary will update on new commits.

Review in cubic

…ssions

The Rust title-clustering fallback (used by apple-fm and other non-LLM
backends) collapsed unrelated sessions into one giant group.

Three root causes, fixed here:

(a) Over-merge via absolute token count + union growth. `tokens_overlap`
    returned true whenever two titles shared >= 2 significant tokens,
    regardless of set size, and `cluster_titles` grew each cluster's
    signature to the UNION of all member tokens. Together a cluster could
    transitively absorb unrelated sessions that merely shared a couple of
    incidental words. Now overlap is the OVERLAP COEFFICIENT
    (shared / min(|a|,|b|) >= 0.6), and clusters keep per-member token
    sets — a candidate joins only if it overlaps an actual member, never an
    accumulated union — so genuine near-duplicates still merge but
    incidental two-token overlaps no longer chain.

(b) Stopword-only titles lumped together. Titles that reduce to no
    significant tokens were all forced into one arbitrary group. Each
    distinct normalized title now gets its own group; identical
    stopword-only titles still collapse.

(c) ASCII-only case folding. `to_ascii_lowercase` left non-ASCII capitals
    untouched while tokens kept Unicode, so "Café"/"café" clustered apart.
    Now uses full `to_lowercase()`.

Adds regression tests for each case; existing near-duplicate / unrelated /
exact-duplicate tests still pass.

Confidence: high
Scope-risk: narrow
Not-tested: interaction with the sibling report-date PR (only the
clustering functions were touched)
@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
tokscale Ignored Ignored Preview Jun 22, 2026 10:14am

Request Review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89643a06eb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let union = a.len() + b.len() - shared;
union > 0 && (shared as f64 / union as f64) >= 0.6
let smaller = a.len().min(b.len());
smaller > 0 && (shared as f64 / smaller as f64) >= CLUSTER_SIMILARITY_THRESHOLD

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid treating singleton token matches as full overlap

With the new overlap coefficient, any title that reduces to a single significant token gets a perfect score against every longer title containing that token, so a generic summary like Fix API/API will cluster with unrelated Add API auth, Update API billing, etc. This regresses the over-merge behavior for apple-fm/non-LLM grouping because the previous Jaccard/shared-token rule would not merge on just one shared token; singleton titles need a stricter rule such as requiring at least two significant shared tokens.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 715c070. tokens_overlap now requires at least two shared significant tokens when the smaller token set has only one token (if smaller <= 1 { return shared >= 2; }), before applying the overlap coefficient. This prevents a singleton title ("API"/"Fix API") from merging with every unrelated longer title containing that token, restoring the stricter behavior for apple-fm/non-LLM grouping while keeping genuine multi-token near-duplicates clustering. Regression tests added (tokens_overlap_singleton_does_not_overcluster, cluster_titles_does_not_overcluster_singletons). cargo test + clippy green.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 1 file

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread crates/tokscale-cli/src/commands/report.rs Outdated
Lowercase report clustering titles before filtering token characters and drop Unicode combining marks introduced by lowercase expansion. This keeps case-only variants such as Turkish dotted I clustered correctly while preserving the overlap-coefficient behavior from the PR. Also converts three report test fixtures from Vec to arrays so clippy remains clean.

Constraint: Only report.rs was in scope for PR validation
Rejected: Add a Unicode normalization dependency | new dependencies were out of scope for this PR fix
Confidence: high
Scope-risk: narrow
Tested: cargo test -p tokscale-cli commands::report::tests
Tested: cargo test -p tokscale-cli -- --skip antigravity::tests::identity_probe_request_decodes_chunked_antigravity_response --skip antigravity::tests::identity_probe_request_prefers_chunked_over_content_length --skip antigravity::tests::identity_probe_request_uses_probe_cap_for_large_bodies --skip antigravity::tests::rpc_request_rejects_oversized_content_length_body --skip antigravity::tests::read_chunked_body_rejects_oversized_accumulated_chunks
Tested: cargo clippy -p tokscale-cli --tests -- -D warnings
Not-tested: Full unfiltered cargo test in this sandbox; antigravity TCP listener tests fail because loopback bind returns Operation not permitted

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread crates/tokscale-cli/src/commands/report.rs
…tles

Address automated review feedback on the title-clustering logic.

The overlap coefficient `shared / min(|a|, |b|)` scores a perfect 1.0 for a
single-token title against any longer title that merely contains that token,
so a generic summary like "API"/"Fix API" clustered with every unrelated
"Add API auth"/"Update API billing". Require at least two shared tokens when
the smaller set has only one token; genuinely-related multi-token titles still
cluster.

Combining-mark stripping was applied asymmetrically: an NFC title kept its
precomposed letter while an NFD equivalent had its mark stripped, tokenizing
identical titles differently and splitting them across clusters. Normalize to
NFC before lowercasing/stripping in both significant_tokens and
normalized_title so canonically-equivalent titles converge.

Constraint: must not regress the original over-merge fix this PR introduced
Rejected: fall back to Jaccard for tiny sets | shared>=2 guard is simpler and matches reviewer suggestion
Confidence: high
Scope-risk: narrow
@junhoyeo
junhoyeo merged commit f96d27f into main Jun 22, 2026
15 of 16 checks passed
t1000040 pushed a commit to tmobi-internal/tokscale that referenced this pull request Jun 30, 2026
…ssions (junhoyeo#755)

* fix(report): stop LLM title clustering from over-merging unrelated sessions

The Rust title-clustering fallback (used by apple-fm and other non-LLM
backends) collapsed unrelated sessions into one giant group.

Three root causes, fixed here:

(a) Over-merge via absolute token count + union growth. `tokens_overlap`
    returned true whenever two titles shared >= 2 significant tokens,
    regardless of set size, and `cluster_titles` grew each cluster's
    signature to the UNION of all member tokens. Together a cluster could
    transitively absorb unrelated sessions that merely shared a couple of
    incidental words. Now overlap is the OVERLAP COEFFICIENT
    (shared / min(|a|,|b|) >= 0.6), and clusters keep per-member token
    sets — a candidate joins only if it overlaps an actual member, never an
    accumulated union — so genuine near-duplicates still merge but
    incidental two-token overlaps no longer chain.

(b) Stopword-only titles lumped together. Titles that reduce to no
    significant tokens were all forced into one arbitrary group. Each
    distinct normalized title now gets its own group; identical
    stopword-only titles still collapse.

(c) ASCII-only case folding. `to_ascii_lowercase` left non-ASCII capitals
    untouched while tokens kept Unicode, so "Café"/"café" clustered apart.
    Now uses full `to_lowercase()`.

Adds regression tests for each case; existing near-duplicate / unrelated /
exact-duplicate tests still pass.

Confidence: high
Scope-risk: narrow
Not-tested: interaction with the sibling report-date PR (only the
clustering functions were touched)

* fix(report): normalize Unicode title tokens

Lowercase report clustering titles before filtering token characters and drop Unicode combining marks introduced by lowercase expansion. This keeps case-only variants such as Turkish dotted I clustered correctly while preserving the overlap-coefficient behavior from the PR. Also converts three report test fixtures from Vec to arrays so clippy remains clean.

Constraint: Only report.rs was in scope for PR validation
Rejected: Add a Unicode normalization dependency | new dependencies were out of scope for this PR fix
Confidence: high
Scope-risk: narrow
Tested: cargo test -p tokscale-cli commands::report::tests
Tested: cargo test -p tokscale-cli -- --skip antigravity::tests::identity_probe_request_decodes_chunked_antigravity_response --skip antigravity::tests::identity_probe_request_prefers_chunked_over_content_length --skip antigravity::tests::identity_probe_request_uses_probe_cap_for_large_bodies --skip antigravity::tests::rpc_request_rejects_oversized_content_length_body --skip antigravity::tests::read_chunked_body_rejects_oversized_accumulated_chunks
Tested: cargo clippy -p tokscale-cli --tests -- -D warnings
Not-tested: Full unfiltered cargo test in this sandbox; antigravity TCP listener tests fail because loopback bind returns Operation not permitted

* fix(report): guard singleton over-clustering and normalize NFC/NFD titles

Address automated review feedback on the title-clustering logic.

The overlap coefficient `shared / min(|a|, |b|)` scores a perfect 1.0 for a
single-token title against any longer title that merely contains that token,
so a generic summary like "API"/"Fix API" clustered with every unrelated
"Add API auth"/"Update API billing". Require at least two shared tokens when
the smaller set has only one token; genuinely-related multi-token titles still
cluster.

Combining-mark stripping was applied asymmetrically: an NFC title kept its
precomposed letter while an NFD equivalent had its mark stripped, tokenizing
identical titles differently and splitting them across clusters. Normalize to
NFC before lowercasing/stripping in both significant_tokens and
normalized_title so canonically-equivalent titles converge.

Constraint: must not regress the original over-merge fix this PR introduced
Rejected: fall back to Jaccard for tiny sets | shared>=2 guard is simpler and matches reviewer suggestion
Confidence: high
Scope-risk: narrow
@junhoyeo
junhoyeo deleted the fix/report-726-clustering branch July 13, 2026 02:17
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.

1 participant