tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 83JSON 198635 Definitions

⎇ 53dbdf0f1deea10d6fc567ea2b43e1e75d6d60cd crates/core/ente-brain/src/autopromote.rs Rust · 102 lines · 3.3 KB History Who touched what raw

chore: monorepo inicial con arje + minga + yahweh absorbidos 53dbdf0f1 · Sergio ·

1//! Autopromote loop. Background task que cada N segundos detecta cristales
2//! con thresholds altos y los promueve sin intervención humana.
3//!
4//! Anti-doble-promote: tras promover, registramos en un set la pareja
5//! (antecedent_kind, consequent_kind). Antes de promover, verificamos que
6//! no exista ya una regla con el mismo trigger_kind (heurística simple —
7//! evita ráfagas de duplicados de la misma estadística).
8
9use crate::audit::AuditAction;
10use crate::crystallize::{crystal_to_rule, detect_crystals, Crystal, CrystallizationParams};
11use crate::introspect::{append_rule_jsonl, BrainState};
12use crate::rules::EventKind;
13use std::collections::HashSet;
14use std::sync::Arc;
15use std::time::Duration;
16use tokio::sync::Mutex;
17use tracing::{info, warn};
18
19#[derive(Debug, Clone, Copy)]
20pub struct AutopromoteParams {
21    pub interval_secs: u64,
22    pub threshold: CrystallizationParams,
23}
24
25impl Default for AutopromoteParams {
26    fn default() -> Self {
27        Self {
28            interval_secs: 60,
29            // Más estrictos que el threshold default — evitar ruido.
30            threshold: CrystallizationParams {
31                min_support: 10,
32                min_conditional_prob: 0.85,
33                min_pmi: 2.0,
34            },
35        }
36    }
37}
38
39/// Spawn del bucle. El handle Mutex evita que dos pasadas concurrentes
40/// promuevan el mismo cristal (el lock garantiza serialización por brain).
41pub fn spawn_autopromote_loop(state: BrainState, params: AutopromoteParams) {
42    let promoted_keys: Arc<Mutex<HashSet<(EventKind, EventKind)>>> =
43        Arc::new(Mutex::new(HashSet::new()));
44
45    tokio::spawn(async move {
46        let mut tick = tokio::time::interval(Duration::from_secs(params.interval_secs));
47        tick.tick().await; // descartar primer tick inmediato
48        info!(?params, "autopromote loop activo");
49        loop {
50            tick.tick().await;
51            run_one_pass(&state, &params, &promoted_keys).await;
52        }
53    });
54}
55
56async fn run_one_pass(
57    state: &BrainState,
58    params: &AutopromoteParams,
59    promoted_keys: &Arc<Mutex<HashSet<(EventKind, EventKind)>>>,
60) {
61    let crystals: Vec<Crystal> = {
62        let obs = state.observer.read().await;
63        detect_crystals(&obs, &params.threshold)
64    };
65    if crystals.is_empty() { return; }
66
67    let mut pk = promoted_keys.lock().await;
68    for c in crystals {
69        let key = (c.antecedent.clone(), c.consequent.clone());
70        if pk.contains(&key) {
71            // Ya promovido — el observer puede seguir reportando este
72            // cristal pero no necesitamos otra regla.
73            continue;
74        }
75        promote_one(state, &c).await;
76        pk.insert(key);
77    }
78}
79
80async fn promote_one(state: &BrainState, c: &Crystal) {
81    let rule = crystal_to_rule(c);
82    let rule_id = rule.id;
83    if let Some(path) = state.rules_out.as_ref() {
84        if let Err(e) = append_rule_jsonl(path, &rule) {
85            warn!(?e, "autopromote: rules_out append falló");
86        }
87    }
88    state.engine.write().await.insert(rule);
89
90    state.audit.write().await.append(AuditAction::PromoteCrystal {
91        rule_id,
92        crystal: c.clone(),
93    });
94    info!(
95        %rule_id,
96        antecedent = ?c.antecedent,
97        consequent = ?c.consequent,
98        cp = c.conditional_prob,
99        pmi = c.pmi,
100        "autopromote: cristal → regla"
101    );
102}