feat(session): LLM intent subtitle on every session (Phase 4 M-A3)#11
Merged
Merged
Conversation
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.
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 3 M5 ships a heuristic
<Verb>: <Object> (<Tail>)name for everyclustered 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>withserde(default, skip_serializing_if = "Option::is_none").serde(default).None.build_intentflowfluxmirror-ai::session_intentmodule exposingsynthesise_session_intents(store, config, &mut [Session]).config.ai.provider == "off"→ no-op, no DB / network.SqliteStoreavailable → no-op.synthesise()error → that session'sintentstaysNone,others continue.
sha256(model + system + redacted_user + version), sosame session id ⇒ same prompt body ⇒ same cache row.
fluxmirror-ai(notfluxmirror-core) becausefluxmirror-ai → fluxmirror-corealready, and the inverse wouldcycle. The studio's
/api/sessionsand/api/session/:idhandlerscall the decorator immediately after
collect_sessions/collect_session_detailreturns.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.intentis absent.Studio plumbing
AppStatenow carriesArc<Config>plusOption<Arc<SqliteStore>>(writable handle for the AI cache + budget layer, distinct from the
read-only dashboard handle).
main.rsopens the writable store only whenprovider != "off"andwarns + falls back when the open fails.
AppState::new(db, db_path)helper keeps existing test fixturesterse — defaults to
provider="off"+ai_store: None.Prompt
prompts/session.txtbumped to# version: 2with placeholdersmatching the new context object (
name,lifecycle,tool_mix_json,top_files_json,event_count). Version bump invalidates everycached response from earlier iterations.
Fallback behaviour
The heuristic surface is unchanged. With
provider="off"(defaultwhen no AI is configured):
intentJSON key is elided everywhere.Test plan
crates/fluxmirror-core/tests/session_intent.rs— heuristicflow still works under
provider="off"; legacy + new wireshapes both deserialise;
intentkey is omitted on serialisewhen
None.crates/fluxmirror-studio/tests/api_sessions_intent.rs—/api/sessionsand/api/session/:idboth elideintentwhen AI is off; struct parses both shapes.
(
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.