Skip to content

feat(session): LLM intent subtitle on every session (Phase 4 M-A3)#11

Merged
rojae merged 1 commit into
feature/phase4/basefrom
feature/phase4/session-intent
May 4, 2026
Merged

feat(session): LLM intent subtitle on every session (Phase 4 M-A3)#11
rojae merged 1 commit into
feature/phase4/basefrom
feature/phase4/session-intent

Conversation

@rojae

@rojae rojae commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 3 M5 ships a heuristic <Verb>: <Object> (<Tail>) name for every
clustered work session. M-A3 layers an optional LLM-classified
one-sentence intent subtitle on top, rendered italicised under the
heuristic name in the studio. Cached per session id — re-running a
window is a free cache hit.

What changed

DTO extension

  • Session.intent: Option<String> with serde(default, skip_serializing_if = "Option::is_none").
  • Older snapshots without the key still parse via serde(default).
  • The key is elided from the wire shape entirely when None.

build_intent flow

  • New fluxmirror-ai::session_intent module exposing
    synthesise_session_intents(store, config, &mut [Session]).
  • Triple-gated:
    1. config.ai.provider == "off" → no-op, no DB / network.
    2. No writable SqliteStore available → no-op.
    3. Any synthesise() error → that session's intent stays None,
      others continue.
  • Cache key is sha256(model + system + redacted_user + version), so
    same session id ⇒ same prompt body ⇒ same cache row.
  • Lives in fluxmirror-ai (not fluxmirror-core) because
    fluxmirror-ai → fluxmirror-core already, and the inverse would
    cycle. The studio's /api/sessions and /api/session/:id handlers
    call the decorator immediately after collect_sessions /
    collect_session_detail returns.

Frontend wiring

  • studio-web/src/lib/api.ts: Session.intent?: string.
  • Sessions.tsx: italic subtitle under the heuristic name in each row.
  • Session.tsx: italic subtitle in the detail header.
  • Home.tsx: italic subtitle on each "recent sessions" card.
  • All three render nothing extra when intent is absent.

Studio plumbing

  • AppState now carries Arc<Config> plus Option<Arc<SqliteStore>>
    (writable handle for the AI cache + budget layer, distinct from the
    read-only dashboard handle).
  • main.rs opens the writable store only when provider != "off" and
    warns + falls back when the open fails.
  • AppState::new(db, db_path) helper keeps existing test fixtures
    terse — defaults to provider="off" + ai_store: None.

Prompt

  • prompts/session.txt bumped to # version: 2 with placeholders
    matching the new context object (name, lifecycle, tool_mix_json,
    top_files_json, event_count). Version bump invalidates every
    cached response from earlier iterations.

Fallback behaviour

The heuristic surface is unchanged. With provider="off" (default
when no AI is configured):

  • Sessions cluster + classify + name correctly.
  • The intent JSON key is elided everywhere.
  • Frontend renders the heuristic name and skips the subtitle.

Test plan

  • crates/fluxmirror-core/tests/session_intent.rs — heuristic
    flow still works under provider="off"; legacy + new wire
    shapes both deserialise; intent key is omitted on serialise
    when None.
  • crates/fluxmirror-studio/tests/api_sessions_intent.rs
    /api/sessions and /api/session/:id both elide intent
    when AI is off; struct parses both shapes.
  • All Phase 3 M5 tests still green
    (api_sessions.rs, api_session_detail.rs).
  • pnpm install --frozen-lockfile && pnpm build (studio-web).
  • FLUXMIRROR_SKIP_WEB_BUILD=1 cargo test --workspace --no-fail-fast.
  • bash scripts/test-rust-hook.sh.
  • bash scripts/check-report-commands.sh.
  • bash scripts/build-manifests.sh --check.
  • bash scripts/check-extension-shape.sh.

Phase 3 M5 already gives every clustered work session a heuristic
"<Verb>: <Object> (<Tail>)" name. M-A3 layers an optional
LLM-classified one-sentence intent subtitle on top, rendered
italicised under the heuristic name in the studio.

Pipeline:
  - DTO: add Session.intent: Option<String> with serde(default,
    skip_serializing_if = "Option::is_none") so older snapshots
    without the field still parse and the wire shape stays lean
    when AI is off.
  - fluxmirror-ai: new session_intent module exposing
    synthesise_session_intents(store, config, &mut [Session]).
    Triple-gated — provider="off", missing writable store, or any
    synthesise() error all leave intent = None for that session.
    Reuses the existing cache/budget/redact stack from M-A1, so
    same session id ⇒ same prompt body ⇒ free cache hit.
  - Studio: AppState now carries Arc<Config> + Option<Arc<SqliteStore>>
    for AI cache writes. List + detail handlers call the decorator
    after collect_sessions / collect_session_detail. Tests use the
    new AppState::new helper that defaults to provider="off".
  - Frontend: api.ts adds intent?: string. Sessions list, Session
    detail header, and Home "recent sessions" panel render the
    intent as italic subtitle under the heuristic name; absent
    when not present.
  - Prompt: session.txt bumped to v2, swapped placeholders to
    match the new context object (name, lifecycle, tool_mix_json,
    top_files_json, event_count). Version bump invalidates every
    cached response from earlier prompt iterations.

Tests:
  - crates/fluxmirror-core/tests/session_intent.rs verifies the
    heuristic flow still succeeds with provider="off" and that
    the legacy/new wire shapes both deserialise.
  - crates/fluxmirror-studio/tests/api_sessions_intent.rs covers
    list + detail: intent key elided when AI is off, struct parses
    both shapes.

Cargo.lock churn from adding fluxmirror-ai as a fluxmirror-studio
dependency.

Heuristic surface stays untouched — sessions still cluster +
classify + name correctly when no AI provider is configured.
@rojae rojae merged commit 6a5b505 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