feat(report): anomaly detection + LLM-written stories (Phase 4 M-A6)#13
Merged
Merged
Conversation
Heuristic detector flags unusual patterns over today / week
windows. The story wrapper turns each detection into a one-sentence
narrative; failure modes (provider off, budget exhausted, parse
error) all fall through to a deterministic template so the surface
never panics or blanks out.
Five detector rules in fluxmirror-core::report::anomaly:
- FileEditSpike — single file's edit count > 3x rolling 4-week mean
(with FILE_EDIT_MIN floor of 5 to suppress noise).
- ToolMixDeparture — cosine distance between window's tool mix and
baseline mix >= 0.4.
- NewAgent — agent name in window not seen in trailing 30d.
- NewMcpMethod — same rule applied to events.method.
- CostPerCallRise — window's avg cost-per-call > 2x baseline.
Each detection carries up to 5 evidence strings.
Wrapper layer (fluxmirror-ai::synthesise_anomaly) sits where
session_intent already does so the core crate stays out of the
fluxmirror-ai dependency cycle. The wrapper builds a json ctx,
calls synthesise() with the existing 'anomaly' prompt template,
and on any error substitutes a heuristic template tagged
source=heuristic.
Studio surface:
- GET /api/anomalies?window=today|week returns Vec<AnomalyStory>.
1h response cache keyed on (window, db_path); detector itself is
cheap, the cache exists to amortise the synthesise() leg.
- studio-web Today renders a 'Notable' section from the response;
empty list omits the section entirely (no empty-state placeholder).
- studio-web Home shows the top story as a single tile with a
'see all' link to /today#notable.
Tests:
- crates/fluxmirror-core/tests/anomaly_detect.rs — five rules x
positive/negative fixtures = 10 deterministic tests.
- crates/fluxmirror-studio/tests/api_anomalies.rs — fixture DB
with seeded edit spike round-trips through /api/anomalies and
comes back with source=heuristic when the provider is off.
Side fix: api_projects.rs and projects_clustering.rs Session
fixtures were missing the 'intent' field added in M-A3 — added
intent: None so the workspace cargo test passes again.
Capture binary deps unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 4 M-A6. Adds a heuristic anomaly detector over today / week
windows, plus an LLM story wrapper that turns each detection into
a one-sentence narrative. Surfaces as a 'Notable' section on /today
and a single tile on the home page.
Detector rules (fluxmirror-core::report::anomaly)
Five deterministic, side-effect-free rules. Each runs against the
current window vs a rolling 4-week baseline.
Each detection ships up to 5 evidence strings.
Story wrapper (fluxmirror-ai::synthesise_anomaly)
Wraps each detection. Lives in fluxmirror-ai (not fluxmirror-core)
because synthesise() depends on the budget / cache / provider
modules, and pushing it down to core would re-introduce the cycle
session_intent already solved by living here. On any failure path
(provider off, missing store, parse error, budget hit) the wrapper
substitutes a deterministic template tagged source=heuristic — no
panics, no blank UI.
Studio surface
1h response cache keyed on (window, db_path); detector itself is
cheap, the cache exists to amortise the synthesise() leg. Invalid
window values 400.
empty list omits the section entirely (no empty-state UI).
/today#notable.
AnomalyStory interfaces and a getAnomalies(window) helper.
Tests
positive/negative fixtures = 10 tests, all deterministic.
with seeded edit spike round-trips through /api/anomalies and
comes back with source=heuristic when the provider is off.
Side fix: api_projects.rs and projects_clustering.rs Session
fixtures were missing the 'intent' field added in M-A3 — added
intent: None so the workspace cargo test passes again.
Capture binary deps unchanged.
Test plan