1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
// SPDX-License-Identifier: MIT OR Apache-2.0
use super::super::{Agent, Channel};
impl<C: Channel> Agent<C> {
/// Fire-and-forget RL routing head update for the selected skill.
///
/// No-op when `rl_routing_enabled = false` or no RL head is loaded.
/// Only updates for the first active skill name (the one that was selected).
pub(crate) fn spawn_rl_head_update(&mut self, outcome: &str) {
let Some(cfg) = self.services.learning_engine.rl_routing else {
return;
};
if !cfg.enabled {
return;
}
let Some(selected_skill) = self.services.skill.active_skill_names.first().cloned() else {
return;
};
let Some(rl_head) = self.services.skill.rl_head.clone() else {
return;
};
let reward = if outcome == "success" {
1.0f32
} else {
-1.0f32
};
let lr = cfg.learning_rate;
let persist_interval = cfg.persist_interval;
let memory = self.services.memory.persistence.memory.clone();
self.try_spawn_learning_task(async move {
if !rl_head.update(reward, lr) {
tracing::debug!(
skill = selected_skill,
"rl_head: no forward cache, skipping update"
);
return;
}
let update_count = rl_head.update_count();
if (persist_interval == 0 || update_count % persist_interval == 0)
&& let Some(mem) = memory
{
// #5974: snapshot embed_dim/bytes/baseline/update_count under one lock
// acquisition — this handle may be shared with concurrent sessions, and a
// second `update()` landing between separate locked reads would otherwise
// desync the persisted `update_count` column from the blob's own copy.
let (embed_dim, bytes, baseline, count) = rl_head.persist_snapshot();
let embed_dim = i64::try_from(embed_dim).unwrap_or(i64::MAX);
let baseline = f64::from(baseline);
let count = i64::from(count);
if let Err(e) = mem
.sqlite()
.save_routing_head_weights(embed_dim, &bytes, baseline, count)
.await
{
tracing::debug!(
skill = selected_skill,
"rl_head: failed to persist weights: {e:#}"
);
}
}
});
}
}