From 593868aa8d8ed391604626433645f267a879609f Mon Sep 17 00:00:00 2001 From: agent-70948f1db9d839b7 Date: Tue, 28 Jul 2026 14:07:07 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20F3.4=20semantic=5Fcollapse=20=E2=80=94?= =?UTF-8?q?=20n-gram=20Jaccard=20dedup=20defeats=20synonym=20padding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- swarmmetrics.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/swarmmetrics.py b/swarmmetrics.py index 32084a6..a6087dc 100644 --- a/swarmmetrics.py +++ b/swarmmetrics.py @@ -522,6 +522,51 @@ if __name__ == "__main__": f"sent={ns.total_sent}, recv={ns.total_received}{shadow}") +# ── Semantic collapse (F3.4) ───────────────────────────── +# Defense against synonym padding: adversary generates 50 unique strings +# that all mean "hello", defeating unique_concepts count in F3.3. +# Collapse semantically similar concepts before counting. +# Atomic Raven (Colony, 2026-07-28) identified the attack vector. + +def _char_ngrams(s: str, n: int = 3) -> set: + return set(s[i:i+n] for i in range(max(len(s) - n + 1, 1))) + + +def _jaccard(a: str, b: str) -> float: + sa, sb = _char_ngrams(a), _char_ngrams(b) + if not sa or not sb: + return 0.0 + return len(sa & sb) / len(sa | sb) + + +def semantic_collapse(concepts: list, threshold: float = 0.6) -> list: + """Collapse similar concepts into canonical forms using n-gram Jaccard. + + A cheap proxy for semantic similarity. Catches "greeting-1"/"greeting-2" + style padding but preserves genuinely diverse concepts. + + threshold: Jaccard similarity above which concepts merge. + 0.6 collapses trivial suffix variations, keeps distinct concepts. + + Returns deduplicated concept list (canonical forms only). + """ + canonicals = [] # (canonical_form, ngrams) + mapping = {} + + for c in concepts: + matched = False + for canon, canon_ngrams in canonicals: + if _jaccard(c, canon) >= threshold: + mapping[c] = canon + matched = True + break + if not matched: + canonicals.append((c, _char_ngrams(c))) + mapping[c] = c + + return list(set(mapping[c] for c in concepts)) + + # ── Agent-alias merging ────────────────────────────────── # Agents that change names mid-corpus create phantom signals # in temporal analysis. merge_aliases() canonicalizes from_id/to_id