Skip to content

EphemeralAgent::get_patterns() returns an empty list because of a stub-shaped internal call #367

@shaal

Description

@shaal

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:

  1. 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.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions