|
|
@@ -79,26 +79,32 @@ settled turn (any mode), or every periodic_s while speech keeps accumulating
|
|
|
criteria
|
|
|
Escalation only drives the per-item `flagged` flag and the `⚠` marker in the panel;
|
|
|
it drives neither the gate nor the brief.
|
|
|
-5. **Brief update.** `extract_enabled` is the brief engine's master switch: with it false
|
|
|
- the panel still classifies and shows signals, but no brief is requested. When it is true
|
|
|
- and a brief function is wired, the fail-open gate decides whether the turn updates the
|
|
|
- brief. The gate reads the `substantive_update` `noul` arm of the signals: the update runs
|
|
|
- when `p >= brief_gate_threshold` (default `0.5`), and any missing or non-numeric signal
|
|
|
- passes. The service queues **one** `brief-update` job on the existing LLM lane. The job
|
|
|
- carries the speaker-labeled pending deltas (the transcripts that arrived since the last
|
|
|
- accepted update) plus the current signal words under a `Focus:` header; a Laya error
|
|
|
- still runs with the deltas only. `[llm].triage_system_prompt` drives a dedicated brief
|
|
|
- conversation that keeps its own history; the reply is a plain-text brief, not JSON
|
|
|
- (fences stripped, then truncated to `brief_max_chars`, never dropped). The accepted brief
|
|
|
- is emitted as the `content` stage, whose payload key is `brief` (the event carries the
|
|
|
- merged signals plus `brief`), and, when `[brief].write = true`, snapshotted to disk.
|
|
|
-6. **Compaction.** The brief conversation is its own `ConversationEngine`, so it compacts
|
|
|
- its history independently, using `[llm].brief_compaction_system_prompt` and the `[llm]`
|
|
|
- compaction settings. When it fires, the summary is injected as a second `system` message,
|
|
|
- which **resets that engine's prompt prefix**: the server's prefix cache is invalidated
|
|
|
- once for it, and the summarizer call can hold the shared LLM lane for up to
|
|
|
- `compaction_timeout_s`.
|
|
|
-7. **Reset.** `clear` bumps the triage generation, clears the stored signals and the ambient
|
|
|
+5. **Brief update.** The service queues **one** `brief-update` job on the existing LLM
|
|
|
+ lane. The job carries the speaker-labeled pending deltas (the transcripts that arrived
|
|
|
+ since the last accepted update) plus the current signal words under a `Focus:` header; a
|
|
|
+ Laya error still runs with the deltas only. The brief engine is **stateful only in code**:
|
|
|
+ it pins the last accepted brief and sends exactly two messages per call — a static system
|
|
|
+ prompt and ONE user turn containing `CURRENT BRIEF:` (the pinned text) and
|
|
|
+ `NEW UTTERANCES:` (the deltas). No conversation history is replayed, so the prompt stays
|
|
|
+ flat instead of growing, the model never has to disambiguate between stale copies of its
|
|
|
+ own prior briefs, and the previous brief is the single source of truth. The reply is a
|
|
|
+ plain-text brief, not JSON (fences stripped, then truncated to `brief_max_chars`, never
|
|
|
+ dropped). `extract_enabled` is the brief engine's master switch: with it false the panel
|
|
|
+ still classifies and shows signals, but no brief is requested. The accepted brief is
|
|
|
+ emitted as the `content` stage, whose payload key is `brief` (the event carries the merged
|
|
|
+ signals plus `brief`), and, when `[brief].write = true`, snapshotted to disk.
|
|
|
+6. **Retention guard.** Because the model re-emits the whole brief each pass, it can silently
|
|
|
+ drop a previously-recorded fact. After the reply is parsed, the service compares the
|
|
|
+ backend's bullet lines (any `- ` line) against the pinned prior brief by normalized key
|
|
|
+ and **re-appends any bullet that vanished**, under the brief's `Prior Details:` section
|
|
|
+ (or appending that section when absent). Matching ignores the `**New:**` marker and
|
|
|
+ whitespace, so a genuine reword/merge still matches; only a truly absent bullet is
|
|
|
+ restored. The guard is deterministic and lives entirely in code — it does not call the
|
|
|
+ model. It is the guarantee that an important detail is never lost to a bad re-emit.
|
|
|
+7. **No compaction.** The brief engine does not use `ConversationEngine`, so there is no
|
|
|
+ history to compact and `[llm].brief_compaction_system_prompt` is unused by the brief
|
|
|
+ path. `[llm]` compaction settings still govern the chat/command session.
|
|
|
+8. **Reset.** `clear` bumps the triage generation, clears the stored signals and the ambient
|
|
|
buffer, drops any pending lane request and any queued `brief-update` job, truncates the
|
|
|
optional durable brief file, and emits the `cleared` stage. An in-flight result that
|
|
|
lands after the bump is suppressed, so a stale brief writes neither the pane nor the
|
|
|
@@ -283,7 +289,7 @@ file.
|
|
|
| `escalate_threshold` | `0.6` | A `noul` answer at or above this `p` is flagged (`⚠` marker only). |
|
|
|
| `choice_min_confidence` | `0.85` | A `choice` answer below this confidence is flagged (`⚠` marker only). |
|
|
|
| `extract_enabled` | `true` | Master switch for the brief engine: with it false the panel classifies but no brief is requested. |
|
|
|
-| `extract_max_tokens` | `1536` | Token cap for the brief reply; the brief is then truncated to `brief_max_chars`. |
|
|
|
+| `extract_max_tokens` | `3072` | Token cap for the brief reply; the brief is then truncated to `brief_max_chars`. |
|
|
|
| `extract_timeout_s` | `30.0` | Timeout for the brief request. |
|
|
|
| `brief_slot_id` | unset | Optional llama.cpp `id_slot` for the brief engine; when unset the `id_slot` field is omitted (needs a server started with `-np 2`). |
|
|
|
| `brief_gate_enabled` | `true` | Fail-open gate: update the brief only when the `substantive_update` `p` is at least `brief_gate_threshold`. |
|
|
|
@@ -295,12 +301,13 @@ file.
|
|
|
| `default_checkpoint` | `"multilingual"` | Router default and the per-request `model` for every call. Multilingual is pinned. |
|
|
|
| `dump_io` | `false` | Opt-in I/O dump under `triage/YYYY-MM-DD/HHMMSS-mmm.json`; a coalesced update writes two files (see below). |
|
|
|
|
|
|
-`[llm].triage_system_prompt` drives the brief conversation: given the current brief (its
|
|
|
-previous reply, if any) and the new segment, it must return the updated brief as plain text
|
|
|
-only, with no markdown fences and no JSON. The reply is bounded by `extract_max_tokens` and
|
|
|
-`extract_timeout_s`, then truncated to `brief_max_chars`.
|
|
|
-`[llm].brief_compaction_system_prompt` is the summarizer used when the brief conversation's
|
|
|
-own history is compacted.
|
|
|
+`[llm].triage_system_prompt` drives the brief engine: it is sent as the static system
|
|
|
+message, and the user turn carries the pinned current brief plus the new segment. The model
|
|
|
+must return the updated brief as plain text only, with no markdown fences and no JSON. The
|
|
|
+reply is bounded by `extract_max_tokens` and `extract_timeout_s`, then truncated to
|
|
|
+`brief_max_chars`. Because the brief engine keeps no conversation history,
|
|
|
+`[llm].brief_compaction_system_prompt` and the `[llm]` compaction settings do not apply to
|
|
|
+it; they remain in effect for the chat/command session.
|
|
|
|
|
|
`[brief]` is a separate section that controls durability of the living brief. `write`
|
|
|
(default `false`) gates disk persistence only: with it false the brief stays in the TUI, and
|
|
|
@@ -352,7 +359,7 @@ the parsed fields in the panel. That is replaced by the plain-text brief describ
|
|
|
|
|
|
- `[triage].extract_enabled` still exists and is still the master switch, but it now gates
|
|
|
the brief update, not a JSON extraction.
|
|
|
-- `[triage].extract_max_tokens` (default raised to `1536`) is the brief reply cap, and
|
|
|
+- `[triage].extract_max_tokens` (default raised to `3072`) is the brief reply cap, and
|
|
|
`extract_timeout_s` is the brief request timeout.
|
|
|
- `[llm].triage_system_prompt` is now a plain-text brief instruction; the shipped default no
|
|
|
longer asks for JSON.
|