From 915d7582715de61d4b71e308ecbfd49b1dd5f788 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 22:39:58 -0700 Subject: [PATCH 1/8] docs(plans): initiative primitive design proposal (I-1) Co-Authored-By: Claude Haiku 5.5 Co-authored-by: Sorrel Agent-Model: claude/claude-haiku-5-5/default --- docs/plans/initiative-primitive.md | 182 +++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 docs/plans/initiative-primitive.md diff --git a/docs/plans/initiative-primitive.md b/docs/plans/initiative-primitive.md new file mode 100644 index 0000000000..5eab079fea --- /dev/null +++ b/docs/plans/initiative-primitive.md @@ -0,0 +1,182 @@ +# Initiative primitive: design proposal (I-1) + +Status: proposal, for CTO review. Pin: `ec85e625c` (main). No code. + +## Problem + +Sam's directions get handed to many agents and then go quiet. A lead cannot see +which direction is moving without asking each owner. The manual version we run +today (a ledger of Sam's directions grouped into initiatives, plus a daily +digest) works only because someone writes it by hand, so the status is whatever +the writer believes. The product version must produce the same digest from +records that already exist, detect stalls without anyone declaring them, and +add at most one new surface: the nudge. + +Reference spec: the daily digest the manual process produces. Its sections are +waiting-on-Sam first, status from evidence, what moved in the last 24h, and the +next step with its owner. Section 2 maps each one onto a derived field. + +## 1. Data model + +**Recommendation: a new `Initiative` collection, with typed links.** + +``` +Initiative { + _id, title, endState (one line), direction (ref to who gave it, a user id), + owner { userId | agentUserId }, // who is answerable for it + links: [{ kind: 'task' | 'pr' | 'pod' | 'decision' | 'evidence', ref, addedAt }], + createdAt, archivedAt +} +``` + +Why not fields on Task: + +- An initiative spans pods (Task.podId is single-valued, `models/Task.ts:15`) and PRs. + A field on Task can name one initiative per task and cannot express a pod or + a decision. +- Identity has to survive its tasks closing. Tasks reach `done` and stay + (Task.ts:5), so an initiative stored on them would vanish from the view the + moment its last task closes. A standalone row does not. + +Prior art, and what it reserves: + +- **ADR-028** (Draft, `docs/adr/ADR-028-work-claims-and-decision-ledger.md:3`). + D1 (:222) gives a claim a work area; D4 (:259) records disposition; D5 (:294) + types decisions with explicit supersede; D10 (:443) makes a digest a read over + the ledger. An initiative with an owner and derived status overlaps claim + custody and the decision ledger. This proposal does not add claim or + disposition fields. `links.kind = 'decision'` points at the ledger rows, and + if ADR-028 ratifies, `owner` should become a reference to its claim record. + ADR-028 is unratified, so nothing here depends on it. +- **Pod focus** (`backend/models/Pod.ts:112-115`, `podFocusService.ts:9-14`). + A pod-scoped goal with `nextTaskIds`. It is one pod only and has no derived + status. Leave it alone. + +## 2. Status derivation + +Rule for every row: status is computed from the linked records listed below. No +owner-written field can set a status. Each record is cited at the pin. + +| Status | Rule | Records and fields (file:line at `ec85e625c`) | +|---|---|---| +| **moving** | At least one linked record has a timestamp within N days (default 3) | Task `updates[].createdAt` (`models/Task.ts:7-12, :85-92`), written by `POST /:podId/:taskId/updates` (`routes/tasksApi.ts:809`), `PATCH` (`:1010`), and `POST /complete` (`:714`). Task `claimedAt` (`:28`). PR merge via webhook (`services/githubGigService.ts:80-89`). | +| **stalled N days** | No linked record is inside the window | Same records as above. A stall is the absence of a timestamp, so it cannot be self-reported. | +| **blocked on X** | A linked Task has `status='blocked'` (`Task.ts:5`), or a linked DecisionRequest is `status='pending'` (`models/DecisionRequest.ts:41, :77`). X is the task title or the decision title. | `Task.status`; `DecisionRequest.status`. | +| **waiting on you** | A linked DecisionRequest is pending and Sam is a human member of its pod. Read the open AttentionItem rows, `kind='decision'`, `recipientUserId = Sam` (`models/AttentionItem.ts:3`, written per human member by `services/attentionItemService.ts:452-457`). | `AttentionItem.recipientUserId`, `kind`, `status='open'`. | +| **done with proof** | Every linked Task is `done` AND at least one linked `pr` merged. The done state must come from the merge webhook, not from `/complete`. | `githubGigService.ts:80-89` sets `done` on a merged PR whose `prUrl` matches. `/complete` (`tasksApi.ts:729`) accepts any pod writer and any `prUrl`, so it is self-reported and cannot count as proof. | + +Findings that shape these rules: + +- **Activity cannot carry task progress.** `models/Activity.ts` has no task or + GitHub type. Its writers are chat, thread, follow, install and auto-join + paths (`activityService.ts:1354–1427` is seed data only; `postController.ts:361,527`; + `routes/agentsRuntime.ts:3897`; `routes/registry/install.ts:890`). No task write + reaches Activity. Derive from Task and PR records, not from Activity. +- **`prUrl` has no sync writer.** It is written only by `/complete` and PATCH + (`tasksApi.ts:729, :1014, :1070`), and the webhook reads it as a match key. A + PR that no task names is invisible to this model. v1 accepts that and reports + it as a count of unlinked merged PRs. +- **Ownership is `assignee` or `claimedBy`.** There is no `owner` concept in the + Task model (`Task.ts:22, :27`). +- **No pending-decision query by user exists.** Per-user pending reads go + through AttentionItem, not DecisionRequest (`attentionItemService.ts:676` + filters by `recipientUserId`, `:755-759`). + +## 3. Digest: generation and delivery + +Reuse what exists: + +- **Generation pattern.** `services/dailyDigestService.ts` builds one summary per + user from `ActivityService.getRecap` (`:171`). Its schedule is an hourly cron + (`services/schedulerService.ts:211`, `'1 * * * *'`), and it sends through + `digestEmailService.ts:142-300`. The kill switch is the `digest.sending` setting + (`digestEmailService.ts:28-45`). +- **Do not use `docs/ai-features/DAILY_DIGESTS.md` as a reference.** It describes + an older LLM-narrated newsletter at 06:00 UTC, which the code no longer does. + `docs/runbooks/daily-digest.md` is the current operational view. +- **Scope.** The existing digest is per user only (`dailyDigestService.ts:171`), + with no pod parameter. The initiative digest is a new generator that reads + `Initiative` rows and groups them by `direction`. It reuses delivery and the + kill switch. + +Delivery: + +- **Email** through `digestEmailService`, as today. +- **One quiet card** in Activity (`frontend/src/v2/components/V2ActivityPage.tsx`, + route `V2App.tsx:313-314`). The page has a needs-you section (`:1242`) and a + moved-forward section (`:1467`). `NeedsYouItem.kind` is a closed union + (`:49`), so a digest card needs a new section or kind, not a reuse. Chat's + `kind:'card'` (`frontend/src/v2/utils/threadView.ts:14-22`) is a thread item, + not a feed item. + +Mapping, Cofounder digest section to derived field: + +| Digest section | Derived field | +|---|---| +| Waiting on Sam | `waiting on you` rows | +| Status from evidence | the status column above | +| What moved in 24h | links with a timestamp in the last 24h | +| Next step, with owner | the open task with the earliest `assignee`, or the pending decision | + +## 4. Stall and nudge + +- **Detection.** An initiative is stalled when no `moving` record is inside the + window (default 3 days). The window is a setting. +- **One nudge per stall episode.** Dedupe on + `AttentionItem` unique index `{recipientUserId, source.type, source.id}` + (`models/AttentionItem.ts:74`). Set `source.id` to + `initiative::stall:`. A second stall in the same episode + then cannot insert a second row. Writers already upsert with `$setOnInsert` + (`attentionItemService.ts:78-98`), so no new dedupe code is needed. +- **Path.** The nudge is an AttentionItem row. The existing kinds are + `mention|approval|decision|handoff|blocked` (`AttentionItem.ts:3`). No `stall` + kind exists, and this proposal adds none. Use `blocked` for an owner who must + act and `handoff` for a reassignment. The nudge wakes no new runtime. +- **Wake policy.** For an agent owner, delivery goes through the existing + attention path, which ADR-018 governs (`ADR-018-agent-attention-claims.md:3`, + Accepted, with its wake-on-message rule at :399). I did not read the full + delivery code for an agent-owned AttentionItem. Keel should confirm that + delivery before this ships. +- **If nothing fits.** ADR-017 (`ADR-017-attention-routing.md:3`, Proposed) has a + blocker concept at :81–94 that overlaps this, but it is unratified and must + not be relied on. v1 uses the AttentionItem row above and records the ADR-017 + dependency as open. + +## 5. Out of v1 + +- Initiative creation UI. v1 creates rows through the API or a script. +- Cross-workspace or federated initiatives. +- Per-pod digests (needs new code, see section 3). +- Counting unlinked merged PRs as activity (needs a PR-to-task sync that does not exist). +- A status that anyone can set by hand. Manual overrides are out. +- A daily cadence for the card. Weekly is v1; daily is a setting if it is free. + +## 6. Effort for an 11-02 v1 + +Initiatives plus a weekly digest. Each item names its witness and tier. +Tiers: **haiku** for mechanical work, **opus** for kernel, record or auth, and +**review** for a non-author gate. + +| # | Item | Witness | Tier | Depends on ADR-033 | +|---|---|---|---|---| +| 1 | `Initiative` model and link-validation | Unit: rejects unknown link kinds | opus | no | +| 2 | Status derivation (pure function over the records above) | Unit table, one case per status row | opus | no | +| 3 | Stall detection and AttentionItem nudge with dedupe | Service test: two stalls in one episode produce one row | opus | no | +| 4 | Weekly digest generator and email, behind `digest.sending` | Dry run against a seeded initiative | haiku | no | +| 5 | Activity quiet card (new section) | Presence test plus browser check (v2 layout rule) | haiku | no | +| 6 | Reproduce the manual digest from a seeded fixture | Diff of section titles and ordering | review | no | + +Mark anything that depends on ADR-033 being ratified. Today none of the items +depends on it, because ADR-033 is cited only in the grant-record context. Items +3 and 5 both touch ADR-018 wake policy and need the review tier at that point. + +## Open items + +- **Reference digest not on disk.** The manual files named in the brief + (`initiatives-1025.md`, `initiatives-digest-.md`) are not in the notes + directory on this seat, so step 6 cannot be checked against them yet. The + section mapping above is taken from the brief's description, not the file. +- **ADR-033 not read.** It is absent at the pin. It merged as Accepted on main + (`97f2ac1d`, #2691) after this pin. It is not cited above and should be + checked before v1 relies on any grant or record field. +- **Status names for ux-lead** are in the next message to keel. From b051516402242db47d96404860a76fa0283d439b Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 23:03:02 -0700 Subject: [PATCH 2/8] docs(plans): I-1 revision after keel gate (24h evidence, no done, installer nudge) Co-Authored-By: Claude Haiku 5.5 Co-authored-by: Sorrel Agent-Model: claude/claude-haiku-5-5/default --- docs/plans/initiative-primitive.md | 230 +++++++++++++++++------------ 1 file changed, 134 insertions(+), 96 deletions(-) diff --git a/docs/plans/initiative-primitive.md b/docs/plans/initiative-primitive.md index 5eab079fea..756d4b99f8 100644 --- a/docs/plans/initiative-primitive.md +++ b/docs/plans/initiative-primitive.md @@ -10,11 +10,13 @@ today (a ledger of Sam's directions grouped into initiatives, plus a daily digest) works only because someone writes it by hand, so the status is whatever the writer believes. The product version must produce the same digest from records that already exist, detect stalls without anyone declaring them, and -add at most one new surface: the nudge. +add at most one new piece: the nudge. -Reference spec: the daily digest the manual process produces. Its sections are -waiting-on-Sam first, status from evidence, what moved in the last 24h, and the -next step with its owner. Section 2 maps each one onto a derived field. +Reference spec: the manual digest. Its rules are at the top of the initiatives +index (`initiatives-1025.md`, the status table). A status counts only dated +evidence: a merge, a deploy, a send, a walk or a measurement. A plan, an +assignment or a promise does not count. `done` needs the end state observed. +Section 3 maps the digest sections onto derived fields. ## 1. Data model @@ -22,9 +24,11 @@ next step with its owner. Section 2 maps each one onto a derived field. ``` Initiative { - _id, title, endState (one line), direction (ref to who gave it, a user id), - owner { userId | agentUserId }, // who is answerable for it - links: [{ kind: 'task' | 'pr' | 'pod' | 'decision' | 'evidence', ref, addedAt }], + _id, title, endState (one line), + direction (user id of whoever gave the direction), + owner { userId | agentUserId }, // stored; set by the lead, never by status + links: [{ kind: 'task' | 'pr' | 'pod' | 'decision', ref, addedAt }], + nudges: [{ episodeStart, nudgedAt }], // dedupe for agent owners (section 4) createdAt, archivedAt } ``` @@ -35,120 +39,154 @@ Why not fields on Task: A field on Task can name one initiative per task and cannot express a pod or a decision. - Identity has to survive its tasks closing. Tasks reach `done` and stay - (Task.ts:5), so an initiative stored on them would vanish from the view the - moment its last task closes. A standalone row does not. + (`models/Task.ts:5`), so an initiative stored on them would vanish from the + view the moment its last task closes. A standalone row does not. Prior art, and what it reserves: - **ADR-028** (Draft, `docs/adr/ADR-028-work-claims-and-decision-ledger.md:3`). D1 (:222) gives a claim a work area; D4 (:259) records disposition; D5 (:294) types decisions with explicit supersede; D10 (:443) makes a digest a read over - the ledger. An initiative with an owner and derived status overlaps claim - custody and the decision ledger. This proposal does not add claim or - disposition fields. `links.kind = 'decision'` points at the ledger rows, and - if ADR-028 ratifies, `owner` should become a reference to its claim record. - ADR-028 is unratified, so nothing here depends on it. + the ledger. This proposal adds no claim or disposition fields. Decision links + point at the ledger rows. If ADR-028 ratifies, `owner` should reference its + claim record. ADR-028 is unratified, so nothing here depends on it. - **Pod focus** (`backend/models/Pod.ts:112-115`, `podFocusService.ts:9-14`). - A pod-scoped goal with `nextTaskIds`. It is one pod only and has no derived - status. Leave it alone. + A pod-scoped goal with `nextTaskIds`. One pod only, no derived status. Leave it alone. ## 2. Status derivation -Rule for every row: status is computed from the linked records listed below. No -owner-written field can set a status. Each record is cited at the pin. +Rule for every row: status is computed from the evidence records below. No +owner-written field sets a status. Each record is cited at the pin. -| Status | Rule | Records and fields (file:line at `ec85e625c`) | +**Evidence records (the only inputs):** + +- **Merge.** The system update that `completeTaskForMergedPull` pushes onto a + Task when its linked PR merges: `author: 'system'`, `authorId: null` + (`services/githubGigService.ts:80-89`, update at :84). Its `createdAt` is the + evidence date. +- **Resolved decision.** A DecisionRequest with `status: 'ruled'`, dated by + `ruling.at` (`models/DecisionRequest.ts:25, :84`). + +**Not evidence, deliberately excluded:** + +- Owner-written Task `updates[]` (`models/Task.ts:7-12, :85-92`, written via + `tasksApi.ts:809`) and `claimedAt` / lease renewals (`Task.ts:28, :31`). Seats + renew leases with update notes, so counting these would make a stalled row + read as moving. +- `POST /complete` (`routes/tasksApi.ts:714, :729`) and PATCH `status` (`:1010`). + Any pod writer can set `done` there, so it is self-reported. + +**Records v1 cannot see.** A deploy, a send, a walk and a measurement have no +record at the pin. v1 counts none of them, so an initiative whose only progress +is a deploy will read as stalled. The digest must say so rather than hide it. + +| Status | Rule | Derived from | |---|---|---| -| **moving** | At least one linked record has a timestamp within N days (default 3) | Task `updates[].createdAt` (`models/Task.ts:7-12, :85-92`), written by `POST /:podId/:taskId/updates` (`routes/tasksApi.ts:809`), `PATCH` (`:1010`), and `POST /complete` (`:714`). Task `claimedAt` (`:28`). PR merge via webhook (`services/githubGigService.ts:80-89`). | -| **stalled N days** | No linked record is inside the window | Same records as above. A stall is the absence of a timestamp, so it cannot be self-reported. | -| **blocked on X** | A linked Task has `status='blocked'` (`Task.ts:5`), or a linked DecisionRequest is `status='pending'` (`models/DecisionRequest.ts:41, :77`). X is the task title or the decision title. | `Task.status`; `DecisionRequest.status`. | -| **waiting on you** | A linked DecisionRequest is pending and Sam is a human member of its pod. Read the open AttentionItem rows, `kind='decision'`, `recipientUserId = Sam` (`models/AttentionItem.ts:3`, written per human member by `services/attentionItemService.ts:452-457`). | `AttentionItem.recipientUserId`, `kind`, `status='open'`. | -| **done with proof** | Every linked Task is `done` AND at least one linked `pr` merged. The done state must come from the merge webhook, not from `/complete`. | `githubGigService.ts:80-89` sets `done` on a merged PR whose `prUrl` matches. `/complete` (`tasksApi.ts:729`) accepts any pod writer and any `prUrl`, so it is self-reported and cannot count as proof. | - -Findings that shape these rules: - -- **Activity cannot carry task progress.** `models/Activity.ts` has no task or - GitHub type. Its writers are chat, thread, follow, install and auto-join - paths (`activityService.ts:1354–1427` is seed data only; `postController.ts:361,527`; - `routes/agentsRuntime.ts:3897`; `routes/registry/install.ts:890`). No task write - reaches Activity. Derive from Task and PR records, not from Activity. +| **moving** | At least one link has evidence dated inside the window (default 24 h, a setting) | merge record, resolved decision | +| **stalled since \** | The newest evidence is older than the window. The date is that evidence's date. If an initiative has no evidence at all, the date is its `createdAt`. | same | +| **blocked on X** | A link is a pending DecisionRequest (`models/DecisionRequest.ts:41, :77`). X is its title. | `DecisionRequest.status` | +| **waiting on you** | A pending decision for which an open AttentionItem of `kind='decision'` exists with `recipientUserId = Sam` (`models/AttentionItem.ts:3`; written per human member at `services/attentionItemService.ts:452-457`) | `AttentionItem` | +| **merged, end state unverified** | Every linked Task is `done` and a linked PR has a merge record | merge record, plus Task status | + +**Not derived in v1: `done`.** The reference's `done` needs the end state +observed (live, walked, sent). No record of that exists at the pin, so v1 +cannot produce it. An initiative whose tasks all merged shows +`merged, end state unverified` until a person closes it with an evidence link, +and even that link is out of v1 (section 5). + +**Blocked on the owner's own flag is excluded.** `Task.status = 'blocked'` +(`models/Task.ts:5`) is an owner-written field. v1 shows it as a note on the +link, not as a status. This departs from the reference, where "blocked" can +name Sam's hands or an outside party; those cases come from pending decisions. + +**Other findings that shape the rules:** + +- **Activity cannot carry progress.** `models/Activity.ts` has no task or GitHub + type. Its writers are chat, thread, follow, install and auto-join paths + (`activityService.ts:1354–1427` is seed data only; `postController.ts:361,527`; + `routes/agentsRuntime.ts:3897`; `routes/registry/install.ts:890`). No task + write reaches it. - **`prUrl` has no sync writer.** It is written only by `/complete` and PATCH - (`tasksApi.ts:729, :1014, :1070`), and the webhook reads it as a match key. A - PR that no task names is invisible to this model. v1 accepts that and reports - it as a count of unlinked merged PRs. -- **Ownership is `assignee` or `claimedBy`.** There is no `owner` concept in the - Task model (`Task.ts:22, :27`). -- **No pending-decision query by user exists.** Per-user pending reads go - through AttentionItem, not DecisionRequest (`attentionItemService.ts:676` - filters by `recipientUserId`, `:755-759`). + (`tasksApi.ts:729, :1014, :1070`); the webhook reads it as a match key. A PR + that no task names is invisible to this model. v1 reports the count of unlinked + merged PRs instead. +- **Owner per link is `claimedBy || assignee`** (`models/Task.ts:22, :27`). +- **No pending-decision query by user exists.** Per-user reads go through + AttentionItem (`attentionItemService.ts:676`, filtered by `recipientUserId`, `:755-759`). ## 3. Digest: generation and delivery Reuse what exists: - **Generation pattern.** `services/dailyDigestService.ts` builds one summary per - user from `ActivityService.getRecap` (`:171`). Its schedule is an hourly cron - (`services/schedulerService.ts:211`, `'1 * * * *'`), and it sends through - `digestEmailService.ts:142-300`. The kill switch is the `digest.sending` setting + user from `ActivityService.getRecap` (`:171`). The hourly cron is + `services/schedulerService.ts:211` (`'1 * * * *'`), which sends through + `digestEmailService.ts:142-300`. Kill switch: the `digest.sending` setting (`digestEmailService.ts:28-45`). -- **Do not use `docs/ai-features/DAILY_DIGESTS.md` as a reference.** It describes - an older LLM-narrated newsletter at 06:00 UTC, which the code no longer does. +- **Do not cite `docs/ai-features/DAILY_DIGESTS.md`.** It describes an older + LLM-narrated newsletter at 06:00 UTC, which the code no longer does. `docs/runbooks/daily-digest.md` is the current operational view. -- **Scope.** The existing digest is per user only (`dailyDigestService.ts:171`), - with no pod parameter. The initiative digest is a new generator that reads - `Initiative` rows and groups them by `direction`. It reuses delivery and the - kill switch. +- **Scope.** The existing digest is per user (`dailyDigestService.ts:171`), with + no pod parameter. The initiative digest is a new generator that reads + `Initiative` rows, groups them by `direction`, and reuses delivery and the kill switch. Delivery: - **Email** through `digestEmailService`, as today. - **One quiet card** in Activity (`frontend/src/v2/components/V2ActivityPage.tsx`, - route `V2App.tsx:313-314`). The page has a needs-you section (`:1242`) and a - moved-forward section (`:1467`). `NeedsYouItem.kind` is a closed union - (`:49`), so a digest card needs a new section or kind, not a reuse. Chat's - `kind:'card'` (`frontend/src/v2/utils/threadView.ts:14-22`) is a thread item, - not a feed item. + route `V2App.tsx:313-314`). Needs-you (`:1242`) and moved-forward (`:1467`) + are the existing sections. `NeedsYouItem.kind` is a closed union (`:49`), so + the card needs a new section, not a reuse of that kind. Chat's `kind:'card'` + (`frontend/src/v2/utils/threadView.ts:14-22`) is a thread item, not a feed item. -Mapping, Cofounder digest section to derived field: +Mapping, reference digest section to derived field: | Digest section | Derived field | |---|---| | Waiting on Sam | `waiting on you` rows | -| Status from evidence | the status column above | -| What moved in 24h | links with a timestamp in the last 24h | -| Next step, with owner | the open task with the earliest `assignee`, or the pending decision | +| Status, per initiative | the status column in section 2 | +| Stalled part inside a moving initiative | per-link status, with its `stalled since` date shown beside the moving line | +| What moved in 24 h | merge and resolved-decision records dated in the last 24 h | +| Next step, with owner | the open link (Task `pending` or `claimed`) with the lowest `taskNum`, owner `claimedBy \|\| assignee`. If no open task, the pending decision. (`taskNum` at `models/Task.ts:16`.) | ## 4. Stall and nudge -- **Detection.** An initiative is stalled when no `moving` record is inside the - window (default 3 days). The window is a setting. -- **One nudge per stall episode.** Dedupe on - `AttentionItem` unique index `{recipientUserId, source.type, source.id}` - (`models/AttentionItem.ts:74`). Set `source.id` to - `initiative::stall:`. A second stall in the same episode - then cannot insert a second row. Writers already upsert with `$setOnInsert` - (`attentionItemService.ts:78-98`), so no new dedupe code is needed. -- **Path.** The nudge is an AttentionItem row. The existing kinds are - `mention|approval|decision|handoff|blocked` (`AttentionItem.ts:3`). No `stall` - kind exists, and this proposal adds none. Use `blocked` for an owner who must - act and `handoff` for a reassignment. The nudge wakes no new runtime. -- **Wake policy.** For an agent owner, delivery goes through the existing - attention path, which ADR-018 governs (`ADR-018-agent-attention-claims.md:3`, - Accepted, with its wake-on-message rule at :399). I did not read the full - delivery code for an agent-owned AttentionItem. Keel should confirm that - delivery before this ships. -- **If nothing fits.** ADR-017 (`ADR-017-attention-routing.md:3`, Proposed) has a - blocker concept at :81–94 that overlaps this, but it is unratified and must - not be relied on. v1 uses the AttentionItem row above and records the ADR-017 - dependency as open. +- **Detection.** An initiative is stalled when its status is `stalled since` + (section 2). The window is the same 24 h setting. +- **Episode.** `episodeStart` is the timestamp of the newest evidence record + when the stall began. A stall with no evidence at all uses the initiative's + `createdAt`. Using evidence time, not run time, keeps each run from minting a + new id. +- **One nudge per episode, routed by owner type:** + - **Human owner.** An AttentionItem with `kind: 'blocked'`, `source.type: + 'initiative'`, `source.id: initiative::stall:`. The + unique index `{recipientUserId, source.type, source.id}` (`models/AttentionItem.ts:74`) + dedupes it. Writers already upsert with `$setOnInsert` (`attentionItemService.ts:78-98`). + - **Agent owner.** AttentionItems go only to human pod members, filtered + `isBot: {$ne: true}` (`attentionItemService.ts:43-44`). The nudge goes to + the agent's installer, resolved by the existing `installerForAgent` + (`attentionItemService.ts:49-64`), as the same kind of AttentionItem. The + agent is not woken, so ADR-018's wake policy is untouched. If there is no + installer, the digest lists the stall with "no human to nudge" and nothing is sent. +- **The one new piece.** `AttentionSourceType` is a closed union + (`models/AttentionItem.ts:4`: `message | approval | decision_request | task`). + Adding `'initiative'` is the single schema change. The `source.type` enum in + the schema (`:55`) gets the same value. +- **Dedupe for the agent path.** The installer path also writes an AttentionItem, + so the same unique index covers it. `nudges` on the Initiative is not needed + for dedupe and is kept only as an audit trail of which episode was sent. +- **Not used.** ADR-017 (`ADR-017-attention-routing.md:3`, Proposed) has a + blocker concept at :81–94. It is unratified and is not relied on. ADR-018 + (Accepted) governs any path that wakes an agent, and v1 does not take one. ## 5. Out of v1 - Initiative creation UI. v1 creates rows through the API or a script. +- A `done` status and any evidence record for deploy, send, walk or measurement. +- Counting unlinked merged PRs as progress (needs a PR-to-task sync that does not exist). - Cross-workspace or federated initiatives. -- Per-pod digests (needs new code, see section 3). -- Counting unlinked merged PRs as activity (needs a PR-to-task sync that does not exist). -- A status that anyone can set by hand. Manual overrides are out. +- Per-pod digests (new code, see section 3). +- Manual status overrides. - A daily cadence for the card. Weekly is v1; daily is a setting if it is free. ## 6. Effort for an 11-02 v1 @@ -159,24 +197,24 @@ Tiers: **haiku** for mechanical work, **opus** for kernel, record or auth, and | # | Item | Witness | Tier | Depends on ADR-033 | |---|---|---|---|---| -| 1 | `Initiative` model and link-validation | Unit: rejects unknown link kinds | opus | no | -| 2 | Status derivation (pure function over the records above) | Unit table, one case per status row | opus | no | -| 3 | Stall detection and AttentionItem nudge with dedupe | Service test: two stalls in one episode produce one row | opus | no | +| 1 | `Initiative` model and link validation | Unit: rejects unknown link kinds | opus | no | +| 2 | Status derivation over merge and resolved-decision records only | Unit table, one case per status row, including a Task `updates[]` write that must NOT change status | opus | no | +| 3 | Stall detection; AttentionItem nudge for human owners and installer; `'initiative'` source type | Service test: two stalls in one episode produce one row; agent owner with no installer produces none | opus | no | | 4 | Weekly digest generator and email, behind `digest.sending` | Dry run against a seeded initiative | haiku | no | | 5 | Activity quiet card (new section) | Presence test plus browser check (v2 layout rule) | haiku | no | -| 6 | Reproduce the manual digest from a seeded fixture | Diff of section titles and ordering | review | no | +| 6 | Reproduce the reference digest from a seeded fixture | Diff of section titles, ordering and status lines | review | no | -Mark anything that depends on ADR-033 being ratified. Today none of the items -depends on it, because ADR-033 is cited only in the grant-record context. Items -3 and 5 both touch ADR-018 wake policy and need the review tier at that point. +None of the items depends on ADR-033 at the pin. Items 3 and 5 touch the +attention path and need the review tier. ## Open items -- **Reference digest not on disk.** The manual files named in the brief - (`initiatives-1025.md`, `initiatives-digest-.md`) are not in the notes - directory on this seat, so step 6 cannot be checked against them yet. The - section mapping above is taken from the brief's description, not the file. - **ADR-033 not read.** It is absent at the pin. It merged as Accepted on main - (`97f2ac1d`, #2691) after this pin. It is not cited above and should be - checked before v1 relies on any grant or record field. -- **Status names for ux-lead** are in the next message to keel. + (`97f2ac1d`, #2691) after this pin. It is not cited above. Check it before v1 + relies on any grant or record field. +- **Reference digest gate.** Step 6 is still open, because the generator is not + written. The rules above are taken from the reference index and the 10-25 + digest on this seat, and nothing has been diffed against a generated output yet. +- **Deploy, send and walk have no record.** This is a known gap of v1, not a fix. + The digest will show initiatives as stalled when their only progress is one of + those. From e89e5b895be0d1ee2e585ef503e6979fc0449091 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 23:11:23 -0700 Subject: [PATCH 3/8] docs(plans): I-1 revision after CTO read (close-with-evidence, ADR-033 path, public-safe wording) Co-Authored-By: Claude Haiku 5.5 Co-authored-by: Sorrel Agent-Model: claude/claude-haiku-5-5/default --- docs/plans/initiative-primitive.md | 150 +++++++++++++++++------------ 1 file changed, 87 insertions(+), 63 deletions(-) diff --git a/docs/plans/initiative-primitive.md b/docs/plans/initiative-primitive.md index 756d4b99f8..e4cdc7a17d 100644 --- a/docs/plans/initiative-primitive.md +++ b/docs/plans/initiative-primitive.md @@ -4,19 +4,19 @@ Status: proposal, for CTO review. Pin: `ec85e625c` (main). No code. ## Problem -Sam's directions get handed to many agents and then go quiet. A lead cannot see -which direction is moving without asking each owner. The manual version we run -today (a ledger of Sam's directions grouped into initiatives, plus a daily -digest) works only because someone writes it by hand, so the status is whatever -the writer believes. The product version must produce the same digest from -records that already exist, detect stalls without anyone declaring them, and -add at most one new piece: the nudge. - -Reference spec: the manual digest. Its rules are at the top of the initiatives -index (`initiatives-1025.md`, the status table). A status counts only dated -evidence: a merge, a deploy, a send, a walk or a measurement. A plan, an -assignment or a promise does not count. `done` needs the end state observed. -Section 3 maps the digest sections onto derived fields. +Directions given to agents get handed off and then go quiet. A direction-giver +cannot see which direction is moving without asking each owner. The manual +version works only because someone writes it by hand: an operator-kept ledger of +directions, grouped into initiatives, with a hand-written daily digest. The +status is whatever the writer believes. The product version must produce the +same digest from records that already exist, detect stalls without anyone +declaring them, and add at most one new piece: the nudge. + +Reference spec: the hand-written digest and its status rules. A status counts +only dated evidence: a merge, a deploy, a send, a walk or a measurement. A plan, +an assignment or a promise does not count. `done` needs the end state observed, +and a person other than the owner observes it. Section 3 maps the digest +sections onto derived fields. ## 1. Data model @@ -25,10 +25,12 @@ Section 3 maps the digest sections onto derived fields. ``` Initiative { _id, title, endState (one line), - direction (user id of whoever gave the direction), + direction (user id of the direction-giver), owner { userId | agentUserId }, // stored; set by the lead, never by status links: [{ kind: 'task' | 'pr' | 'pod' | 'decision', ref, addedAt }], - nudges: [{ episodeStart, nudgedAt }], // dedupe for agent owners (section 4) + cadence { windowHours: 24, digest: 'weekly' }, // one setting per initiative + closed { at, byUserId, evidenceUrl, note } | null, // section 2, close-with-evidence + nudges: [{ episodeStart, nudgedAt }], // audit trail of sent nudges (section 4) createdAt, archivedAt } ``` @@ -50,15 +52,18 @@ Prior art, and what it reserves: the ledger. This proposal adds no claim or disposition fields. Decision links point at the ledger rows. If ADR-028 ratifies, `owner` should reference its claim record. ADR-028 is unratified, so nothing here depends on it. +- **ADR-033** (Accepted 2026-10-25, `docs/adr/ADR-033-grant-record.md` on main + at `97f2ac1d`, after the pin). Section 5 covers it. v1 adds no record kind + and writes no `seats.record` line. - **Pod focus** (`backend/models/Pod.ts:112-115`, `podFocusService.ts:9-14`). A pod-scoped goal with `nextTaskIds`. One pod only, no derived status. Leave it alone. ## 2. Status derivation -Rule for every row: status is computed from the evidence records below. No -owner-written field sets a status. Each record is cited at the pin. +Rule for every row: status is computed from the evidence records below, or from +the close record. No owner-written field sets a status. Each record is cited at the pin. -**Evidence records (the only inputs):** +**Evidence records (the only inputs to moving and stalled):** - **Merge.** The system update that `completeTaskForMergedPull` pushes onto a Task when its linked PR merges: `author: 'system'`, `authorId: null` @@ -67,6 +72,16 @@ owner-written field sets a status. Each record is cited at the pin. - **Resolved decision.** A DecisionRequest with `status: 'ruled'`, dated by `ruling.at` (`models/DecisionRequest.ts:25, :84`). +**Close record (the reference's `done`):** + +- Only the direction-giver can close an initiative. The close writes + `closed = {at, byUserId, evidenceUrl, note}`: one evidence URL (a walk, a + deploy log, a sent message) and a one-line note. The close is a dated record, + not a status field, and it is a write that ends a record (Tier 2, section 6). +- If the owner is also the giver, the view shows **closed by owner**. That + close is not an independent observation, so the digest flags it rather than + counting it as done. + **Not evidence, deliberately excluded:** - Owner-written Task `updates[]` (`models/Task.ts:7-12, :85-92`, written via @@ -75,29 +90,26 @@ owner-written field sets a status. Each record is cited at the pin. read as moving. - `POST /complete` (`routes/tasksApi.ts:714, :729`) and PATCH `status` (`:1010`). Any pod writer can set `done` there, so it is self-reported. +- A merge alone. A merged PR is not the end state. **Records v1 cannot see.** A deploy, a send, a walk and a measurement have no -record at the pin. v1 counts none of them, so an initiative whose only progress -is a deploy will read as stalled. The digest must say so rather than hide it. +record that the derivation reads. Until a close record exists, an initiative +whose only progress is one of those reads as stalled. The digest says so +rather than hides it. | Status | Rule | Derived from | |---|---|---| -| **moving** | At least one link has evidence dated inside the window (default 24 h, a setting) | merge record, resolved decision | -| **stalled since \** | The newest evidence is older than the window. The date is that evidence's date. If an initiative has no evidence at all, the date is its `createdAt`. | same | +| **moving** | At least one link has evidence dated inside the window (default 24 h) | merge record, resolved decision | +| **stalled since \** | The newest evidence is older than the window. The date is that evidence's date. With no evidence at all, the date is `createdAt`. A stalled part is shown inside a moving initiative with its own date. | same | | **blocked on X** | A link is a pending DecisionRequest (`models/DecisionRequest.ts:41, :77`). X is its title. | `DecisionRequest.status` | -| **waiting on you** | A pending decision for which an open AttentionItem of `kind='decision'` exists with `recipientUserId = Sam` (`models/AttentionItem.ts:3`; written per human member at `services/attentionItemService.ts:452-457`) | `AttentionItem` | -| **merged, end state unverified** | Every linked Task is `done` and a linked PR has a merge record | merge record, plus Task status | - -**Not derived in v1: `done`.** The reference's `done` needs the end state -observed (live, walked, sent). No record of that exists at the pin, so v1 -cannot produce it. An initiative whose tasks all merged shows -`merged, end state unverified` until a person closes it with an evidence link, -and even that link is out of v1 (section 5). +| **waiting on you** | A pending decision linked to the initiative, with an open AttentionItem of `kind='decision'` and `recipientUserId = initiative.direction` (`models/AttentionItem.ts:3`; per-member fan-out at `services/attentionItemService.ts:452-457`) | `AttentionItem` | +| **needs your check** | Every linked Task is `done` and a linked PR has a merge record, with no close record. Shown to the giver under "Needs your check". It is never a stall and never nudges. | merge record, plus Task status | +| **closed** | A close record exists by the giver. **closed by owner** when the owner is the giver. | close record | **Blocked on the owner's own flag is excluded.** `Task.status = 'blocked'` (`models/Task.ts:5`) is an owner-written field. v1 shows it as a note on the link, not as a status. This departs from the reference, where "blocked" can -name Sam's hands or an outside party; those cases come from pending decisions. +name the giver's hands or an outside party; those cases come from pending decisions. **Other findings that shape the rules:** @@ -129,6 +141,8 @@ Reuse what exists: - **Scope.** The existing digest is per user (`dailyDigestService.ts:171`), with no pod parameter. The initiative digest is a new generator that reads `Initiative` rows, groups them by `direction`, and reuses delivery and the kill switch. +- **Cadence.** The window and digest cadence are one setting per initiative + (`cadence`, section 1), defaulting to 24 h for the window and weekly for the digest. Delivery: @@ -143,16 +157,17 @@ Mapping, reference digest section to derived field: | Digest section | Derived field | |---|---| -| Waiting on Sam | `waiting on you` rows | +| Waiting on you | `waiting on you` rows | +| Needs your check | `needs your check` rows, for the giver only | | Status, per initiative | the status column in section 2 | | Stalled part inside a moving initiative | per-link status, with its `stalled since` date shown beside the moving line | -| What moved in 24 h | merge and resolved-decision records dated in the last 24 h | +| What moved in the window | merge and resolved-decision records dated inside it | | Next step, with owner | the open link (Task `pending` or `claimed`) with the lowest `taskNum`, owner `claimedBy \|\| assignee`. If no open task, the pending decision. (`taskNum` at `models/Task.ts:16`.) | ## 4. Stall and nudge - **Detection.** An initiative is stalled when its status is `stalled since` - (section 2). The window is the same 24 h setting. + (section 2). `needs your check` is never a stall and never nudges. - **Episode.** `episodeStart` is the timestamp of the newest evidence record when the stall began. A stall with no evidence at all uses the initiative's `createdAt`. Using evidence time, not run time, keeps each run from minting a @@ -173,48 +188,57 @@ Mapping, reference digest section to derived field: Adding `'initiative'` is the single schema change. The `source.type` enum in the schema (`:55`) gets the same value. - **Dedupe for the agent path.** The installer path also writes an AttentionItem, - so the same unique index covers it. `nudges` on the Initiative is not needed - for dedupe and is kept only as an audit trail of which episode was sent. + so the same unique index covers it. `nudges` on the Initiative is an audit + trail only. - **Not used.** ADR-017 (`ADR-017-attention-routing.md:3`, Proposed) has a blocker concept at :81–94. It is unratified and is not relied on. ADR-018 (Accepted) governs any path that wakes an agent, and v1 does not take one. -## 5. Out of v1 +## 5. Out of v1, and the ADR-033 path - Initiative creation UI. v1 creates rows through the API or a script. -- A `done` status and any evidence record for deploy, send, walk or measurement. - Counting unlinked merged PRs as progress (needs a PR-to-task sync that does not exist). - Cross-workspace or federated initiatives. - Per-pod digests (new code, see section 3). -- Manual status overrides. -- A daily cadence for the card. Weekly is v1; daily is a setting if it is free. +- Manual status overrides. The close record is the only human write that changes the view. + +**v2 evidence path (ADR-033).** ADR-033 (Accepted) defines the record for agent +runs: `result.reported` with `refs: [{type, id}]`, `tool.call` with +`surface: commonly`, `task.done` (written by the board), and `decision.ruled`. +Those lines can carry evidence for work an agent performed. They have no kind +for a human deploy, a send or a walk. So v2 can count an agent's sent message +or walk through `result.reported`, and a human deploy still needs either a new +kind or the close record. The ADR's own gaps apply: `commonly_*` calls are not +recorded server-side until TASK-513 ships. + +**v1 conflicts with ADR-033: none.** v1 writes no `seats.record` line. Its close +record lives in the `Initiative` collection, not in the record format. It reads +merge and decision records and does not treat a `task.done` line as evidence. +It does not move the board (ADR-033 D4), and it adds no field to the record +format. A v2 reader of `result.reported` can be added later without changing v1. ## 6. Effort for an 11-02 v1 Initiatives plus a weekly digest. Each item names its witness and tier. -Tiers: **haiku** for mechanical work, **opus** for kernel, record or auth, and -**review** for a non-author gate. - -| # | Item | Witness | Tier | Depends on ADR-033 | -|---|---|---|---|---| -| 1 | `Initiative` model and link validation | Unit: rejects unknown link kinds | opus | no | -| 2 | Status derivation over merge and resolved-decision records only | Unit table, one case per status row, including a Task `updates[]` write that must NOT change status | opus | no | -| 3 | Stall detection; AttentionItem nudge for human owners and installer; `'initiative'` source type | Service test: two stalls in one episode produce one row; agent owner with no installer produces none | opus | no | -| 4 | Weekly digest generator and email, behind `digest.sending` | Dry run against a seeded initiative | haiku | no | -| 5 | Activity quiet card (new section) | Presence test plus browser check (v2 layout rule) | haiku | no | -| 6 | Reproduce the reference digest from a seeded fixture | Diff of section titles, ordering and status lines | review | no | - -None of the items depends on ADR-033 at the pin. Items 3 and 5 touch the -attention path and need the review tier. +Tiers: **general** for ordinary work, **opus** for kernel, record or auth, +**Tier 2** for writes that end a record or touch the attention path (two +gates, one non-Haiku), and **review** for a non-author gate. + +| # | Item | Witness | Tier | +|---|---|---|---| +| 1 | `Initiative` model and link validation | Unit: rejects unknown link kinds; `cadence` defaults to 24 h and weekly | general | +| 2 | Status derivation over merge and resolved-decision records only | Unit table, one case per status row, including a Task `updates[]` write that must NOT change status, and a merge that must read `needs your check` and not stall | opus, Tier 2 | +| 3 | Stall detection; AttentionItem nudge for human owners and the installer; `'initiative'` source type | Service test: two stalls in one episode produce one row; agent owner with no installer produces none; `needs your check` produces no nudge | opus, Tier 2 | +| 4 | Weekly digest generator and email, behind `digest.sending` | Dry run against a seeded initiative | haiku | +| 5 | Activity quiet card (new section) | Presence test plus a 390 and 1440 browser walk against ux-lead's artboard (due 10-28) | Tier 1 | +| 6 | Reproduce the reference digest from a seeded fixture | Diff of section titles, ordering and status lines | review | +| 7 | Close-with-evidence: giver-only write of the close record, and the "closed by owner" label | Unit: a non-giver close is refused; owner-as-giver shows "closed by owner" | general, Tier 2 | ## Open items -- **ADR-033 not read.** It is absent at the pin. It merged as Accepted on main - (`97f2ac1d`, #2691) after this pin. It is not cited above. Check it before v1 - relies on any grant or record field. -- **Reference digest gate.** Step 6 is still open, because the generator is not - written. The rules above are taken from the reference index and the 10-25 - digest on this seat, and nothing has been diffed against a generated output yet. -- **Deploy, send and walk have no record.** This is a known gap of v1, not a fix. - The digest will show initiatives as stalled when their only progress is one of - those. +- **ADR-033 read.** Its record kinds are in section 5. The v2 evidence path + needs a decision on human deploy, send and walk records. That is a v2 question. +- **Reference digest gate.** Step 6 is still open. The generator is not written, + so nothing has been diffed against a generated output yet. +- **Deploy, send and walk have no record in v1.** This is a known gap, not a fix. + The digest will show such initiatives as stalled until a close record exists. From b947627aee86ffbe5671ab3d5ee7cb3d6c105a7c Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 23:12:19 -0700 Subject: [PATCH 4/8] docs(plans): I-1 cite ADR-033 record lines in section 5 Co-Authored-By: Claude Haiku 5.5 Co-authored-by: Sorrel Agent-Model: claude/claude-haiku-5-5/default --- docs/plans/initiative-primitive.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/plans/initiative-primitive.md b/docs/plans/initiative-primitive.md index e4cdc7a17d..eb7bdecad5 100644 --- a/docs/plans/initiative-primitive.md +++ b/docs/plans/initiative-primitive.md @@ -203,8 +203,9 @@ Mapping, reference digest section to derived field: - Manual status overrides. The close record is the only human write that changes the view. **v2 evidence path (ADR-033).** ADR-033 (Accepted) defines the record for agent -runs: `result.reported` with `refs: [{type, id}]`, `tool.call` with -`surface: commonly`, `task.done` (written by the board), and `decision.ruled`. +runs: `result.reported` with `refs: [{type, id}]` (`docs/adr/ADR-033-grant-record.md:213`), +`tool.call` with `surface: commonly` (:210), `task.done`, written by the board (:201), +and `decision.ruled` (:206). The board owns claims (D4, :84). Those lines can carry evidence for work an agent performed. They have no kind for a human deploy, a send or a walk. So v2 can count an agent's sent message or walk through `result.reported`, and a human deploy still needs either a new From a81d5269e42271fdda5891ace8020b12e000fcff Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 23:14:37 -0700 Subject: [PATCH 5/8] docs(plans): Initiative primitive design, Accepted Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Uub7iKypiDa4iTWuc1oYZQ --- docs/plans/initiative-primitive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plans/initiative-primitive.md b/docs/plans/initiative-primitive.md index eb7bdecad5..5a10f8366c 100644 --- a/docs/plans/initiative-primitive.md +++ b/docs/plans/initiative-primitive.md @@ -1,6 +1,6 @@ # Initiative primitive: design proposal (I-1) -Status: proposal, for CTO review. Pin: `ec85e625c` (main). No code. +Status: **Accepted** (2026-10-26), ratified by the project cofounder. It was Proposed and reviewed before ratification. Pin: `ec85e625c` (main). No code in this document; the build follows the rows in section 6. ## Problem From 4bcafaa972a38752f5afb241dfecd6ae6bc73b2b Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 23:23:28 -0700 Subject: [PATCH 6/8] docs(plans): Initiative statuses take one precedence order Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Uub7iKypiDa4iTWuc1oYZQ --- docs/plans/initiative-primitive.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/plans/initiative-primitive.md b/docs/plans/initiative-primitive.md index 5a10f8366c..3d3a386a4d 100644 --- a/docs/plans/initiative-primitive.md +++ b/docs/plans/initiative-primitive.md @@ -106,6 +106,17 @@ rather than hides it. | **needs your check** | Every linked Task is `done` and a linked PR has a merge record, with no close record. Shown to the giver under "Needs your check". It is never a stall and never nudges. | merge record, plus Task status | | **closed** | A close record exists by the giver. **closed by owner** when the owner is the giver. | close record | +**Precedence.** An initiative shows exactly one status: the first rule that matches, in this order. +1. **closed**. A close is terminal and overrides everything else. +2. **waiting on you**. This is the giver-specific case of a pending decision, so it beats *blocked on X*. +3. **blocked on X**. A pending decision outranks evidence. Recent activity next to an open blocker is still blocked. +4. **needs your check**. Finished work outranks recent evidence, because the next step is the giver's. +5. **moving**. +6. **stalled since \**. + +Only the selected status drives the nudge in section 4. A stalled *part* inside a higher-ranked +status is shown as a line, never as a nudge. + **Blocked on the owner's own flag is excluded.** `Task.status = 'blocked'` (`models/Task.ts:5`) is an owner-written field. v1 shows it as a note on the link, not as a status. This departs from the reference, where "blocked" can @@ -228,7 +239,7 @@ gates, one non-Haiku), and **review** for a non-author gate. | # | Item | Witness | Tier | |---|---|---|---| | 1 | `Initiative` model and link validation | Unit: rejects unknown link kinds; `cadence` defaults to 24 h and weekly | general | -| 2 | Status derivation over merge and resolved-decision records only | Unit table, one case per status row, including a Task `updates[]` write that must NOT change status, and a merge that must read `needs your check` and not stall | opus, Tier 2 | +| 2 | Status derivation over merge and resolved-decision records only | Unit table, one case per status row, including a Task `updates[]` write that must NOT change status, a merge that must read `needs your check` and not stall, and one case per precedence overlap (pending decision + recent evidence → blocked; a close + recent evidence → closed; all merged + recent evidence → needs your check) | opus, Tier 2 | | 3 | Stall detection; AttentionItem nudge for human owners and the installer; `'initiative'` source type | Service test: two stalls in one episode produce one row; agent owner with no installer produces none; `needs your check` produces no nudge | opus, Tier 2 | | 4 | Weekly digest generator and email, behind `digest.sending` | Dry run against a seeded initiative | haiku | | 5 | Activity quiet card (new section) | Presence test plus a 390 and 1440 browser walk against ux-lead's artboard (due 10-28) | Tier 1 | From aa583570c462e13c8cbe989366ec9ec7cda2fc63 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 23:24:34 -0700 Subject: [PATCH 7/8] docs(plans): Initiative digest heading leads with motion, blocked parts as lines Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Uub7iKypiDa4iTWuc1oYZQ --- docs/plans/initiative-primitive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plans/initiative-primitive.md b/docs/plans/initiative-primitive.md index 3d3a386a4d..9e521a5097 100644 --- a/docs/plans/initiative-primitive.md +++ b/docs/plans/initiative-primitive.md @@ -170,7 +170,7 @@ Mapping, reference digest section to derived field: |---|---| | Waiting on you | `waiting on you` rows | | Needs your check | `needs your check` rows, for the giver only | -| Status, per initiative | the status column in section 2 | +| Status, per initiative | The heading leads with the initiative's **motion**: *moving* if any link has evidence in the window, otherwise *stalled since \*, or *closed* / *needs your check* when those apply. Pending decisions follow as lines, e.g. "moving, three parts blocked on you". This matches the reference digest. The single selected status from the precedence in section 2 still drives sorting, the waiting-on-you section and the nudge. | | Stalled part inside a moving initiative | per-link status, with its `stalled since` date shown beside the moving line | | What moved in the window | merge and resolved-decision records dated inside it | | Next step, with owner | the open link (Task `pending` or `claimed`) with the lowest `taskNum`, owner `claimedBy \|\| assignee`. If no open task, the pending decision. (`taskNum` at `models/Task.ts:16`.) | From 3cf57335a954d64c528dff76c0e6f45622114a8a Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 23:28:06 -0700 Subject: [PATCH 8/8] docs(plans): Initiative digest names decisions not parts; nudge pod rule Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Uub7iKypiDa4iTWuc1oYZQ --- docs/plans/initiative-primitive.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/plans/initiative-primitive.md b/docs/plans/initiative-primitive.md index 9e521a5097..197483a2a7 100644 --- a/docs/plans/initiative-primitive.md +++ b/docs/plans/initiative-primitive.md @@ -170,7 +170,7 @@ Mapping, reference digest section to derived field: |---|---| | Waiting on you | `waiting on you` rows | | Needs your check | `needs your check` rows, for the giver only | -| Status, per initiative | The heading leads with the initiative's **motion**: *moving* if any link has evidence in the window, otherwise *stalled since \*, or *closed* / *needs your check* when those apply. Pending decisions follow as lines, e.g. "moving, three parts blocked on you". This matches the reference digest. The single selected status from the precedence in section 2 still drives sorting, the waiting-on-you section and the nudge. | +| Status, per initiative | The heading leads with the initiative's **motion**: *moving* if any link has evidence in the window, otherwise *stalled since \*, or *closed* / *needs your check* when those apply. Pending decisions follow, named by their title ("moving, blocked on ") or counted ("moving, 3 decisions blocked on you"). A link has no part field, so the digest never names parts. This matches the reference digest. The single selected status from the precedence in section 2 still drives sorting, the waiting-on-you section and the nudge. | | Stalled part inside a moving initiative | per-link status, with its `stalled since` date shown beside the moving line | | What moved in the window | merge and resolved-decision records dated inside it | | Next step, with owner | the open link (Task `pending` or `claimed`) with the lowest `taskNum`, owner `claimedBy \|\| assignee`. If no open task, the pending decision. (`taskNum` at `models/Task.ts:16`.) | @@ -194,6 +194,10 @@ Mapping, reference digest section to derived field: (`attentionItemService.ts:49-64`), as the same kind of AttentionItem. The agent is not woken, so ADR-018's wake policy is untouched. If there is no installer, the digest lists the stall with "no human to nudge" and nothing is sent. +- **Nudge pod.** `AttentionItem.podId` is required, and an initiative spans pods. The pod is the + one of the owner's open linked Task with the lowest `taskNum`, the same rule as *Next step* in + section 3. Failing that, it is the first `pod` link. Failing both, nothing is sent and the digest + shows "no pod to route". The installer lookup runs in that pod. Each fallback has a test in row 3. - **The one new piece.** `AttentionSourceType` is a closed union (`models/AttentionItem.ts:4`: `message | approval | decision_request | task`). Adding `'initiative'` is the single schema change. The `source.type` enum in