Friction is a contract the person wrote
See the weakest authorised intervention used, an override that changes one action only, and three overrides that still cannot raise the strength.
Sometimes the best interface adds friction. The chapter’s constraint is that a goal alone authorises nothing: the intervention, its scope, its override and its expiry are part of the permission, and only the weakest authorised form is used.
Run it
git clone https://github.com/ernanhughes/project-language.git
cd project-language
git checkout 329660f679a5a87831f91fe1430c20ac9360dda8
npm ci
npm run example:personal -- ch25The example is cumulative. The review is an invented one and the model is a scripted stand-in.
Expected outcome
The contract permits one form of intervention, and that is the one used:
...
Chapter 25: friction is a contract Iris wrote. The goal is hers; the form of help is only what she authorised
action "opening another paper": reminder — "You asked for a reminder before opening another paper."
override: open it now, without further confirmationAn override changes that action and nothing else — not the goal, not the contract, not the intervention:
...
override recorded for "opening another paper" only. goal changed: false; contract revoked: false; intervention changed: false
overrides so far: 1And three overrides still do not authorise anything stronger; they only prompt a question:
...
override recorded for "opening another paper" only. goal changed: false; contract revoked: false; intervention changed: false
overrides so far: 3
Keep this reminder, change it, or remove it?The weakest authorised form wins
This is the decision, in full. A goal with no authorised intervention yields no friction at all: the default is ordinary access.
export function decideFriction(state: PersonalState, action: string, ctx: Context): FrictionDecision {
const reasons: string[] = [];
const live = [...state.entries.values()]
.filter((e): e is typeof e & { record: FrictionContract } => e.record.kind === "friction_contract" && e.record.target_action === action)
.filter(e => { const a = assess(state, e.record.id, ctx); if (!a.applicable) reasons.push(`${e.record.id}: ${a.reasons.join("; ")}`); return a.applicable; });
if (live.length === 0) return { intervention: "none", contract: null, message: null, override: null, reasons: [...reasons, "no applicable contract: ordinary access"], receipt: RECEIPT };
const authorised = live.filter(e => e.record.allowed_intervention !== null);
if (authorised.length === 0) return { intervention: "none", contract: live[0]!.record.id, message: null, override: null, reasons: [...reasons, "a goal is not an authorisation: no intervention was supplied or accepted"], receipt: RECEIPT };
const weakest = authorised.reduce((a, b) => (strength(a.record.allowed_intervention!) <= strength(b.record.allowed_intervention!) ? a : b)).record;
return {
intervention: weakest.allowed_intervention!, contract: weakest.id,
message: `You asked for ${article(weakest.allowed_intervention!)} ${weakest.allowed_intervention} before ${weakest.target_action}.`,
override: weakest.override, reasons: [...reasons, `weakest authorised intervention of ${authorised.length}`], receipt: RECEIPT,
};
}And escalation is never done on the system’s initiative:
/** Escalation is never done on the system's initiative. Asking for it returns a refusal naming what would be needed. */
export function proposeEscalation(contract: FrictionContract, to: Intervention): { escalated: false; reason: string } {
if (contract.allowed_intervention === null) return { escalated: false, reason: "a goal alone authorises no intervention; the person would have to supply one" };
return { escalated: false, reason: strength(to) > strength(contract.allowed_intervention)
? `${to} is stronger than the ${contract.allowed_intervention} the person authorised; repeated overrides do not authorise it, and only a new explicit decision about that form can`
: `${to} is not stronger than the authorised ${contract.allowed_intervention}` };
}Mechanism and limitations
Documented: the contract’s fields, the strength ordering and the rule that
an override never raises the strength are in core/personal/src/friction.ts and
core/personal/src/types.ts.
Observed: the run above. A reminder was used, not a confirmation; each override was recorded against one action and changed nothing else; and a proposed escalation to a stronger form was refused with the reason that only a new explicit decision could authorise it.
Not shown. Nothing here watches or blocks anything: the module decides what may be shown beside an action, and it says so. The receipt proves which authorised intervention the contract permits for this action now; it does not prove that the intervention helps, or that the person was not distracted. The friction ordering and the time-per-token constant are declared policy, not measured. Whether people find this mediation worth the labour is the chapter’s open question and is untested.
Understand this example
Copy this prompt into your AI tool. No code runs here.
Apply this example
Copy this prompt into your AI tool. No code runs here.