What's happening
EphemeralAgent::get_patterns() is supposed to return the list of patterns the agent has learned so far. In practice it returns an empty Vec — every time, regardless of how much the agent has learned.
The line at fault
In crates/sona/src/training/federated.rs, around line 232:
pub fn get_patterns(&self) -> Vec<LearnedPattern> {
self.engine.find_patterns(&[], 0)
}
The call is asking the engine two things at once:
- "Give me the patterns most similar to this query embedding:
&[]" (an empty vector)
- "Return the top
k = 0 of them"
Both halves are degenerate. k = 0 means "return zero results." The engine complies and hands back Vec::new(). So get_patterns() answers its "please give me everything the agent learned" contract with "nothing, ever" — silently.
The shape of the call (passing &[] and 0 as literals, rather than forwarding real arguments) strongly suggests this was scaffolding that compiled and was never revisited.
Why this is easy to miss
A consumer calling agent.get_patterns() gets vec![] back. No error, no panic, no log line. If the downstream UI then renders "no patterns yet" or simply hides an empty panel, the agent appears to be doing its job — it just appears to be a very quiet learner. Stats surfaces that read from get_stats() instead (which works fine) continue to report the correct pattern counts, so any "how many patterns do you have?" check gives the impression that everything is wired up.
I found this while building a consumer that tried to visualise the learned-pattern list and was confused why it was always empty despite get_stats().patterns growing.
What's actually available in the engine
The underlying engine.find_patterns(query, k) works — it does a proper cosine-similarity top-k lookup. And there's a get_all_patterns() method on the engine that returns the full accumulated list. Neither is exposed through EphemeralAgent:
get_patterns() should almost certainly delegate to engine.get_all_patterns() (the literal "everything you've learned" contract).
- There's no public
find_patterns(query, k) on EphemeralAgent at all — so consumers who want a query-ranked top-k have no way to ask for one, even though the engine can do it.
The WASM binding inherits the same gap: WasmEphemeralAgent::getPatterns returns empty, and there's no findPatterns JS method on the WASM wrapper at all.
Suggested fix (sketch)
A narrow, two-file change:
federated.rs: make get_patterns() call self.engine.get_all_patterns(), and add a public fn find_patterns(&self, query: &[f32], k: usize) that forwards to the engine.
wasm.rs: add a #[wasm_bindgen(js_name = findPatterns)] binding on WasmEphemeralAgent that accepts a Vec<f32> + usize and returns the serialized pattern list.
I have a ready-to-review PR with exactly this change — linking it shortly.
Version / context
Found against main (upstream head at time of reporting: 6964dfdc). Reproducible by constructing an EphemeralAgent, calling agent.observe(...) / the equivalent pattern-accumulation path enough times that get_stats().patterns > 0, then calling get_patterns() — which will still return vec![].
Happy to adjust the fix shape (e.g. rename, deprecate get_patterns in favour of find_patterns(query, k), or anything else) based on what you'd prefer for the public API.
What's happening
EphemeralAgent::get_patterns()is supposed to return the list of patterns the agent has learned so far. In practice it returns an emptyVec— every time, regardless of how much the agent has learned.The line at fault
In
crates/sona/src/training/federated.rs, around line 232:The call is asking the engine two things at once:
&[]" (an empty vector)k = 0of them"Both halves are degenerate.
k = 0means "return zero results." The engine complies and hands backVec::new(). Soget_patterns()answers its "please give me everything the agent learned" contract with "nothing, ever" — silently.The shape of the call (passing
&[]and0as literals, rather than forwarding real arguments) strongly suggests this was scaffolding that compiled and was never revisited.Why this is easy to miss
A consumer calling
agent.get_patterns()getsvec![]back. No error, no panic, no log line. If the downstream UI then renders "no patterns yet" or simply hides an empty panel, the agent appears to be doing its job — it just appears to be a very quiet learner. Stats surfaces that read fromget_stats()instead (which works fine) continue to report the correct pattern counts, so any "how many patterns do you have?" check gives the impression that everything is wired up.I found this while building a consumer that tried to visualise the learned-pattern list and was confused why it was always empty despite
get_stats().patternsgrowing.What's actually available in the engine
The underlying
engine.find_patterns(query, k)works — it does a proper cosine-similarity top-k lookup. And there's aget_all_patterns()method on the engine that returns the full accumulated list. Neither is exposed throughEphemeralAgent:get_patterns()should almost certainly delegate toengine.get_all_patterns()(the literal "everything you've learned" contract).find_patterns(query, k)onEphemeralAgentat all — so consumers who want a query-ranked top-k have no way to ask for one, even though the engine can do it.The WASM binding inherits the same gap:
WasmEphemeralAgent::getPatternsreturns empty, and there's nofindPatternsJS method on the WASM wrapper at all.Suggested fix (sketch)
A narrow, two-file change:
federated.rs: makeget_patterns()callself.engine.get_all_patterns(), and add a publicfn find_patterns(&self, query: &[f32], k: usize)that forwards to the engine.wasm.rs: add a#[wasm_bindgen(js_name = findPatterns)]binding onWasmEphemeralAgentthat accepts aVec<f32>+usizeand returns the serialized pattern list.I have a ready-to-review PR with exactly this change — linking it shortly.
Version / context
Found against
main(upstream head at time of reporting:6964dfdc). Reproducible by constructing anEphemeralAgent, callingagent.observe(...)/ the equivalent pattern-accumulation path enough times thatget_stats().patterns > 0, then callingget_patterns()— which will still returnvec![].Happy to adjust the fix shape (e.g. rename, deprecate
get_patternsin favour offind_patterns(query, k), or anything else) based on what you'd prefer for the public API.