Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 123 additions & 73 deletions apps/engine/src/distill/defaults.ts

Large diffs are not rendered by default.

41 changes: 40 additions & 1 deletion apps/engine/src/distill/distiller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { PresetDocuments } from '../presets/index.js'
import type { WorkspaceRegistry } from '../store/index.js'
import { VoiceDocuments, compileVoiceVars, interpolateTemplate, resolveVoice } from '../voice/index.js'
import { bucketIntoWindows } from './merge.js'
import { NOTHING_NOTEWORTHY } from './defaults.js'
import { DistillDocuments } from './documents.js'
import { extractMoments, type ExtractInput } from './moments.js'
import { captureLaneLabel } from './transcribe.js'
Expand Down Expand Up @@ -69,6 +70,20 @@ export interface DistillerDeps {
const isText = (chunk: CaptureChunk): boolean =>
chunk.encoding === 'utf8' && chunk.source !== 'focus' && chunk.contentType !== 'application/json'

/**
* Silence over noise (#245) — the deterministic pre-gate: the minimum count of OBSERVED window material
* (chunk.data, never our machine-owned capture-lane labels — the fields.ts precedent) below which a window is
* too small to hold any substantive phrase, so it produces NO distillate (and no moments/entities) without
* ever spending an invoke. This floor is deliberately LOW — LOWER than the fast-field gates (topic 40 /
* entities 60 / work-items 80). Those fields are OPTIONAL derived views that skip when a specific slice lacks
* enough material to be worth deriving, so a high bar is harmless. The distill note is the PRIMARY record of
* what was said; suppressing a real short utterance ("We ship Thursday.", "agreed, ship it") would be data
* loss. So the deterministic layer only drops windows too small to carry even a two-word substantive phrase
* (stray ASR tokens, acknowledgements, garble) and delegates the harder "enough words, no real substance"
* judgment to the model's NOTHING_NOTEWORTHY sentinel below. 12 chars ≈ two short words.
*/
const DISTILL_MIN_CHARS = 12

const groupBySession = (chunks: readonly CaptureChunk[]): Map<string, CaptureChunk[]> => {
const groups = new Map<string, CaptureChunk[]>()
for (const chunk of chunks) {
Expand Down Expand Up @@ -210,6 +225,17 @@ export class Distiller {
const windows = bucketIntoWindows(sessionChunks, mode.distill.mergeWindow)
for (const window of windows) {
const workspaceId = window.chunks[0]!.workspaceId
// #245 silence gate (pre-gate): a window whose OBSERVED material is below the meaningful floor carries
// nothing a human notetaker would jot — skip the whole window (no distillate, and therefore no moments
// or entities from filler). Count chunk.data only, never the capture-lane labels the transcript below
// prefixes (fields.ts precedent: relevance is about observed material, not our label vocabulary). This
// is a cheap deterministic guard before any invoke; a window that clears it but still holds nothing is
// caught by the model's NOTHING_NOTEWORTHY sentinel after the summary call. Skips are logged, not silent.
const observedChars = window.chunks.map((chunk) => chunk.data).join('\n').trim().length
if (observedChars < DISTILL_MIN_CHARS) {
this.log(`distill window ${window.start}→${window.end} skipped: ${observedChars} < ${DISTILL_MIN_CHARS} observed chars (silence gate)`)
continue
}
// #116: ONE correlation id per window pass — the distillate, its moments, its entity mentions, and
// a guard hold (if the window is suspended) all share it, so the audit trail joins them exactly.
const spanId = this.newId()
Expand Down Expand Up @@ -280,6 +306,19 @@ export class Distiller {
throw err
}

// #245 silence gate (model escape): the summary template is instructed to answer with the exact
// sentinel when the window — though it cleared the character floor — holds nothing worth a note. Drop
// the whole window (no distillate, no moments, no entities) when the trimmed response is EITHER the
// exact sentinel (case-sensitive, full-text — a sentinel embedded inside a real note does NOT count,
// so a genuine note that mentions the token is kept) OR empty: an empty/whitespace-only response is
// the most literal "nothing", and persisting it as an empty-text distillate would defeat the gate.
// Determinism is preserved: the drop is a pure function of the (replayable) model text.
const summaryText = result.text.trim()
if (summaryText === '' || summaryText === NOTHING_NOTEWORTHY) {
this.log(`distill window ${window.start}→${window.end} dropped: ${summaryText === '' ? 'empty response' : 'nothing noteworthy'}`)
continue
}

const distillate: Distillate = {
id: this.newId(),
sessionId,
Expand All @@ -288,7 +327,7 @@ export class Distiller {
windowEnd: window.end,
sourceChunks: window.chunks.map((chunk) => chunk.id),
spanId,
text: result.text.trim(),
text: summaryText,
voice: {
scope: resolved.scope,
dials: resolved.dials,
Expand Down
75 changes: 68 additions & 7 deletions apps/engine/src/distill/documents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { PromptTemplate } from '@openinfo/contracts'
import { WorkspaceRegistry } from '../store/index.js'
import { NEUTRAL_DIALS, compileVoiceVars, interpolateTemplate } from '../voice/index.js'
import { DistillDocuments } from './documents.js'
import { PREVIOUS_BUILTIN_BODIES, defaultDistillTemplate, defaultEntitiesTemplate, defaultExtractTemplate } from './defaults.js'
import { NOTHING_NOTEWORTHY, PREVIOUS_BUILTIN_BODIES, defaultDistillTemplate, defaultEntitiesTemplate, defaultExtractTemplate, defaultSummaryTemplates } from './defaults.js'

const TEMPLATE_KIND = 'prompt-template'

Expand Down Expand Up @@ -41,7 +41,7 @@ test('a fresh install seeds the three neutral window templates (no baked voice v
})
})

test('the rendered default distill prompt for a fresh install contains no voice/persona vocabulary (re-bloat guard)', async () => {
test('the rendered default distill prompt for a fresh install carries no dial vocabulary and pins the notes-voice register (#245)', async () => {
await withStore((store) => {
const docs = new DistillDocuments(store)
docs.ensureDefaults()
Expand All @@ -54,11 +54,16 @@ test('the rendered default distill prompt for a fresh install contains no voice/
transcript: 'we shipped the thing',
})
assert.doesNotMatch(rendered, VOICE_VOCAB)
// and it dropped materially vs the previous voice-baked body (the length guard against re-bloat)
assert.ok(
docs.template().body.length < PREVIOUS_BUILTIN_BODIES[defaultDistillTemplate.id]!.length - 100,
'neutral distill body must be materially shorter than the old voice-baked body',
)
// The #245 register is opinionated by DESIGN, not by re-bloated dials: it drops the old slop framing,
// it explicitly BANS the transcription-narration openers, and it carries the silence sentinel.
const body = docs.template().body
assert.doesNotMatch(body, /tight, factual summary of what just happened/, 'the old slop framing is gone')
assert.match(body, /the transcript indicates/i, 'the body names the banned narration framings (as prohibitions)')
assert.match(body, /the speaker (said|expressed|stated)/i, 'the body bans the speaker-narration framings')
assert.ok(body.includes(NOTHING_NOTEWORTHY), 'the body instructs the silence sentinel')
// Still bounded (a re-bloat guard without depending on a since-superseded prior body): a note-taking
// instruction is a handful of lines, never a persona essay.
assert.ok(body.length < 1400, 'the distill body stays a tight instruction, not a re-bloated essay')
})
})

Expand Down Expand Up @@ -138,3 +143,59 @@ test('a builtin whose v1 body diverges from the previous shipped body is left un
assert.equal(now.body.body, 'some other v1 body {{transcript}}')
})
})

// #245 — the SUMMARY-family bodies (distill window notes + the five hierarchical levels) speak the human
// note-taking register: no transcription-narration framings, honesty preserved, and each hierarchical level
// still interpolates its bound placeholders. The banned framings appear in the bodies only as PROHIBITIONS.

// The narration framings the notes-voice register forbids the model from OPENING with. They legitimately
// appear inside the template bodies as the ban instruction itself, so this is a value-level, not body-level,
// guard: it is asserted against RENDERED slop in the suppression/pipeline tests, and asserted PRESENT (as a
// prohibition) in the body tests below.
const NARRATION_FRAMINGS = /the (speaker|transcript) (said|expressed|stated|indicates|shows|mentions)|it was (mentioned|noted|recorded|discussed)|the notes indicate/i

test('#245: every summary-family body drops the old slop framing and keeps the invent-nothing honesty rule', async () => {
const distillId = defaultDistillTemplate.id
const families = [defaultDistillTemplate, ...defaultSummaryTemplates]
for (const t of families) {
assert.doesNotMatch(t.body, VOICE_VOCAB, `${t.id} carries no dial vocabulary`)
assert.doesNotMatch(t.body, /(tight, factual summary of what just happened|You are writing a|You are naming|You are distilling)/, `${t.id} drops the old report-speak framing`)
assert.match(t.body, /invent nothing|no invented/i, `${t.id} keeps an invent-nothing honesty rule`)
// the leading instruction reads like a note-taking directive, never a minutes preamble
assert.match(t.body.split('\n')[0]!, /\b(jot|keep|write|name)\b/i, `${t.id} leads with a note-taking directive`)
// only the distill window template carries the silence sentinel (the distiller is the drop point);
// the hierarchical levels ride on suppressed distillates and are not sentinel-gated (#245 design).
if (t.id === distillId) {
assert.ok(t.body.includes(NOTHING_NOTEWORTHY), 'the distill body instructs the silence sentinel')
assert.match(t.body, NARRATION_FRAMINGS, 'the distill body names the banned narration framings as prohibitions')
}
}
})

test('#245: an UNEDITED summary builtin left at its #177 body auto-upgrades to the notes-voice body', async () => {
await withStore((store) => {
for (const t of defaultSummaryTemplates) {
store.layouts.put(TEMPLATE_KIND, t.id, { ...t, body: PREVIOUS_BUILTIN_BODIES[t.id]! })
}
const docs = new DistillDocuments(store)
docs.ensureDefaults()
for (const t of defaultSummaryTemplates) {
const now = store.layouts.getLatest<PromptTemplate>(TEMPLATE_KIND, t.id)!
assert.equal(now.version, 2, `${t.id} refreshed (version bumped once)`)
assert.equal(now.body.body, t.body, `${t.id} refreshed to the notes-voice body`)
}
})
})

test('#245: a USER-EDITED summary builtin is never clobbered by the refresh', async () => {
await withStore((store) => {
const target = defaultSummaryTemplates[0]! // rolling
store.layouts.put(TEMPLATE_KIND, target.id, { ...target, body: PREVIOUS_BUILTIN_BODIES[target.id]! })
store.layouts.put(TEMPLATE_KIND, target.id, { ...target, body: 'MY OWN SUMMARY PROMPT {{children}}' })
const docs = new DistillDocuments(store)
docs.ensureDefaults()
const now = store.layouts.getLatest<PromptTemplate>(TEMPLATE_KIND, target.id)!
assert.equal(now.version, 2, 'the user edit stands — no refresh put')
assert.equal(now.body.body, 'MY OWN SUMMARY PROMPT {{children}}')
})
})
12 changes: 6 additions & 6 deletions apps/engine/src/distill/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,13 @@ export class DistillDocuments {
if (!this.store.layouts.getLatest<PromptTemplate>(TEMPLATE_KIND, defaultAskTemplate.id)) {
this.store.layouts.put(TEMPLATE_KIND, defaultAskTemplate.id, defaultAskTemplate)
}
// The hierarchical-summary prompt documents (#177) — one per live-loop level. Seed-if-absent like the
// fast/judge documents; each edits over the same GET/PUT /templates routes and is read fresh per pass, so
// a user's edit to a level's cadence/bound/body takes effect with no restart and is never clobbered.
// The hierarchical-summary prompt documents (#177) — one per live-loop level. Seed-if-absent, then the
// same one-time unedited-builtin refresh the window templates use (#245): the summary bodies moved to the
// human note-taking register, so an UNEDITED summary builtin left at its #177 body auto-upgrades while a
// user edit is never clobbered. Each edits over the same GET/PUT /templates routes and is read fresh per
// pass, so a user's edit to a level's cadence/bound/body still takes effect with no restart.
for (const summaryTemplate of defaultSummaryTemplates) {
if (!this.store.layouts.getLatest<PromptTemplate>(TEMPLATE_KIND, summaryTemplate.id)) {
this.store.layouts.put(TEMPLATE_KIND, summaryTemplate.id, summaryTemplate)
}
this.seedOrRefreshBuiltin(summaryTemplate)
}
if (!this.store.layouts.getLatest<Mode>(MODE_KIND, defaultMeetingMode.id)) {
this.store.layouts.put(MODE_KIND, defaultMeetingMode.id, defaultMeetingMode)
Expand Down
2 changes: 1 addition & 1 deletion apps/engine/src/distill/guard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ test('guard OFF (guardEnabled false) skips the guard entirely — pre-#63 behavi
return Promise.resolve({ text: 'summary', endpoint: 'loc', slot: 'llm' })
}
const distiller = new Distiller({ store: deps.store, voice: deps.voice, docs: deps.docs, fabric: deps.fabric, guardDocs: deps.guardDocs, guardHolds: deps.guardHolds, guardEnabled: () => false, invoke })
const produced = await distiller.distillChunks([speech(1, 'hello')])
const produced = await distiller.distillChunks([speech(1, 'hello everyone, thanks for joining')])
assert.equal(produced.length, 1)
assert.equal(sawGuardOpt, false, 'no guard option is threaded when the flag is off')
} finally {
Expand Down
4 changes: 2 additions & 2 deletions apps/engine/src/distill/preset-injection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ test('pill P2: an UNSET preset is byte-identical to today (regression guard)', a
assert.equal(prompts.length, 1, 'one summary invoke')
// Byte-identical to the pre-P2 prompt: it starts with the shipped distill instruction and carries no
// prepended preset context.
assert.ok(prompts[0]!.startsWith('You are distilling a live meeting'), 'the base distill prompt, unmodified')
assert.ok(prompts[0]!.startsWith('Jot the notes a sharp colleague would take'), 'the base distill prompt, unmodified')
assert.ok(!prompts[0]!.startsWith('Context:'), 'no preset context prepended')
assert.equal(distillate!.provenance.presetId, undefined, 'no presetId stamped when none was active')
} finally {
Expand All @@ -84,7 +84,7 @@ test('pill P2: flipping the active preset changes the distill prompt AND stamps
// summary — proof the selection actually reaches the prompt path).
assert.notEqual(withPreset, baseline, 'the prompt changed when the preset was flipped')
assert.ok(withPreset.startsWith(`${salesBody}\n\n`), 'the active preset body is prepended')
assert.ok(withPreset.includes('You are distilling a live meeting'), 'the base distill prompt is preserved beneath it')
assert.ok(withPreset.includes('Jot the notes a sharp colleague would take'), 'the base distill prompt is preserved beneath it')
// The why-record honestly names the preset that shaped the summary.
assert.equal(distillate!.provenance.presetId, 'preset-sales', 'provenance names the active preset')

Expand Down
Loading
Loading