Skip to content

audit: execute the field-audit list (six tracks merged, records by hand) + audit lab-log entry - #470

Merged
r3dbars merged 15 commits into
mainfrom
claude/tilde-autocomplete-strategy-c88fd9
Sep 2, 2026
Merged

r3dbars merged 15 commits into
mainfrom
claude/tilde-autocomplete-strategy-c88fd9

Conversation

@r3dbars

@r3dbars r3dbars commented Sep 2, 2026 •

Copy link
Copy Markdown
Owner

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)

  • Screen Memory master toggle in Privacy & Data; off drops every held snapshot and the menu names it instead of calling it a pause
  • Sensitive-scene guard now scans reference snippets (production; adds silence only)
  • Streaming survives "Personal suggestions": 60 ms first-prefix hold instead of an unconditional off
  • PRIVACY.md states the packaged capture/redaction proof accurately
  • Qwen picker copy: higher-resource option still under study; Gemma the measured default
  • Compatibility results sheet (NOT RUN) for the first recorded runbook pass
  • Tilde key documented in setup, Help, and README
  • Platform audit: Corrections block striking the unrecorded "Q10R" figures; tally 2 of 3; ledger entry phrase corrected

Magic (gated or display-only)

  • PreparedCompletionContext: cleaner, echo, grounding derived once per request; golden tests pin byte-identical output
  • Mid-word model continuation as InteractionPolicy.requestsMidWordContinuation — off in production, on in the 9B preview; clause punctuation no longer filed as mid-word in the ledger
  • Trained personal model persisted encrypted beside the log; deleted with delete-everything
  • OutcomeLedgerSummary behind Your Tilde and the menu bar: keystrokes saved, kept after 30 s, streaks, held-back count by reason, honest line when Screen Access is off
  • One-click "Ignore " from the menu bar (bundle id and display name only)
  • Q14 registered (docs/experiments/Q14-generator-by-policy-2x2.md); two owner-local campaigns validated

License

  • README license section and TRADEMARK.md prepare the AGPL-3.0 switch; the LICENSE file lands in a follow-up with the verbatim text. Prior releases remain MIT. A lawyer should confirm.

Test plan

  • Every track's suites; ./script/proof.sh fast green on the merged tree (one stub fixed post-merge)
  • Install on the daily driver; dogfood mid-word, the toggle, Your Tilde
  • Q14 Gemma then Qwen campaigns (started serially)

🤖 Generated with Claude Code

Redbars and others added 14 commits September 2, 2026 09:13
…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>
…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>
@r3dbars r3dbars changed the title lab: field audit entry, Q10R discrepancy on the record audit: execute the field-audit list (six tracks merged, records by hand) + audit lab-log entry Sep 2, 2026
…rg); prior releases stay MIT

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@r3dbars
r3dbars merged commit d7e9f5a into main Sep 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant