MalloryEstate Briefing
M·056Draft
← All briefings

Feedback & Preferences

Conversational Feedback

How "don't do that again" said out loud becomes a durable change — a spoken correction, confirmed aloud, routed either to a standing preference or to Q.

Dated 2026-08-28Programme Feedback & Preferences

Situation

When the 09:00 briefing puts the wrong thing in the morning list, there is no way for Alex to say so. Not a hard way — none. A grep over src/ for a feedback route returns nothing, and usage_events records only behaviour: push_sent, push_muted, view, acted, push_tapped. Nothing in the estate accepts an opinion. The rule for what to do about one already exists and is already right — src/persona.ts tells Mallory to "take it as a note, or hand development work to Q". The rule exists; the plumbing does not.

Every derived surface in the estate is full and every self-reported one is empty: the vault journal (zero entries), the daily note's "In your words" slot (blank in every note), current_focus (read by the briefing, written by nothing, killed 16 August), and any feedback route (never existed). One failure mode four times over — a blank page Alex has to initiate. The estate has already written the fix down, in recipes/eod-reflect.yaml: "confirmation beats composition." The nightly miss log is Mallory grading Mallory. This is its missing inbound twin.

Key decisions

Conversational, never a form

No widget, no thumbs, no > 🔧 marker — Alex declined the marker, and the frictionless text slot he already has is blank in every note. A widget is a blank page with rounded corners.

The tier line is ownership, not deploys

Tier 1 is a standing preference (selection, exclusion, ordering, tone, timing — legible alone in six months); Tier 2 is a rule change, which belongs in the recipe or the code even when a prose line could fake it. The recipes are LLM prose, so the deploy test puts almost everything in Tier 1 — and ten prose lines quietly rewriting the promotion step is a recipe rewrite by accretion.

Bias to Tier 1, and say so

Mis-routing a structural problem down costs one wasted line and a recurring miss the log already catches; mis-routing a preference up costs a PR cycle and a slice of approval attention, the scarce resource here.

A two-stage gate, biased to recall

Stage A is deterministic and LLM-free on every turn — a corrective marker and a referent to Mallory's own output; stage B is a cheap model call on stage-A hits only. It copies reminderProposal's placement and inverts its bias, because a spoken confirm absorbs precision errors a silent card cannot.

Nothing is written unconfirmed

A false positive is one spurious question, never a silently changed briefing. The budget is ~1 unconfirmed proposal per 3 days, and it measures itself: feedback_confirmed ÷ feedback_proposed is the false-positive rate.

The store cannot be a vault file

The edge-local invariant means the Worker never writes the vault, so a preference spoken in chat physically cannot land in Context.md — which delivers "Mallory can never clobber Alex's prose" by construction rather than by discipline.

Architecture

Increment 1 adds two pure modules and one call site — no migration, no table, no runtime PR. The store, the vault projection and the Q hand-off only exist if two weeks of evidence say they should.

src/lib/feedbackGate.ts
Stage A: pure, synchronous, no I/O. Corrective marker plus referent, so "I had a rough morning" and "don't let me forget the milk" fall out for free.
src/lib/feedbackProposal.ts
Stage B: classifies feedback / not, the tier, the referent, the proposed line. Mirrors reminderProposal.ts — proposes on the meta event, never writes.
src/routes/chat.ts · runTurn
The one call site, alongside buildReminderProposal on the user-message path.
Referent resolution
Three tiers in order: a closed lexicon of Mallory's own surfaces, then recency from usage_events (view, push_tapped; 30-minute window), then searchCorpus. Unresolved after one clarify becomes a note, and says so.
memories (migration 021)
Increment 1's store: source:'explicit', proposed → active, and forgotten_at as the undo — retirement is a timestamp, never a delete.
usage_events.detail
feedback_proposed / feedback_confirmed with tier and referent in JSON — the push_by_source trick: two actions, no migration. Deliberately not exposed on the client beacon, which would punch a hole in the origin gate.
preferences (migration 030) — gated
Append-only, capped at 20 active lines, per-line provenance, projected to _generated/preferences.md with a Synced: line so a stale artifact states its gap rather than vanishing.

Risks & guardrails

The fifth zero

Four attempts have returned nothing. S1: fewer than 3 confirmed items in four weeks retires the channel — and explicitly does not respond by adding a form, a widget or a button, which is what the previous four did.

The split comes back Tier-2-heavy

"Heavily Tier 1" is a guess with no corpus anywhere to test it against, so the split is the first thing measured. S2: ≥60% Tier 2 retires the preference store unbuilt and sends the effort to the Q pipeline instead.

A gate that only makes noise

S3: confirmed ÷ proposed under 0.5 with more than 5 proposals a week, two weeks running — tighten stage A or require an explicit address. The gate is wrong, not the idea.

Preferences that don't take

S4: the miss log shows the same miss recurring while a live preference covers it. Then prose in an artifact is not changing recipe behaviour and everything is really Tier 2.

A list nobody audits

The weekly cull surfaces each line with a specific claim about it — "never exercised since July; still want it?" — because a list-to-audit is the fifth blank page. Nothing expires on a timer; the cap forces the decision, since a silent disappearance is indistinguishable from a bug.

Delivery

1 — the gate and the confirm

Both stages, referent resolution, tier routing, the spoken confirm. Writes to memories only. No migration, no new table, no vault projection, no runtime PR. Already strictly better than an estate whose answer is nothing.

2 — the ledger

feedback_proposed and feedback_confirmed in UsageAction, written server-side, with tier and referent in detail — so §9's numbers are readable without a hand-written query.

3 — two-week observation

Build nothing. The gate on everything below: ≥3 confirmed items, a legible tier split, spurious rate inside budget. Any stop condition firing here ends the line.

4 — the store

Migration 030, GET /preferences, the _generated/preferences.md projection and one artifact line in briefing.yaml. A preference confirmed on Tuesday visibly changes Wednesday's briefing.

5 — Tier 2 to Q

A confirmed structural complaint becomes a q_tasks row carrying the utterance and referent. Nothing is needed on the approval surface — "Approve & run" over the socket already works from the phone.

6 — the review pass

The evidence-carrying cull in the weekly review. Depends on a vault skill this cycle must not edit, so it is handed to a later session.

Out of scope

Any form, widget, rating or "how did I do?" prompt — and any marker convention, even as a secondary pathWriting to `assistant/Context.md` or any curated vault prose — forbidden by the edge-local invariant and owned elsewhereApplying feedback without confirmation, and inferring preferences silently from behaviourAutomatic decay or expiry — the cap prompts, it never shredsA preference editor screen or a dashboard; the numbers land in the weekly reviewA new persona, addressee or route for feedback — giving it an address makes it a place Alex has to goThe physical layout of the Tier-1 store, decided elsewhere; this spec sets requirements only
spec · docs/superpowers/specs/2026-08-28-conversational-feedback-design.md
Mallory · Estate briefing