Repository navigation
feat: summaries in the publishing pipeline [D-621] - #207
Merged
Merged
Conversation
`IsConfigured()` required an API key AND a base URL, so pointing Cassini at a
self-hosted OpenAI-compatible server — llama.cpp, vLLM, Ollama, none of which
usually authenticate — silently disabled readable cleanup and summaries
entirely. There is no error and no log line that says why; the pipeline just
publishes without a summary. That makes the comparison D-621 exists to run
(a local model against the paid one, on real meetings) impossible to even set
up, which is why this comes first.
The base URL is now what enables the capability, and the key is optional:
IsConfigured() == !Disabled && BaseURL != ""
That has a consequence worth naming, because it is not obvious from the diff.
`CASSINI_SUMMARY_DISABLED` worked by blanking the API key and relying on
IsConfigured() to reject the result. With the key no longer required, blanking
it disables nothing — so the kill-switch needs a real field. `LLMConfig.Disabled`
is that field, applied to a copy taken *before* either switch, so the two
capabilities stay independent in both directions.
Readable cleanup gets its own switch, `CASSINI_READABLE_DISABLED`, in the same
change. Without it this fix would quietly switch cleanup back on for every local
endpoint, because the two share the gate — a decision made by accident rather
than on purpose. We have dropped cleanup for our own use; others may want it;
either way it should not ride summarisation's switch.
Two more small blockers on the same path:
- The request timeout was hardcoded at 240s, which is short for a CPU-bound
local model on a long prompt. `CASSINI_LLM_TIMEOUT_SEC` now sets it,
default raised to 900s. Note this is a per-request bound and readable
cleanup makes many sequential requests, so a wedged endpoint can stall a
build longer than before; build jobs have no operator-side timeout.
- `max_tokens` was hardcoded at 4096, which truncates a long summary.
`CASSINI_LLM_MAX_TOKENS` now sets it, same default.
`Authorization` is omitted entirely when there is no key rather than sent as
`Bearer ` with an empty value, which some self-hosted servers reject outright.
Finally, the manifest. `SUMMARY_MODEL` and `CASSINI_SUMMARY_DISABLED` were
implemented but never declared in appinfo/info.xml, and AppAPI silently drops
undeclared keys — so on a real install they were unreachable no matter what an
administrator set. They are declared now, along with the three new keys, and
carried through the deployment bundle and the harness ExApp registration.
Docs across six files described the API key as the switch. They now describe the
base URL as the switch, and the privacy note is about "whatever endpoint this
names" rather than "that third party", since it may now be your own host.
One upgrade behaviour change: anyone who had set LLM_BASE_URL without a key was
getting nothing, and after this gets both steps.
Tests in llm_config_test.go cover keyless vs keyed vs endpointless config, each
kill-switch independently, the request-bound overrides and their defaults, and
the wire behaviour — that the header is absent when keyless, present when keyed,
and that the configured max_tokens reaches the request body.
…[D-621] Drop the five new deploy-time declarations — SUMMARY_MODEL, CASSINI_SUMMARY_DISABLED, CASSINI_READABLE_DISABLED, CASSINI_LLM_TIMEOUT_SEC, CASSINI_LLM_MAX_TOKENS — from info.xml, compose.yml, the harness passthrough and the ExApp docs. They stay as recorder env: that is how the operator will drive `cassini build` from persisted settings (D-696) and how scripts configure it. But an AppAPI deploy option can only be changed by re-creating the container, and D-696 replaces that path with in-app settings, so declaring them now adds install-form fields we are about to retire. LLM_BASE_URL, OPENROUTER_API_KEY and LLM_MODEL remain as the install-time seed, with the reworded descriptions. Adds the changelog fragment D-621 asks for, including the upgrade note. Recorder code and tests unchanged.
delasous
marked this pull request as ready for review
September 1, 2026 14:07
…-endpoint # Conflicts: # cassini-go-recorder/internal/transcribe/llm.go # docs/exapp-install.md
The operator now owns the LLM policy instead of the deploy environment:
llm-settings.json beside the job database holds named providers (base URL +
write-only key) and, for readable cleanup and the meeting summary, whether the
step runs, on which provider, with which model. GET/PUT /settings/llm reads and
changes it; GET /settings/llm/providers/{id}/models proxies the endpoint's
model list so the key stays server-side.
Env is now the operator→recorder wire only. ChildEnv strips every inherited
LLM variable and emits a resolved per-step set — READABLE_* / SUMMARY_* — so
"off" is simply no endpoint; the recorder learns those per-step overrides
(an endpoint override brings its own key). The deploy trio LLM_BASE_URL /
OPENROUTER_API_KEY / LLM_MODEL seeds the file once on first start and is
ignored afterwards; an install configured only by env behaves as before.
A third admin-gated surface, Settings, beside Browse and Operator — same fragment routing and the same admin probe as the operator surface. It hosts the transcription-quality panel (moved off the operator page) and a new LLM panel: endpoint CRUD with write-only keys, per-step enable/endpoint/model for readable cleanup and the meeting summary (model field is free text with a fetched model list behind it), and the request bounds. The client normalizes the /settings/llm contract defensively and never carries a raw key.
delasous
marked this pull request as draft
September 2, 2026 09:35
Decision (Chris + Silvio): transcripts publish as transcribed — speaker attribution and paragraph breaks, no LLM rewriting. The meeting summary is now the only LLM step. Gone with the step: its pipeline stage and batch client; the config surface (CASSINI_READABLE_DISABLED, CASSINI_READABLE_STRICT_BATCHES, the per-step READABLE_* variables, BuildConfig.LLM); the readable step in the operator's LLM settings and the app's Settings panel; and the preferred-spellings vocabulary end to end (STT settings field, CASSINI_TRANSCRIPTION_TERMS, the panel section) — it existed only to steer the cleanup prompt. Captions are now always written, from the canonical transcript; they were previously a by-product of cleanup and vanished without an LLM endpoint. ChildEnv still strips the retired variable names so an old deploy env cannot leak them to the recorder, and the seed still honours a legacy CASSINI_SUMMARY_DISABLED. The portable format keeps reading files that carry readable transcripts, and the viewer keeps rendering them: old meetings are untouched.
# Conflicts: # cassini-app/src/SettingsPanel.svelte # cassini-app/src/operator/client.test.ts # cassini-app/src/operator/client.ts # cassini-app/src/operator/types.ts # cassini-operator/internal/operator/build_runtime.go # cassini-operator/internal/operator/settings.go # cassini-operator/internal/operator/settings_test.go
The exapp image's web-builder predated the root package-lock.json and ran npm install over the stale per-package lockfiles, which crashes npm 10+ (edgesOut of null). npm ci from the root lockfile is what CI and local builds already do.
delasous
marked this pull request as ready for review
September 3, 2026 16:05
A timeout describes the endpoint's host — a CPU-bound local model needs a longer leash than a hosted API — so timeout_sec and max_tokens move from a global section onto each provider, in the settings file, the API, and the panel. The wire is unchanged: ChildEnv emits the resolved endpoint's bounds. The panel shows an empty field with the default as placeholder (900s / 4096) instead of a literal 0. A pre-existing llm-settings.json with root-level bounds loses them (only the harness ever wrote one); the env seed now attaches CASSINI_LLM_TIMEOUT_SEC / MAX_TOKENS to the seeded provider.
# Conflicts: # cassini-go-recorder/internal/cassini/retag.go # changelog.d/viewer-published-v1-transcript.md # deployment/Dockerfile.exapp
Main's published-v1-only rework changed buildPortableOpusFixture to an options object with payload-ref transcripts; the D-697 summary test still passed the manifest directly and crashed on wire.readableTranscripts.
Assert that decoder vocabulary, keyless summary settings, STT quality, and the Talk secret all survive the composed child environment. Remove stale documentation that still described portable summaries as future work.
Resolves two viewer conflicts left by main's 1379554, which retired `pickReadableForTranscript` in favour of `pickDisplayForTranscript` while #207 was adding the sealed-summary reader alongside it. - loadArtifact.ts: drop the import of the deleted function (its call sites are already on pickDisplayForTranscript), keep readPortableSummaryMarkdown, which still feeds `summary:`. - portable.test.ts: keep #207's readPortableSummaryMarkdown suite and main's pickDisplayForTranscript suite, which supersedes the readable one and covers the same cases plus the flagged-default variant.
This was referenced Sep 8, 2026
ikusteu
added a commit
that referenced
this pull request
Sep 9, 2026
Main brought the insight surfaces (D-700, D-715..D-724), the meetings-list endpoint (D-701) and the harness seed pack; this branch made Nextcloud access control an opt-in storage model with two archive roots (D-616, D-708). Fifteen files conflicted. Most were additive unions; three were real design meetings. The catalog resolution. D-701 extracted resolveCatalogForCaller so `published/meetings-list` and `catalog.json` share ONE access-control path, and built it on the per-caller PROPFIND intersect. This branch had already split that read in two — the intersect for the Team folder, a plain owner read for the private root — because a per-caller scan of a tree only the service account has can answer nothing but 404. Resolved by moving the model decision INTO resolveCatalogForCaller: it dispatches to resolveOwnerCatalog or resolveACLCatalogForCaller, serveOwnerCatalog is re-expressed over the first, and D-701's invariant survives intact — every reader still goes through one resolution. Without that join `published/meetings-list` would have answered "you may read no meetings" on every default-model install, the exact false negative the endpoint exists to prevent. TestMeetingsListInTheDefaultModelServesTheWhole Archive pins it and fails without the dispatch. The read identity. Main's context-bundle staging reads as the caller from a hard-coded ncRecordingsRoot, which this branch had already split into two constants. Identity and root are one question, so they are now answered in one place: ncArchiveReadIdentity, used by the read proxy, readableMeetingsForCaller and stageMeetingForContext alike. Pairing the wrong identity with the wrong root is the disclosure that guard exists to prevent, and it can no longer be done half-way. The surfaces. D-723 folded #207's `surface=settings` into a panel nav inside Operator; this branch had added `setup` as a third surface. Both kept: settings is gone as a surface, `setup` stays one, because it does not configure the pipeline — it decides where the whole archive lives, and moves it. /setup now carries awaiting_choice AND features, and readSetupHealth reads both. Verified: both Go modules build, vet and test clean; the app's 367 tests and the viewer's suite pass; gofmt, shellcheck and the offline script suites are green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B2TDAnxLKfdKKdVDPFRvYL
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.
Summaries as an automatic part of the publishing pipeline (D-621). Integration branch for the whole track; same tip as
feat/summary.llm-settings.json,GET/PUT /settings/llm, named endpoints with write-only keys, model discovery proxied via the operator. Deploy env seeds the first start; endpoint changes never need a redeploy again..opus(D-697) — retroactive for already-published meetings.cassini meetings summarizebackfills summaries into sealed meetings from their own transcripts, in place, audio untouched.Upgrade notes:
LLM_BASE_URLset without a key used to do nothing and now enables summaries; the env trio is read once to seed the settings, then ignored.Suites green: recorder (16 pkgs), operator, viewer (406), app (93). Changelog fragments included.
🤖 Generated with Claude Code