The assumed learning signal for a content system is engagement. Publish, measure, do more of what worked.

At one person's volume that loop never closes. Detecting a twenty per cent lift with any confidence needs on the order of six hundred and twenty posts, roughly three years of continuous publishing. A system claiming to learn per-user preferences from engagement inside a month is fitting noise.

There is a better signal sitting unused in the approval queue.

1. Why edits beat engagement

PROPERTYENGAGEMENTAPPROVALS AND EDITShow often it arrivesonce per published postten to fifty times thatattributionconfounded by timing, reshares, luckdirect and causalnoiseheavy tailed, variance dominatesclose to nonewhat it tells youthat something workedexactly which words were wrongtime to convergeabout 620 posts, roughly three yearsdays to weeksAn edit is a labelled example, produced by the only person qualified to label it, given away free, at the moment of most context.
Figure 1. Engagement is the signal everyone builds for and the one that cannot converge at this scale.

An edit says this exact word was wrong and here is what it should have been. There is no attribution problem, because there is only one cause.

2. The signal table

One row per real approve, edit or reject decision the user makes anywhere in the product.

user_behavior_signals
  workspaceId  sourceId  action
  intentType  category  workflowType
  contentPurpose  contentArchetype
  lengthBefore  lengthAfter
  hashtagsBefore  hashtagsAfter
  rejectionReason

Two things about this table are deliberate.

The source identifier is not a foreign key to any one entity. It is the id of whatever produced the signal. Today that is a task. A future approval loop outside the task model feeds the same table with its own recorder call, not a new table.

And writing a row costs nothing. No model call. Character counts, hashtag counts and a word diff are all arithmetic. The qualitative learning about why something changed runs separately and asynchronously into living memory. This table is the countable counterpart, and its cheapness is why it can capture every decision rather than a sample.

3. Sorting an edit

Deterministic. Paying for a model call to answer whether hashtags were removed would be waste.

the word diffadded, removed, countsthe hashtag count changed, so: structurelength moved more than fifteen per cent, so: lengthemoji added or removed, so: voicea number changed, so: factualnothing matched, so: voicethis one mattersfar more than the restA number changing is not a stylistic correction. It means the system asserted something wrong under the user's name,which is the failure the whole provenance chain exists to prevent.
Figure 2. Several categories may apply to one edit. The factual one indicates a defect rather than a preference.

4. From edits to proposals

A window of observations produces proposals. Below the repetition threshold it produces nothing.

WHAT WE SAWHOW OFTENWHAT WE PROPOSEthe same word removed, never added backthree editsban that wordemoji taken out and never put inthree editsstop using emojihashtags cut downthree editsuse the number they usually keepthe draft shortened below four fifthsthree editsuse the length they usually keepthe opening rewrittensix editsask what they would rather open withTwo guards worth naming:a word counts once per edit, so one edit deleting it five times is one observation, not fivea word the user also sometimes adds is not a word they dislike, and is excluded outright
Figure 3. Each rule is deterministic, carries its support count, and states its reason in the user's own terms.

Three repetitions is the threshold. Enough to rule out a one-off, few enough to learn inside a week or two of ordinary use.

Confidence rises with support and is capped.

below three observations   confidence is zero
otherwise                  0.5 + 0.4 x (support - 3) / 3, capped at 0.9

At three observations a proposal starts at 0.5, meaning suggestive. At six it reaches 0.9 and stops. It never reaches one, because an inference about a person is never certain.

5. A proposal is a question

Every proposal carries a sentence written to be read rather than parsed.

You've removed "leverage" from 4 drafts and never added it. Should I stop using it?

You've cut hashtags in 3 drafts, usually down to 1. Should that be the default?

You've rewritten the opening on 7 drafts. Tell me what you'd rather I open with and I'll stop guessing.

The last one does not propose a value. It admits the system keeps getting something wrong and asks for input, which is often the most useful thing a personalisation system can do.

6. The ordering

The design in one line:

an explicit preference  beats  a repeated-edit proposal  beats  measurement
measurementautomatic, on every syncan edit proposalneeds three repetitionsthe user sets it directlysets the field and locks itonly once acceptedthe locked fieldsthe one gatethe style cardthe one place preferences liveA locked field is also filtered out of future proposals. Once the user has decided, the system stops asking.Proposing a change to a field they already set is the system arguing with its own user.
Figure 4. Three writers, one gate. There is no second preference store.

The safety property that matters: one accidental edit must never become a permanent rule. A proposal needs repetition to appear, and it needs the user to accept it before anything is written. Accepting is only ever called by a user action.

One detail in accepting is worth showing, because getting it wrong is silent.

if (proposal.path === 'lexicon.banned') {
  const { card } = await this.style.getOrEmpty(workspaceId);
  const next = [...new Set([...card.lexicon.banned, String(proposal.value)])];
  await this.style.applyUserEdit(workspaceId, 'lexicon.banned', next);
  return;
}

The banned list is a list, so accepting has to append. A plain write would mean accepting a second banned word quietly dropped the first, which the user would experience as the system forgetting.

7. Rejections carry a reason

Rejection is coarser than an edit and still useful, provided the reason is structured. Six options, one of which is other.

Reason What it indicates
too generic A specificity failure, usually a missing input
wrong tone The voice model is off
factual errors A provenance failure, not a style one
wrong format The wrong shape for the platform
not what I asked for The intent was misread
other

These aggregate into the behaviour profile under the same floor as everything else. Below three signals no ranking is displayed, because a bar chart built on two data points is not a finding.

8. What we deliberately did not build

No fine tuning. Edits are strong enough to justify a training loop and there is nowhere near enough volume per person to run one. The proposals write into a specification instead.

No automatic application. Every proposal waits for consent. The cost is slower learning. The benefit is that no user is ever surprised by a preference they did not set.

No second preference store. Everything lands in the style card. A separate table of learned preferences would drift from it and become a second source of truth about the same person.

9. Where this is weak

Word diffs are lexical. Someone rewriting a sentence entirely produces a large removal set with no repeated word, and generates no proposal at all, despite having made the strongest correction available.

The observation window is not weighted by time. A preference held three months ago and since changed still counts toward support.

Proposals are per person, not per context. Someone who wants no emoji in one place and occasional emoji in another sees their edits combine into one contradictory signal.

And the conversion from characters to words divides by 5.5, which is an approximation and is language dependent.