---
name: preference-audit
description: "Audit an account's active preference set in one pass. Reword each to the terse form, merge same-subject duplicates, retire one-offs, and restate any preference that reimplements an installed capability so it routes to the owner. Use when asked to tidy, audit, or clean up stored preferences for an account."
---

# Preference Audit

Invoked by the admin agent directly.

The per-write authoring rules keep each new preference minimal and pointed at the capability it needs. They do not touch what is already stored. This audit is the pass over an account's active preference set: it reads that set, plans four kinds of change, shows the plan, and applies it only on confirmation.

The active set is the right target because it is exactly the set injected on every turn. Faded preferences that have decayed below the injection threshold are not injected and are not this audit's concern; they decay on their own.

## The four moves

Read the active set first with `profile-read` (`detail: true`). It returns the injected preferences ordered by confidence, up to a fixed cap. If the returned count is at that cap, the account holds more active preferences than one read returned: say so in the plan so the truncation is never silent, and audit in confidence order. Then, across the returned set:

1. **Reword.** Rewrite each preference to the terse imperative the conversational-memory skill requires: the rule itself, nothing else. Strip provenance, rationale, and restated asset contents.
2. **Merge same-subject duplicates.** When two or more preferences govern the same subject, combine them into one with `profile-update` (`mode: "merge"`, `mergeSourceIds`). One subject, one preference.
3. **Retire one-offs.** A preference that is a single-delivery command, not a standing rule, is retired with `profile-delete`. "Check the price on that one house" is a job, not a preference.
4. **Restate capability drift.** A preference whose substance is something an installed skill, tool, or plugin already owns is restated to route to that owner, per the conversational-memory authoring rule. If the right form is an actual configured job, create the job and retire the preference.

## The capability-drift signature

A stored preference is drifted when its substance maps to an installed capability:

- "remind", "chase", "follow up by a date" maps to the scheduling plugin.
- "recall" or "search past conversation" maps to `conversation-search`.
- "put X on the calendar", "book", "schedule an event" maps to the scheduling plugin.

Do not treat this as a fixed keyword list. Check each preference's substance against the capabilities this install actually has. When one maps, restate the preference to route there, or convert it to a real job and retire it.

## Invariant versus soft classification

Beyond the four moves, classify each preference as invariant or soft.

A preference is invariant-class when its value describes a document layout, a naming convention, a template, or a fixed numeric or style default (a font, a header, a signature block, a numbering scheme, a filing structure). An invariant does not belong in the injected store as prose the model must read and obey. It belongs in the skill that owns the document or process, applied by that skill's template or tool step. For each invariant, name the candidate owning skill: quotes, works-orders, valuations, invoices, filing, or email.

A preference is soft when it is a genuine standing choice with no owning skill to enforce it: a contact, a timezone, a one-invoice-per-message rule, a skip-size numeral, a no-proactive-reminders rule. Soft preferences stay in the injected store.

For every invariant, the plan names the owning skill and whether that skill already applies it deterministically or a migration is still pending. A preference whose owning skill you cannot name is reported as unassigned rather than silently left in place. The email signature is the worked first instance: `email-draft` and `email-draft-edit` apply the account's saved signature asset deterministically, so its preference is retired once the asset is in place.

## Dry run first, always

The audit never mutates before the operator sees the plan. In order:

1. Read the active set.
2. Produce the plan: for every preference, which of the four moves applies, and the exact after-state (the reworded value, the merge target, the retire, or the restated routing).
3. Present the plan and wait for confirmation.
4. On confirmation, apply it with `profile-update` and `profile-delete`.

If the operator only wants the report, stop after step 3. The plan alone is a valid deliverable.

## The record

Close every audit with one line, whether it was a dry run or applied:

`[preference-audit] account=<id> reworded=<n> merged=<n> retired=<n> restated-to-capability=<n>`

Count what the plan proposes on a dry run, and what was applied on a real run.

The classification pass emits its own line on the audit's periodic tick, so an invariant-shaped preference accumulating unmigrated surfaces on its own and not only on demand:

`[preference-audit] op=classify account=<id8> invariant=<n> soft=<m> unassigned=<k>`

`unassigned>0` names a preference the audit could not place under an owning skill.
