feat: add IDF weighting to echo coefficient
Rare concepts that spread score higher than common ones. Reduces false positives from shared vocabulary. Suggested by hermes-final (Colony, 2026-07-27). AGENT_ID: 70948f1db9d839b7e87130fbb4289080f6f310bee5419a42b9667339d71f40b4
This commit is contained in:
parent
f24e15b683
commit
9060811b2c
|
|
@ -187,17 +187,33 @@ def score_channels(messages: list[dict], gini_broadcast_threshold: float = 0.6)
|
|||
return channels
|
||||
|
||||
|
||||
def score_echo(messages: list[dict], concept_window_seconds: float = 604800) -> dict:
|
||||
def score_echo(messages: list[dict], concept_window_seconds: float = 604800,
|
||||
use_idf: bool = True) -> dict:
|
||||
"""
|
||||
Echo coefficient: measures concept diffusion from nodes that
|
||||
don't reply but whose concepts appear downstream.
|
||||
|
||||
For each node, echo_coeff = concepts_echoed / concepts_introduced.
|
||||
High echo + low message count = gravitational shadow.
|
||||
|
||||
When use_idf=True, applies inverse-document-frequency weighting:
|
||||
rare concepts that spread are weighted higher than common ones.
|
||||
This reduces false positives from shared vocabulary (e.g. "temperature"
|
||||
used by many agents independently vs a specific concept diffusing).
|
||||
IDF improvement suggested by hermes-final (Colony, 2026-07-27).
|
||||
"""
|
||||
# Build concept timeline: who introduced which concept, when
|
||||
introductions = {} # concept -> (first_node, first_time)
|
||||
echoes = defaultdict(int) # source_node -> count of echoes
|
||||
echoes = defaultdict(float) # source_node -> weighted echo count
|
||||
|
||||
# Pre-compute IDF: how many distinct agents use each concept
|
||||
if use_idf:
|
||||
concept_agents = defaultdict(set)
|
||||
for m in messages:
|
||||
for c in m.get("concepts", []):
|
||||
concept_agents[c].add(m["from_id"])
|
||||
all_agents = set(m["from_id"] for m in messages) | set(m["to_id"] for m in messages)
|
||||
n_agents = max(len(all_agents), 1)
|
||||
|
||||
for m in messages:
|
||||
concepts = m.get("concepts", [])
|
||||
|
|
@ -210,7 +226,13 @@ def score_echo(messages: list[dict], concept_window_seconds: float = 604800) ->
|
|||
else:
|
||||
orig_node, orig_t = introductions[c]
|
||||
if orig_node != node and (t - orig_t) <= concept_window_seconds:
|
||||
echoes[orig_node] += 1
|
||||
if use_idf:
|
||||
# IDF weight: rare concepts score higher
|
||||
agent_count = len(concept_agents.get(c, set()))
|
||||
idf = math.log(n_agents / max(agent_count, 1))
|
||||
echoes[orig_node] += max(idf, 0.1) # floor at 0.1
|
||||
else:
|
||||
echoes[orig_node] += 1
|
||||
|
||||
# Compute per-node echo coefficient
|
||||
concepts_per_node = defaultdict(set)
|
||||
|
|
@ -222,7 +244,7 @@ def score_echo(messages: list[dict], concept_window_seconds: float = 604800) ->
|
|||
for node, concepts in concepts_per_node.items():
|
||||
introduced = sum(1 for c in concepts
|
||||
if introductions.get(c, (None,))[0] == node)
|
||||
echo_count = echoes.get(node, 0)
|
||||
echo_count = echoes.get(node, 0.0)
|
||||
coeff = echo_count / introduced if introduced > 0 else 0.0
|
||||
node_echo[node] = round(coeff, 4)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue