Commitment Flow Score Rules
Overview
The Commitment Flow Score (CFS) is the team-health composite that rolls up three axes — Flow Score (Say-Do Ratio), Ghost Rate, and Reciprocity. It is the Say-Do receipt of the team. This guide states exactly which rows count toward each axis, when they enter the calculation, and what causes them to leave.
Actors
- Lead — the CFS subject of the Garden widget. The Lead's CFS represents the team they manage.
- Knight — contributes events into the calculation but does not "have a CFS" of their own; the Knight's reliability surfaces on their Dossier as historical roll-ups.
- Squire — does not participate in scoring. Squire-extracted Shrubberies enter the calculation only after the Knight has Handshaked them.
Flow
- A Shrubbery is proposed —
DraftorPending_Handshake. Not counted yet in any axis. CFS is consent-first; nothing scores until the Knight has accepted. - Knight Handshakes —
Pending_Handshake→Active. Row enters the Flow Score denominator (it is now an "Active commitment with a deadline"). - Knight completes on or before the deadline —
Active→Completed,completed_at <= deadline. Row enters the Flow Score numerator (a delivered commitment). A commitment with no deadline counts as on time when completed. - Knight completes late —
Active→Completed,completed_at > deadline. Row stays in the denominator but not the numerator. Flow Score drops. - Knight refuses during
Pending_Handshake—Pending_Handshake→Refused. Row enters the Ghost Rate numerator. It does not enter the Flow Score denominator (the Knight never agreed to deliver). - A
Pending_Handshakeages out past the stale threshold without acceptance — counts toward Ghost Rate the same as an explicitRefused. - Lead is the Assignee on a Shrubbery (manager doing work on their team's behalf) — that row contributes to the Lead's own Reciprocity axis using the same Flow-Score arithmetic.
Edge cases
- Renegotiation — a substantive edit on an
Activerow reverts it toPending_Handshakeand emits aRenegotiatedaudit event. The row leaves the Flow Score calculation until the Knight re-Handshakes. The original deadline does not carry over implicitly; the new deadline is whatever the Lead set on the edit. - Edited but not Renegotiated — non-substantive edits emit an
Editedevent and keep the row inActive. No CFS impact. - Deleted rows — soft-deletes preserve the audit trail; CFS calculation joins on the live
shrubberiesrows, so deletions remove the row from both numerator and denominator immediately. - Externally-completed via Sync webhook —
actor_kind = api_tokenon the audit row, but the CFS arithmetic treats it identically to a user-mark-complete (the Knight is still the responsible party). The Dossier surfaces the actor distinction; the score does not. - Open
Pending_Handshakepast the stale threshold but not yet aged out — sits in neither numerator. Visible on the dashboard as a Nudge candidate.
Thresholds
The numbers below are the live constants in src/lib/metrics/cfs.ts. Each surface that shows a score reads from the same engine, so these apply everywhere.
- Rolling window — 90 days. Only events within the last 90 days count. Windowing keys on the state-change time (completion time for a delivered row, refusal/aged-out time for a Ghost), not on when the row was created — so a long-lived commitment completed recently still counts.
- Minimum denominator — 3. An axis with fewer than 3 decided commitments in the window shows a "building — N of 3" state instead of a percentage. This stops a single early row from reading as 0% or 100%.
- Stale threshold — 14 days. A
Pending_Handshakeleft unanswered for 14 days ages out and counts toward Ghost Rate exactly like an explicitRefused. Accepting or refusing before then changes the outcome; re-deriving status from the audit trail means accepting after an age-out moves the row back into the Active "yes" set (it is no longer a Ghost). - No deadline — on time. A completed commitment with a null deadline counts in the Flow Score numerator.
Sentiment
Pillar III. How the Knight felt about the commitment at the moment they Handshake it. Optional, skippable, and never weighted into the numeric Flow Score, Ghost Rate, or Reciprocity axes — Sentiment is a display-only distribution, not a fourth scored axis.
- Knight Handshakes — the accept action offers a 4-chip prompt: Confident / Comfortable / Stretched / Overwhelmed, plus a Skip. Selecting a chip or Skip both complete the Handshake immediately; Skip is zero-friction, not a second step behind the chips.
- The choice is written once, at acceptance, to
shrubberies.acceptance_sentiment(NULLon Skip). It is immutable afterward — there is no path to edit Sentiment after the fact. computeCfs()aggregates Sentiment behind anincludeSentimentflag intobreakdown.sentimentBreakdown— a{ confident, comfortable, stretched, overwhelmed, skipped }count over rows where the subject is the assignee (the same row set as Reciprocity), keyed on the row'sactivated_at(Sentiment is an acceptance-time attribute, not a status-replay one) within the same 90-day rolling window as every other axis.- Renegotiation clears
activated_atback tonullon revert toPending_Handshake— the same field the Flow Score calc drops the row on. A previously-recorded Sentiment therefore drops out of the aggregate until the row is re-Handshaked; the stored column value itself is untouched, only window membership. - Surfaces: a stacked distribution bar on
/homebeneath the Flow Score hero, and a compact proportional chip row on the Knight Dossier. Both hide entirely when the aggregate is all-zero — no empty chart.
Related concepts
- Commitment Flow Score (CFS)
- Flow Score
- Ghost Rate
- Reciprocity
- Sentiment
- Active
- Completed
- Refused
- Pending Handshake
- Ghost Commitment
Last updated: 7 July 2026