Skip to content

feat(report): anomaly detection + LLM-written stories (Phase 4 M-A6)#13

Merged
rojae merged 1 commit into
feature/phase4/basefrom
feature/phase4/anomaly-stories
May 4, 2026
Merged

feat(report): anomaly detection + LLM-written stories (Phase 4 M-A6)#13
rojae merged 1 commit into
feature/phase4/basefrom
feature/phase4/anomaly-stories

Conversation

@rojae

@rojae rojae commented May 4, 2026

Copy link
Copy Markdown
Contributor

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.

Rule Trigger
FileEditSpike One file's edit count > 3x rolling 4-week mean (FILE_EDIT_MIN floor of 5).
ToolMixDeparture Cosine distance between window vs baseline tool 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 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

  • GET /api/anomalies?window=today|week returns Vec.
    1h response cache keyed on (window, db_path); detector itself is
    cheap, the cache exists to amortise the synthesise() leg. Invalid
    window values 400.
  • studio-web Today renders the 'Notable' section under id=notable;
    empty list omits the section entirely (no empty-state UI).
  • studio-web Home renders the top story as a tile linking to
    /today#notable.
  • studio-web/lib/api.ts gains AnomalyKind / AnomalySource /
    AnomalyStory interfaces and a getAnomalies(window) helper.

Tests

  • crates/fluxmirror-core/tests/anomaly_detect.rs — 5 rules x
    positive/negative fixtures = 10 tests, all deterministic.
  • 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.

Test plan

  • cargo test --workspace --no-fail-fast (clean)
  • scripts/test-rust-hook.sh
  • scripts/check-report-commands.sh
  • scripts/build-manifests.sh --check
  • scripts/check-extension-shape.sh
  • studio-web pnpm build

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.
@rojae rojae merged commit d8ea009 into feature/phase4/base May 4, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant