Repository navigation
audit: execute the field-audit list (six tracks merged, records by hand) + audit lab-log entry - #470
Merged
Merged
Conversation
…exist Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The outcome ledger already records every shown ghost and every silent opportunity. Nothing read it back to the owner, so the two surfaces that face them still counted words typed — activity, not value — and Tilde's considered silence was invisible. OutcomeLedgerSummary is a read-only, aggregate-only reader for that file. It reads the tail (capped at 1 MB, seek-to-end, whole lines only, off the main thread), decodes each line strictly as TextFreeOnlineEvent, and projects it through OutcomeLedgerFact — the complete, named set of fields this reader consumes: a date, a session hash, two counters, two booleans, one enum raw value, and one retention observation. A test proves two events differing in every other field project to the same fact. It computes keystrokes saved today and over seven days, the character-weighted share of accepted text still in place after 30 s (missing horizons excluded from numerator and denominator both, never read as zero), helpful streaks (three or more accepts inside one session digest within two minutes), and the times Tilde held back today grouped into the five reasons a person can act on: you kept typing, the conversation looked sensitive, the model had nothing, the filters were not confident, Tilde was not ready. Your Tilde now leads with keystrokes saved, kept-after-30-seconds, and streaks, with one honest line about silence, and keeps the learning progress below the divider. The menu bar's words-today line becomes keystrokes saved plus the held-back count, cached and refreshed off the main thread. Considered silence never reads as breakage: when the top reason is runtime-unavailable, or Screen Access is off, the same line says so and names the fix. Until the ledger has evidence, both surfaces fall back to the words line they showed before. A missing file, a garbage line, and a half-written final line all degrade to fewer facts rather than an error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Q12 and Q13 measured the tuned display filters on Qwen 3.5 9B only, and the tuned values are already served for the official Qwen choice while production Gemma still runs the conservative gates. Q14 registers the generator x policy 2x2 that decides whether that split should stay: conservative (DecisionPolicy.conservative) versus tuned (DecisionPolicy.tuned9B's display filters) on the same certified-v2 development exam, once per model. Pre-registered before the run: bad-when-shown as the primary metric with a 95% useful-display retention guardrail, matching Q12/Q13's accounting; the promotion rule that lets the tuned arm become the production Gemma default only on the validation partition, with the guardrail held and the review recorded supported; and the kill rules. The registration also states what the offline instrument cannot do: LabArmConfiguration has no field for tuned9B's anchored reply cue or window title in scene, so both arms hold the Lab default and Q14 measures the display-filter half of the tuned stack only. Nothing is promoted by this registration, and no production default moves. The two owner-local campaign manifests are validated and stay out of Git. No Swift, no behaviour change, so no new test; ./script/proof.sh fast is green and both manifests pass tilde-lab validate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Screen Memory master toggle. The covenant requires one and the setting existed, but nothing in the UI could set it false. Adds the toggle to Settings > Privacy & Data, bound the way suggestions and personalization are. Turning it off is a real off: the setting is written, then the app tells the capture service, which drops both snapshots, both reuse baselines, the pending capture task and the typing target (forgetCapturedScreenState). freshScene now also re-reads the toggle itself, and recognized text is dropped at the three points it would have been retained if the toggle went off during OCR — so nothing keeps serving a scene captured while it was on. The caption states the real consequence: with Screen Memory off Tilde answers every completion with silence, not merely with fewer suggestions. The menu's resume action no longer hides that flip. `.disabled` covers two different user choices, and "Resume Tilde" would silently switch a privacy control back on, so when Screen Memory is the reason the menu says "Screen Memory is Off" and the action names what it turns on. Model picker honesty. resourceGuidance now says Gemma is the measured default and Qwen needs more memory and storage and is still under study, one line each, in the setup picker, the settings picker and the README. The tilde key. Key code 50 has always accepted the whole visible ghost and was documented nowhere. One shared copy (TildeAcceptKeys) now names both keys in the setup window's ready state and in Settings > Help beside Run Setup Again, and README lists it with Tab and Esc. One-click "Ignore <App>" in the menu bar. The app tracks the last frontmost non-Tilde application — bundle identifier and display name only, never a window title — and the menu item adds it to the same excluded-apps list the Settings file panel writes, so the exclusion applies to Screen Memory capture, Personal History and the outcome ledger alike. The add is idempotent and refuses an identifier the shared contract would reject. Tests: view-model tests for the toggle (default on, explicit off persists, copy states the real consequence), capture-service tests for stop-and-clear, pure tests for the ignore item's title and list arithmetic, menu presentation for the named Screen Memory state, and model-picker copy. The freshScene fixtures that passed enabled:false while asserting a served scene were flipped to true — that combination was the hole this change closes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…epare completion context once per request Two changes in TildeCore plus their wiring in the app runtime. 1. SensitiveScenePolicy scanned only `scene.conversationTurns`, so the phrase table that keeps Tilde out of grief and crisis conversations covered `.replying` scenes and nothing else. A `.referencing` scene — composing while another visible window shares vocabulary — feeds that window's text into the prompt through RawContinuationPrompt's Reference block, so a visible crisis in a document beside the composer could still be completed over. `isSensitive` and `matchedCategory` now scan `referenceSnippets` as well, turns first, so a `.replying` scene reports exactly the category it always did and the snippet pass only ever adds a decision where there was none. The suppression already has a terminal reason in the flight recorder (`sensitive-scene`); no new reason, no change to the configuration digest. 2. A streaming request ran the cleaner on every complete-word partial and again on the final, and each call re-derived the same structures from inputs that cannot change mid-request: re-tokenizing the bounded typed context, re-normalizing every scene turn and snippet for SceneEchoPolicy, re-extracting the scene's factual anchors for FactualGroundingPolicy. PreparedCompletionContext holds all three, built once in LlamaCompletionEngine.decide and handed to every partial and to the final. `stablePartial` now takes the prepared context rather than the context string and the scene, so a partial cannot re-derive them. The per-call entry points remain and build a prepared form on the way in, so the Lab's CompletionOutputCleaner API is untouched. Visible output is unchanged, and proved so rather than asserted: Tests/TildeCoreTests golden signatures for 210 (context, raw output) pairs and 44 (scene, candidate) policy decisions were captured from the pre-refactor implementation and are checked in as literals; the streaming test in Tests/TildeAppTests pins each partial and the final for four request shapes (production, prefix trim, grounded 9B, scene echo), recorded from the pre-refactor engine. A counting test proves the context is tokenized once per request rather than once per partial, with a companion case showing the counter would have read twelve had it been per-partial. Sensitive-scene tests cover a crisis phrase in a reference snippet, a benign referencing scene, word boundaries, every category and every shipped phrase through the snippet path, and the existing conversation cases unchanged. ./script/proof.sh fast: PASS, plus the structural-change variant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Between spaces the keyboard's only predictor was an NSSpellChecker completion on a 3+ letter partial; the model was asked at word and punctuation boundaries only. `InteractionPolicy.requestsMidWordContinuation` adds a second source there — false in `.conservative`, true in `.tuned9B`, so production is unchanged and the 9B preview trials it. With it served, a 3-to-24-letter partial also issues a model request after the dictionary suffix is presented (or when the dictionary has none), throttled 120ms so a burst of typing costs one request rather than one a letter: the wait ends on the schedule revision the next keystroke already bumps, so the newest ticket supersedes the old through machinery that existed. The opportunity is opened the same way a boundary request is and is recorded with boundary `mid-word`. A model answer can never take back a visible dictionary ghost: the reducer's grow-only `.update` rule is used as-is, so an answer that does not extend what the writer is reading is dropped. The app accepts a mid-word context on the wire only when the served policy allows it (`acceptsCompletionContext`), and the prompt path finally has a shape for a partial word: the finished words go on the `Text:` line and the unfinished one seeds `Continuation:`, so the model completes the word instead of opening a new one. `normalizedContinuation` puts the seed back on the front — without doubling a model that restates it — which hands the cleaner exactly the repeated-partial shape its prefix trimming already knew. Personal serving stays word-boundary only. `TextFreeCursorBoundary` now files clause punctuation as a boundary rather than mid-word, so the preview's two interaction trials do not share one label in the flight recorder. Production, which asks at neither, records what it recorded before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…able Personal History had two defects that cost the owner something real. Streaming: `PartialResponseSink` was constructed `enabled: supportsStreamingResponses && !personalSuggestionsEnabled`, so turning on "Personal suggestions (experimental)" silently disabled the word-by-word ghost for every request. The reason was sound — a personal answer may replace the base ghost's first word, and the keyboard grows a visible ghost rather than rewriting it — but the price was the whole feature. The sink is now the gate instead: it holds the FIRST stable prefix for a bounded 60ms window while the personal lookup races the generator. A replacement that wins inside the window closes the stream and the request answers final-only with the personal text; a base or agreed answer, or an expired window, releases the prefix and streaming continues as with the toggle off; and once anything has been written the terminal line honours the base ghost, so a shown prefix is never rewritten. The decision itself is pure and lives in `TildeCore` (`PersonalSuggestionPolicy.streamDecision` / `finalSuggestion`), deciding on the first stable prefix because the arbiter compares first words only. `personal-lookup-timing` keeps its meaning — the lookup is still awaited once, with the same 250ms terminal deadline — and the hold reports itself separately as `personal-stream-hold` with a fixed outcome vocabulary (not-held / streamed / expired / final-only / unstreamed). Persistence: only the paired-evaluation aggregate counters were checkpointed. The trained context→target table was rebuilt on every launch from a bounded 4 MiB tail of raw history, so everything learned beyond that tail quietly reset. `PersonalNextWordTrainedModel` is now the durable form of the table plus the per-stream parser state (a restart mid-sentence must not reset context words, a half-typed token, or the censoring flag), versioned by schema and learning recipe so a change rebuilds instead of misreading. It is written to the same owner-only encrypted store as the history log — same Keychain key, same 0600 permissions, same delete-everything, its own record label so a history record cannot be replayed as a model — as a whole-file record replaced atomically, throttled to at most one write per 20s. Staleness is safe rather than lossy: every log record now carries a position, the saved model names the position it covers, and a restore replays exactly the records after it, so restore + catch-up is the same model a rebuild would have produced. An unreadable table falls back to the old rebuild instead of failing the replay. The storage meter counts its bytes. What is learned and how serving decides are unchanged. Tests: the streaming gate's four required cases (personal off streams; personal on with a late answer streams; personal on with a fast replacement goes final-only with the personal text; personal on with agreement streams) plus write-failure and finish behaviour, driven through an injected writer; the policy decisions in Core; restore-plus- catch-up equalling an uninterrupted shadow, including a word split across the boundary; refusal of a foreign recipe, schema, or scope; the encrypted round trip with no learned word on disk; deletion removing the table; and, at the controller, learning surviving a restart the bounded tail could not, with the no-saved-model control that still resets. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e-strategy-c88fd9
…context, streaming under personalization, persisted personal model, mid-word (preview), restraint UI, Q14, records Six reviewed track branches merged plus the records track by hand: PRIVACY.md accuracy, platform-audit Q10R corrections, TRADEMARK.md, README license section for the AGPL switch, compatibility results sheet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rg); prior releases stay MIT Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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
Executes the 2026-09-02 field audit (this PR also carries the audit's own lab-log entry, which was opened first on this branch). Seven Opus tracks in isolated worktrees, each reviewed by Fable before merge; six merged, the records/license track done by hand after a content-filter failure.
Must-be-true-first (all landed)
Magic (gated or display-only)
PreparedCompletionContext: cleaner, echo, grounding derived once per request; golden tests pin byte-identical outputInteractionPolicy.requestsMidWordContinuation— off in production, on in the 9B preview; clause punctuation no longer filed as mid-word in the ledgerOutcomeLedgerSummarybehind Your Tilde and the menu bar: keystrokes saved, kept after 30 s, streaks, held-back count by reason, honest line when Screen Access is offdocs/experiments/Q14-generator-by-policy-2x2.md); two owner-local campaigns validatedLicense
Test plan
./script/proof.sh fastgreen on the merged tree (one stub fixed post-merge)🤖 Generated with Claude Code