Skip to content

feat: summary reliability — pre-warm the model to prevent failures, and heal the ones that still happen #178

Description

@fmasi

Problem

When the summary endpoint is unreachable or the model isn't loaded, the summary is lost permanently. The transcript survives (it is the canonical artifact and always written first), but the -summary.md never appears, and nothing ever tries again — the user has to notice the failed notification and manually re-run Parley summarize -i <transcript>.

This is not hypothetical. Two real recordings lost their summary to endpoint failures:

The realistic everyday cases are simpler still: LM Studio isn't running, the model was unloaded to free VRAM, or the machine was on battery/asleep when a recording finished. A meeting summary is exactly the thing a user assumes happened and only discovers is missing days later.

Why this fits the product

The summary is a derivative — the transcript is canonical — so a failure is recoverable in principle. Nothing currently acts on that. Given the airgapped, courtroom-grade framing, a silently-missing derivative is a smaller failure than a corrupt transcript but still a gap the system already has enough information to close on its own.

Sketch

Persist the intent, not just the failure. On a failed summary, record a pending marker (e.g. ~/Library/Application Support/Parley/pending-summaries.json, or a field in the session state) holding the transcript path, the failure kind, and an attempt count.

Retry on the signals that actually mean "it might work now":

  • app launch (covers "the Mac was asleep / LM Studio wasn't up yet")
  • endpoint reachability regained — a cheap probe already exists in LMStudioSummaryProvider.fetchLoadedState() (/api/v0/models, 5 s timeout)
  • a bounded backoff while the app is running

Only retry what is worth retrying. The URLError mapping added alongside #173 makes the failure kinds distinguishable, which is what makes this safe:

  • .timedOut, .cannotConnectToHost, .cannotFindHost, .notConnectedToInternet → retry
  • SummaryError.invalidEndpoint, auth failures, context-overflow after self-correction → do NOT retry; these need the user to change something

Constraints that matter:

  • Idempotent — skip if -summary.md already exists. A user who ran summarize by hand must not get a duplicate or an overwrite.
  • Airgap — retry only the configured endpoint. Never fall back to a different provider, and never let a retry be the thing that first sends a transcript off-machine (see SummaryDisclosure, Make the airgap claim auditable: record whether a transcript's contents ever left the machine #138).
  • Quiet — do not re-notify per attempt. One notification when a pending summary eventually succeeds is useful; a notification per retry is how the whole notification channel gets muted (the same reasoning as gotcha bug: CLIHandler --debug may crash on exit if /usr/bin/log failed to launch #62 on the capture-anomaly label).
  • Bounded — give up after N attempts or M days and say so once, rather than retrying a dead endpoint forever.
  • Batched — several pending transcripts should drain in order, not race.

Acceptance sketch

  • Kill LM Studio, record a short meeting, confirm the transcript is written and a pending marker exists.
  • Start LM Studio, and without any user action the summary appears (on launch, or on the reachability probe).
  • Re-running the retry with a summary already present is a no-op.
  • A permanently bad endpoint stops retrying and reports once.

Related

#173 (timeout, fixed), #157 (summary quality), #37 (Apple Intelligence provider — a second provider makes the "don't silently switch providers" constraint more important, not less).


Part 2: prevent the failure, not only recover from it

Retry and pre-warm are the same goal approached from opposite ends — keep the summary from failing,
and make a failure self-correcting when it happens anyway. Tracking both here.

The measurement that motivates it (2026-08-14, this machine)

Same transcript throughout: the 2026-08-11 meeting, 500 segments / ~7.7k tokens / 32.6 minutes.

condition time
Cold — model not loaded 36 s
Warm — model resident 13 s
The real failure, 2026-08-11 60.5 s → timed out

So a cold load costs ~23 s (36 − 13), and idle-cold is comfortably inside even the old 60 s
default. The failure was not explained by transcript size.

Why it failed anyway: the summary runs at the worst possible moment

Auto-summary fires immediately after the transcript is written — which is precisely when
transcription and diarization have just finished saturating the ANE/GPU and memory pressure is
highest. That session's log also shows CoreAudio reporting overload with Teams still running. Cold
load plus contention plus a busy machine pushed a 36 s job past 60 s.

Two consequences worth designing around:

  1. Every post-meeting summary currently pays the cold-load penalty, because LM Studio unloads
    the model between uses (observed again while measuring: no model loaded, hours after the last
    summary). The penalty lands at the one moment the machine can least afford it.
  2. Timing, not size, is the risk. A longer timeout (Summary request has no explicit timeout — 60s URLSession default kills long local-LLM summaries #173, now 600 s) stops the failure being
    fatal, but it does not stop the summary being slow and contended.

Pre-warm sketch

Trigger the load when a recording STOPS, not when the summary request arrives. At that point the
user is done talking, the pipeline is starting its work, and there are typically seconds-to-minutes
before the transcript is ready — free wall-clock in which the model can load. By the time the
summary is requested it is resident, and the request is a 13 s job instead of a 36 s one.

Mechanism: LM Studio's REST API can load a model explicitly, and fetchLoadedState()
(/api/v0/models) already reports whether it is resident, so the check is cheap and exists.

Constraints:

  • Only if a summary is actually going to run — respect summary.enabled and the configured
    provider. Never load a model for a user who has summaries off.
  • Never block the pipeline. Warming is best-effort and fire-and-forget; a failure to warm must
    not delay or fail the transcript, which is the canonical artifact.
  • Don't fight the user's memory. If they unloaded the model deliberately to free VRAM,
    re-loading it on every recording is hostile. Worth a setting, defaulting to on only when the
    endpoint is local.
  • Consider deferring the summary slightly instead of (or as well as) warming — waiting for the
    ASR/diarization pipeline to go quiet costs the user nothing, since nobody reads a summary in the
    first seconds, and it removes the contention rather than merely out-waiting it.

How the two halves fit

  • Pre-warm removes the most common cause (cold load at peak contention).
  • Retry/heal covers everything pre-warming cannot: server not running at all, machine asleep,
    model evicted under memory pressure, user quit LM Studio mid-pipeline.

Neither subsumes the other, and the retry classification work already done makes both safe: a
timeout or unreachable host is worth retrying, an auth failure or bad endpoint is not.

Related

#173 (timeout, fixed — a 600 s ceiling means slow no longer means lost). Note also that LM Studio
now REQUIRES a bearer token, so 401 is a live failure mode on this setup, not theoretical — which is
why the retry classifier must treat auth failures as non-retryable.

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