agenda-current-future-restructure.md73.5 KBView on GitHub
# Agenda restructure — Current | Future, flat tasks, consistent task row

## 1) Introduction — goal, present state, future state

We want the agenda to be two long-lived surfaces instead of a doc per calendar day: a **Current** view holding everything that is due now, and a **Future** view bucketing everything ahead into relative bands (Tomorrow, Day after, Next week, Next month, Future) — with a flat, uniform task row that reads `[ ] [Open draft] follow up with @scott` rather than a nested company header wrapping a single child. Today, every calendar day gets its own `user/agendas/YYYY-MM-DD` document with its own Y.js provider; the day matching the client's local today is a "rolling" doc that pulls the last 30 days, while every other date is a strict single-day doc, and a forward-only 7-day date strip is the only navigation. Tasks are wrapped in `conversationGroup` nodes keyed on a conversation id even when a conversation has exactly one task, the action affordance is split across two mutually exclusive render branches with inconsistent geometry, the hover scrim's `backdrop-blur` never blurs anything because it paints below the text it is meant to obscure, and deleting a task removes the node outright. After this change there are exactly two agenda documents per user, the server derives band membership from `dueDate` on every read, conversations render as inline `@` chips inside the task text so a task can reference several conversations, indentation carries parent/child task structure that is passed wholesale to the agent on invoke, and the daily agent runs once against an already-populated agenda whose only job is to re-order it and write a summary at the top.

## 2) Present state

### 2.1 Architecture diagram

```text
                    ┌──────────────────────────────────────────┐
                    │ AgendaHome                               │
                    │  useState(selectedDate = today)          │
                    │  ┌────────────────────────────────────┐  │
                    │  │ AgendaDateNav (forward-only, 7 day)│  │
                    │  └────────────────────────────────────┘  │
                    │  <AgendaDocument key=[redacted] date={date}/>│
                    └───────────────────┬──────────────────────┘
                                        │ trpc documents.getDoc
                                        │   path: user/agendas/<yyyy-MM-dd>
                                        │   agendaTodayKey=[redacted] today>
                                        ▼
   ┌────────────────────────────────────────────────────────────────────┐
   │ documents.getDoc  (trpc/routes/documents.ts:699)                   │
   │   advisory lock ─► fetchActiveDailyTasks ─► reconcile ─► persist   │
   └───────────────┬────────────────────────────────────────────────────┘
                   │ rolling = (subPath === agendaTodayKey)
                   │   rolling  → dueDate ∈ [today-30d, endOfDay(today)]
                   │   !rolling → dueDate ∈ that one UTC day
                   ▼
   ┌────────────────────────────────────────────────────────────────────┐
   │ reconcileDailyAgendaYDoc  (services/agenda/daily-agenda-reconciler)│
   │   dateHeading                                                      │
   │   taskGroupSection{id} ─► conversationGroup{convId} ─► agendaTask* │
   │   taskGroupSection{'__past__'}   (rolling only, >7d old)           │
   │   taskGroupSection{''}           (Misc, always last)               │
   └───────────────┬────────────────────────────────────────────────────┘
                   │ Y.js update over provider (keyed by documents.id)
                   ▼
   ┌────────────────────────────────────────────────────────────────────┐
   │ AgendaTaskNode NodeView                                            │
   │   branch A (slot.invoke)  → right hover overlay, icon-only buttons │
   │   branch B (!slot.invoke) → second row below text, labelled pills  │
   └───────────────┬────────────────────────────────────────────────────┘
                   │ edits flow back
                   ▼
   ┌────────────────────────────────────────────────────────────────────┐
   │ agendaTaskSyncHook (document-saving/hooks/agenda-task-sync.ts)     │
   │   agendaTask node diff ─► user_tasks INSERT / UPDATE / soft-DELETE │
   │   new-task dueDate ◄── fallbackDueDateFromSubPath(path tail)       │
   └────────────────────────────────────────────────────────────────────┘

   Separate, once-daily:  playbook cron "0 7 * * 1-5"
     ─► daily-agenda subagent ─► tasks skill ─► list-tasks (#today,#overdue)
     ─► write-document(path: user/agendas/<today>, type: agenda)
```

### 2.2 Step-by-step walkthrough

1. **Day selection** — `AgendaHome` at [AgendaHome.tsx:32-33](apps/mail/modules/agentCanvas/components/AgendaHome.tsx)
   - Holds `const [selectedDate, setSelectedDate] = useState(format(new Date(), 'yyyy-MM-dd'))`. There is no URL param for the agenda day anywhere in the app.
   - Renders `AgendaDateNav` then `<AgendaDocument key=[redacted] date={selectedDate} padded={false} />` — the `key` forces a full remount (and a fresh Y.js provider) on every day change.

2. **Date strip** — `AgendaDateNav` at [AgendaDateNav.tsx:37-104](apps/mail/modules/agentCanvas/components/AgendaDateNav.tsx)
   - `FORWARD_DAYS = 7`; renders today … today+6, never earlier. Selected day gets a `motion.div layoutId="agenda-date-active"` pill. A `ChevronRight` calls `onSelectDate(dayKey(addDays(selectedDay, 1)))`.
   - Its only consumer is `AgendaHome`; its only effect is `setSelectedDate`.

3. **Doc id resolution** — `AgendaDocument` at [AgendaDocument.tsx:222-238](apps/mail/modules/agentCanvas/components/AgendaDocument.tsx)
   - Calls `trpc.documents.getDoc({ documentType: 'agenda', path: buildDocPath.agenda(dayKey), omitContent: true, agendaTodayKey=[redacted] Date()) })`.
   - `buildDocPath.agenda` at [buildDocPath.ts:13](apps/mail/modules/files/store/buildDocPath.ts) is `` (date) => `user/agendas/${date}` ``.
   - Only `agendaDoc.id` is consumed; content arrives over Y.js.
   - Data after this step:
     ```json
     { "id": "9f1c…", "documentType": "agenda", "path": "user/agendas/2026-07-21" }
     ```

4. **Path parse** — `dailyAgendaDef.parsePath` at [doc-type-registry.ts:156-178](apps/server/src/services/documents/doc-type-registry.ts)
   - `DAILY_AGENDA_PATH = new RegExp(`^${USER_AGENDAS_PATH}/(\\d{4}-\\d{2}-\\d{2})$`)`; returns `{ scopeId: '', subPath: m[1] }`.
   - [doc-type-registry.ts:183](apps/server/src/services/documents/doc-type-registry.ts) computes `const rolling = !!agendaTodayKey && subPath === agendaTodayKey`.

5. **Task fetch** — `fetchActiveDailyTasks` at [documents.ts:81-143](apps/server/src/trpc/routes/documents.ts)
   - Branches on `rolling`:
     - rolling: `lowerBound = date - ROLLING_LOOKBACK_DAYS (30)`, upper `endOfDay(today)`; then drops `status='done'` rows older than the recent window ([documents.ts:125-130](apps/server/src/trpc/routes/documents.ts)).
     - non-rolling: `dueDate ∈ ['{date}T00:00:00.000Z', '{date}T23:59:59.999Z']`.
   - Always excludes `deleted`, `agent_deleted`, `recommended`. Note `done` is retained on this surface.
   - Data after this step:
     ```json
     [{ "id": "t1", "description": "follow up with Scott", "dueDate": "2026-07-21",
        "status": "todo", "conversationId": "c-scott", "taskGroupId": "g-deals" }]
     ```

6. **Group fetch** — `fetchActiveDailyGroups` at [documents.ts:146-165](apps/server/src/trpc/routes/documents.ts)
   - Orders by `position, createdAt`, then appends the virtual Misc lane with `position: Number.MAX_SAFE_INTEGER`. Misc is `task_group_id IS NULL`; no row exists for it.

7. **Reconcile** — `reconcileDailyAgendaYDoc` at [daily-agenda-reconciler.ts:697-806](apps/server/src/services/agenda/daily-agenda-reconciler.ts)
   - Passes, each in its own `ydoc.transact(…, 'system')`:
     - `ensureLeadingDateHeading` ([:388](apps/server/src/services/agenda/daily-agenda-reconciler.ts)) — required because the client's `dateHeadingProtect` plugin rejects any transaction whose first child isn't a `dateHeading`.
     - `ensureSections` ([:413](apps/server/src/services/agenda/daily-agenda-reconciler.ts)) — creates a section for **every** group in `position` order even at zero tasks; never deletes one.
     - `migrateLegacyTopLevelIntoSections` ([:487](apps/server/src/services/agenda/daily-agenda-reconciler.ts)).
     - `removeInactiveAgendaTasks` ([:594](apps/server/src/services/agenda/daily-agenda-reconciler.ts)) — drops any `agendaTask` whose `taskId` left the active set, prunes emptied `conversationGroup`s, preserves nodes with unset `taskId` (locally-typed).
     - `insertTasksIntoSection` ([:662](apps/server/src/services/agenda/daily-agenda-reconciler.ts)) — appends new tasks; conversation-linked tasks **fold into an existing `conversationGroup` or create one**; personal tasks append flat.
   - Deliberate non-behavior ([:35-39](apps/server/src/services/agenda/daily-agenda-reconciler.ts)): never reflows `checked` / `conversationId` / `taskGroupId` on a task already in the doc. Structural insert/remove only.
   - `isPastForDay` ([:92](apps/server/src/services/agenda/daily-agenda-reconciler.ts)) is `taskDueKey < dayKey - 6`; past tasks go to `PAST_SECTION_KEY=[redacted]`, rendered only when non-empty.
   - Doc after this step:
     ```json
     { "type": "doc", "content": [
       { "type": "dateHeading", "attrs": { "date": "2026-07-21" } },
       { "type": "taskGroupSection", "attrs": { "taskGroupId": "g-deals" }, "content": [
         { "type": "taskGroupHeader", "attrs": { "taskGroupId": "g-deals" } },
         { "type": "conversationGroup", "attrs": { "conversationId": "c-scott" }, "content": [
           { "type": "conversationGroupHeader", "attrs": { "conversationId": "c-scott" } },
           { "type": "agendaTask", "attrs": { "taskId": "t1", "conversationId": "c-scott",
             "dueDate": "2026-07-21", "checked": false, "indentLevel": 0, "deleted": false } } ] } ] } ] }
     ```

8. **Row render, action slot** — `deriveAgendaRightSlot` at [agenda-right-slot-state.ts:38-65](apps/mail/modules/agentCanvas/utils/agenda-right-slot-state.ts)
   - Returns `{ artifact, bot, invoke }` where `invoke: !artifact && !bot`. Email only yields `'open-draft'` when `draftId` is present.
   - Data after this step:
     ```json
     { "artifact": "open-draft", "bot": "finished", "invoke": false }
     ```

9. **Row render, two branches** — `AgendaTaskNodeView` at [AgendaTaskNode.tsx:536-866](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx)
   - Branch B, `!slot.invoke` ([:633-765](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx)): renders a **second row** under the text (`ml-6 mt-1`) holding the bot icon, the labelled artifact pill (`bg-action`, `px-2 py-1`, auto width), then a hover-faded cluster of Snooze / Re-do / Delete.
   - Branch A, `slot.invoke` ([:773-866](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx)): renders an **absolutely positioned right overlay** with icon-only Delete / Snooze / Invoke, plus the scrim at [:783-786](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx):
     ```tsx
     <div aria-hidden className="bg-surface/70 pointer-events-none absolute inset-0 -z-10
          backdrop-blur-md [mask-image:linear-gradient(to_left,black_55%,transparent)]" />
     ```
     `-z-10` places the scrim **below** the task text in paint order. `backdrop-filter` only blurs what is painted beneath the element, so it samples the page background and never the text — the blur is a no-op. Visibility is toggled on the overlay via `invisible`/`visible`, which additionally suppresses `backdrop-filter` in Chromium while hidden.
   - Conversation badge ([:595-626](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx)) is suppressed entirely when the task sits inside a `conversationGroup` (`shouldHideConversationBadge`), because the group header already shows the company.

10. **Delete** — `handleDeleteTask` at [AgendaDocument.tsx:1117-1141](apps/mail/modules/agentCanvas/components/AgendaDocument.tsx)
    - Computes `removeFrom`/`removeTo`; if the task is the lone `agendaTask` child of a `conversationGroup`, widens the range to swallow the whole group. Dispatches `tr.delete(...)`, then calls `deleteTask(taskId)`. The node is gone from the doc immediately.
    - The `deleted` attr already exists on the node ([AgendaTaskNode.tsx:914-954](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx)) and renders a red ✕ tombstone at [:568-571](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx), but nothing on the delete path ever sets it.

11. **Write-back** — `agendaTaskSyncHook` at [agenda-task-sync.ts:27-222](apps/server/src/services/document-saving/hooks/agenda-task-sync.ts)
    - Reads `changeSet.byType.get('agendaTask')` and emits `user_tasks` INSERT / UPDATE / soft-DELETE.
    - create ([:97-130](apps/server/src/services/document-saving/hooks/agenda-task-sync.ts)): **skipped entirely when there is no `conversationId`**, because `user_tasks.conversation_id` is NOT NULL. Such tasks stay doc-only.
    - `fallbackDueDateFromSubPath` ([:254-260](apps/server/src/services/document-saving/hooks/agenda-task-sync.ts)) turns the `yyyy-MM-dd` **path tail** into `{date}T12:00:00.000Z`. This is the only reason a task typed into a day's agenda lands on that day.
    - delete ([:211-222](apps/server/src/services/document-saving/hooks/agenda-task-sync.ts)): node removal → `status: 'deleted'`.

12. **Markdown boundary** — `parseAgendaMarkdown` / `serializeAgendaToMarkdown` at [agenda-markdown.ts:44-364](apps/server/src/services/agenda/agenda-markdown.ts)
    - Grammar: `{taskGroupSection,taskGroupId:"…"}`, `{conversationGroup,conversationId:"…"} Header`, `- [ ] {taskId:"…",conversationId:"…",localId:"…"} Text`, `# YYYY-MM-DD`, inline `@[conversationId]`.
    - `dueDate` is **never parsed from markdown** ([:130](apps/server/src/services/agenda/agenda-markdown.ts)) — an agent round-tripping the doc through markdown silently loses every task's due date.

13. **Agent run** — `daily-agenda` subagent, seeded at [seed-playbook.ts:161](apps/server/src/services/playbook/seed-playbook.ts) with `<trigger type="cron" schedule="0 7 * * 1-5">`
    - Fires via `processAopAutomations` at [cron-task-registry.ts:178](apps/server/src/cron/cron-task-registry.ts) → `runPlaybookSectionExecution` at [playbook-execution-triggers.ts:71](apps/server/src/services/playbook/playbook-execution-triggers.ts).
    - **Nothing is pre-injected.** The agent assembles the agenda itself via `listTasksTool` at [listTasksTool.ts:241-250](apps/server/src/mastra/tools/tasks/listTasksTool.ts): `#today` = last 7 days → end of today, `#overdue` = strictly before today with no lower bound. It then calls `write-document({ path: 'user/agendas/<today>', type: 'agenda' })`.
    - Skill body at [SKILL.md:342-504](apps/server/.claude/skills/tasks/SKILL.md); agent-facing grammar at [agenda-format.md](apps/server/.claude/skills/document-management/agenda-format.md), which predates `taskGroupSection` and does not document it.

14. **Invoke** — `buildInvocationPrompt` at [build-invocation-prompt.ts:33](apps/server/src/services/tasks/build-invocation-prompt.ts)
    - Carries exactly one task: description, conversation id/name, `dueDate`, notes. No sibling, parent, or child task context.

## 3) Designed state

### 3.1 Architecture diagram

```text
                    ┌──────────────────────────────────────────┐
                    │ AgendaHome                               │
                    │  useState(view: 'current' | 'future')    │
                    │  ┌────────────────────────────────────┐  │
                    │  │ Today's agenda   [ Current |Future]│  │
                    │  └────────────────────────────────────┘  │
                    │  <AgendaDocument key=[redacted] view={view}/>│
                    └───────────────────┬──────────────────────┘
                                        │ trpc documents.getDoc
                                        │   path: user/agendas/{current|future}
                                        │   agendaTodayKey=[redacted] today>
                                        ▼
   ┌────────────────────────────────────────────────────────────────────┐
   │ documents.getDoc                                                   │
   │   subPath 'current' → dueDate <= endOfDay(today)      (no floor)   │
   │   subPath 'future'  → dueDate >  endOfDay(today)                   │
   └──────────┬──────────────────────────────┬──────────────────────────┘
              ▼                              ▼
  ┌───────────────────────────┐  ┌────────────────────────────────────┐
  │ daily-agenda-reconciler   │  │ future-agenda-reconciler  (NEW)    │
  │  dateHeading{view:current}│  │  dateHeading{view:future}          │
  │  taskGroupSection{id}     │  │  agendaBandSection{'tomorrow'}     │
  │    agendaTask*  ◄─ FLAT   │  │    taskGroupSection{id}            │
  │  taskGroupSection'__past__│  │      agendaTask*                   │
  │  taskGroupSection{''}     │  │  agendaBandSection{'day-after'}    │
  │                           │  │  …{'next-week'} …{'next-month'}    │
  │                           │  │  …{'future'}                       │
  └──────────────┬────────────┘  └───────────────┬────────────────────┘
                 │        band = bandForDueDate(dueDate, todayKey)
                 │        reflowBands() moves tasks as today advances
                 ▼                              ▼
   ┌────────────────────────────────────────────────────────────────────┐
   │ AgendaTaskNode NodeView — ONE branch                               │
   │  [drag] [✓] [ Open draft ]  follow up with (@scott)   [⋯ overlay]  │
   │              └ w-32, rounded-md, bg-action | bg-sunken             │
   │  conversation = inline conversationNode chip, N per task           │
   │  scrim: z-0 above text, buttons z-10, mounted on hover (no z-−10)  │
   └───────────────┬────────────────────────────────────────────────────┘
                   │
                   ▼
   ┌────────────────────────────────────────────────────────────────────┐
   │ agendaTaskSyncHook                                                 │
   │   deleted:false→true  ─────► status:'deleted'   (tombstone stays)  │
   │   owning conversationId ◄── attr, else first inline chip           │
   │   new-task dueDate ◄── 'current' → now | 'future' → tomorrow 12:00 │
   └────────────────────────────────────────────────────────────────────┘

   Once daily:  cron ─► daily-agenda subagent
     ─► read-document(user/agendas/current)   ◄── ALREADY FULLY POPULATED
     ─► re-order sections/tasks + prepend summary
     ─► write-document(user/agendas/current)
```

### 3.2 Step-by-step walkthrough

1. **View selection** — `AgendaHome` at [AgendaHome.tsx](apps/mail/modules/agentCanvas/components/AgendaHome.tsx)
   - `useState<'current' | 'future'>('current')` replaces `selectedDate`. Renders the title row `Today's agenda` / `Upcoming tasks` on the left and the new `AgendaViewToggle` on the right, then `<AgendaDocument key=[redacted] view={view} />`.

2. **Toggle** — `AgendaViewToggle` at [AgendaViewToggle.tsx](apps/mail/modules/agentCanvas/components/AgendaViewToggle.tsx) (new, replaces `AgendaDateNav`)
   - Two-segment control. Keeps the `motion.div layoutId` sliding pill from `AgendaDateNav` so the active-segment animation carries over.
   - Props: `{ view: AgendaView; onSelectView: (v: AgendaView) => void }`.

3. **Doc id resolution** — `AgendaDocument` at [AgendaDocument.tsx:222-238](apps/mail/modules/agentCanvas/components/AgendaDocument.tsx)
   - Prop `date: string` becomes `view: AgendaView`. Path comes from `buildDocPath.agenda(view)` at [buildDocPath.ts:13](apps/mail/modules/files/store/buildDocPath.ts), now `` (view) => `user/agendas/${view}` ``.
   - `agendaTodayKey` is still sent and becomes **load-bearing**: it is the client's local today, and every band boundary is computed from it.
   - Data after this step:
     ```json
     { "id": "3ab2…", "documentType": "agenda", "path": "user/agendas/future" }
     ```

4. **Path parse** — `dailyAgendaDef.parsePath` at [doc-type-registry.ts:156-178](apps/server/src/services/documents/doc-type-registry.ts)
   - `DAILY_AGENDA_PATH` becomes `/^user\/agendas\/(current|future)$/`; `subPath` is now the view key, not a date. The `rolling` flag is deleted — `current` is always rolling by definition.
   - Registry dispatches on `subPath`: `'current'` → `reconcileDailyAgendaYDoc`, `'future'` → `reconcileFutureAgendaYDoc`.

5. **Task fetch** — `fetchActiveDailyTasks` at [documents.ts:81-143](apps/server/src/trpc/routes/documents.ts)
   - Signature takes `view` instead of `{ date, rolling }`.
     - `current`: `dueDate <= endOfDay(agendaTodayKey)`, **no lower bound** — the 30-day `ROLLING_LOOKBACK_DAYS` floor is removed so Current holds everything currently due.
     - `future`: `dueDate > endOfDay(agendaTodayKey)`, no upper bound.
   - Status filter is unchanged except that `status = 'deleted'` rows are now returned when the matching node carries `deleted: true`, so tombstones survive a reconcile (see step 9).
   - Data after this step:
     ```json
     [{ "id": "t7", "description": "prep renewal deck", "dueDate": "2026-07-24",
        "status": "todo", "conversationId": "c-pirros", "taskGroupId": "g-deals" }]
     ```

6. **Band assignment** — `bandForDueDate` at [future-agenda-reconciler.ts](apps/server/src/services/agenda/future-agenda-reconciler.ts) (new)
   - Pure, UTC day-key arithmetic against `todayKey`, mirroring `addDaysKey` at [daily-agenda-reconciler.ts:85](apps/server/src/services/agenda/daily-agenda-reconciler.ts):

     | band | range (days after today) |
     |---|---|
     | `tomorrow` | +1 |
     | `day-after` | +2 |
     | `next-week` | +3 … +7 |
     | `next-month` | +8 … +31 |
     | `future` | +32 and beyond |

   - Data after this step:
     ```json
     { "t7": "next-week" }
     ```

7. **Future reconcile** — `reconcileFutureAgendaYDoc` at [future-agenda-reconciler.ts](apps/server/src/services/agenda/future-agenda-reconciler.ts) (new)
   - Mirrors the daily reconciler's pass structure, with one addition and one divergence:
     - `ensureLeadingDateHeading` — emits `dateHeading{ view: 'future' }`, satisfying `dateHeadingProtect`.
     - `ensureBandSections` — all five `agendaBandSection`s always exist, in fixed order. This is the analogue of the daily doc's always-show-all-groups invariant.
     - **Divergence:** inside a band, a `taskGroupSection` is emitted **only for groups that have at least one task in that band**. Current renders every group unconditionally; Future would otherwise show five bands × N groups of empty headers.
     - `reflowBands` (**new pass, no analogue in the daily reconciler**) — for every task already in the doc, recompute its band; if it differs from the section it sits in, move the node. This is required because band membership is *derived* from `dueDate` relative to today and drifts every midnight, whereas the daily reconciler's explicit policy at [daily-agenda-reconciler.ts:35-39](apps/server/src/services/agenda/daily-agenda-reconciler.ts) is never to reflow. Order **within** a band is preserved, so the agent's manual arrangement survives; only cross-band moves are forced.
     - `removeInactiveAgendaTasks` — reused, with the tombstone exemption from step 9.
   - Doc after this step:
     ```json
     { "type": "doc", "content": [
       { "type": "dateHeading", "attrs": { "date": null, "view": "future" } },
       { "type": "agendaBandSection", "attrs": { "band": "tomorrow" }, "content": [
         { "type": "agendaBandHeader", "attrs": { "band": "tomorrow" } } ] },
       { "type": "agendaBandSection", "attrs": { "band": "next-week" }, "content": [
         { "type": "agendaBandHeader", "attrs": { "band": "next-week" } },
         { "type": "taskGroupSection", "attrs": { "taskGroupId": "g-deals" }, "content": [
           { "type": "taskGroupHeader", "attrs": { "taskGroupId": "g-deals" } },
           { "type": "agendaTask", "attrs": { "taskId": "t7", "conversationId": "c-pirros",
             "dueDate": "2026-07-24", "indentLevel": 0 },
             "content": [ { "type": "text", "text": "prep renewal deck " },
                          { "type": "conversationNode", "attrs": { "conversationId": "c-pirros" } } ] } ] } ] } ] }
     ```

8. **Flat task build** — `buildSectionBodyJson` at [daily-agenda-reconciler.ts:197-234](apps/server/src/services/agenda/daily-agenda-reconciler.ts)
   - `conversationGroup` / `conversationGroupHeader` construction is deleted. Tasks append flat into their `taskGroupSection`, sorted by conversation priority (`CONVERSATION_PRIORITY_ORDER` at [:123](apps/server/src/services/agenda/daily-agenda-reconciler.ts)) then description.
   - Each task's inline content gains a trailing `conversationNode` chip for its owning `conversationId`. The `conversationId` **attr is retained** as the authoritative DB link — the chip is the rendering of it, not a replacement.
   - `insertTasksIntoSection` at [:662-695](apps/server/src/services/agenda/daily-agenda-reconciler.ts) loses its group-folding branch and always appends flat.

9. **Tombstones** — `removeInactiveAgendaTasks` at [daily-agenda-reconciler.ts:594-635](apps/server/src/services/agenda/daily-agenda-reconciler.ts)
   - Gains an exemption: a node whose `deleted` attr is `true` is **kept** even though its task is inactive. Without this the reconciler would delete the tombstone on the very next read and the X would flash out of existence.
   - Sweep: tombstones are dropped when the daily agent rebuilds the doc (step 13), so an X'd task stays visible and undoable for the rest of the day.

10. **Row render, one branch** — `AgendaTaskNodeView` at [AgendaTaskNode.tsx:536-866](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx)
    - The two mutually exclusive branches collapse into one layout:
      ```
      [drag] [✓] [ Open draft ] follow up with (@scott)          [bot ⏰ ↺ ✕]
                  └─ leading action button          └─ inline chip  └─ hover overlay
      ```
    - Leading action button — fixed geometry so the column aligns down the whole agenda: `w-32 h-6 rounded-md text-xs font-medium`, `shrink-0`. `w-32` (128px) is sized for the longest label, `Open message`.
      - `slot.artifact` set → `bg-action hover:bg-action-hover text-action-foreground`, label `Open draft` / `Open message` / `Open invite`, handler `handleOpenArtifact`.
      - otherwise → `bg-sunken hover:bg-muted text-muted-foreground`, label `Execute`, handler `handleInvoke`.
    - `deriveAgendaRightSlot` at [agenda-right-slot-state.ts:38-65](apps/mail/modules/agentCanvas/utils/agenda-right-slot-state.ts) keeps returning `{ artifact, bot, invoke }`; only the consumer changes. `invoke` now means "the leading button says Execute" rather than "render the overlay branch".
    - The right overlay holds only the transient actions — bot icon, Snooze, Re-do, tombstone ✕ — for every task, in every state.

11. **Scrim fix** — [AgendaTaskNode.tsx:783-786](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx)
    - `-z-10` is removed. The scrim becomes `absolute inset-0 z-0` and the button cluster becomes `relative z-10`, so the scrim paints **above** the task text and `backdrop-filter` finally has something beneath it to blur.
    - The `invisible`/`visible` toggle is replaced by conditional mounting on the existing `isRowHovered` React state ([AgendaTaskNode.tsx:545-546](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx)) — this sidesteps both the Chromium `visibility:hidden` suppression of `backdrop-filter` and the opacity-animation flicker the current comment warns about, since the element is never animated, just mounted.

12. **Inline conversation chips** — `ConversationNode` at [ConversationNode.tsx:54-97](apps/mail/modules/agentCanvas/extensions/ConversationNode.tsx)
    - Already an inline atom with a `conversationId` attr, a `@[id]` markdown round-trip, and live name/avatar hydration via `useConversationNodeData`. It needs no schema change — it simply becomes the standard way a task shows its conversation.
    - `shouldHideConversationBadge` and the trailing badge block at [AgendaTaskNode.tsx:591-626](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx) are deleted; the `AtSign` "link a conversation" affordance moves into the slash/`@` mention menu that already inserts `conversationNode`.
    - Multi-conversation tasks fall out for free: N chips inline, no join table. The `conversationId` attr names the **owning** conversation (the FK); extra chips are references.

13. **Ownership adoption** — `agendaTaskSyncHook` at [agenda-task-sync.ts:97-130](apps/server/src/services/document-saving/hooks/agenda-task-sync.ts)
    - create: when the node has no `conversationId` attr but its inline content contains at least one `conversationNode`, adopt the **first** chip's id as the owning `conversationId`. This closes the long-standing hole where a task typed without a conversation could never be persisted (`user_tasks.conversation_id` is NOT NULL).
    - `fallbackDueDateFromSubPath` at [:254-260](apps/server/src/services/document-saving/hooks/agenda-task-sync.ts) is replaced by `fallbackDueDateForView(view)`: `'current'` → `now`, `'future'` → tomorrow at `12:00:00.000Z`. Noon UTC is retained deliberately — it is what `countPastPending` at [user-tasks.ts:2465-2468](apps/server/src/trpc/routes/user-tasks.ts) already assumes.
    - update: a `deleted` transition of `false → true` is treated exactly like the existing node-removal path — `status: 'deleted'`, `completedAt` untouched. The node stays in the doc.

14. **Markdown grammar** — [agenda-markdown.ts:44-364](apps/server/src/services/agenda/agenda-markdown.ts) and its mail-side twin
    - `GROUP_HEADER_RE` and all `conversationGroup` handling are removed.
    - New: `{agendaBandSection,band:"tomorrow"}` recognizer/serializer, mirroring `TASK_SECTION_RE`.
    - **`dueDate` is added to the task attr block** — `- [ ] {taskId:"…",dueDate:"2026-07-24",conversationId:"…"} Text`. Without this, the agent rewriting the Future doc through markdown would strip every due date and collapse all bands, since band membership is derived from `dueDate`.
    - Data after a round-trip:
      ```text
      {agendaBandSection,band:"next-week"}
      {taskGroupSection,taskGroupId:"g-deals"}
      - [ ] {taskId:"t7",dueDate:"2026-07-24",conversationId:"c-pirros"} prep renewal deck @[c-pirros]
      ```

15. **Agent run** — `daily-agenda` subagent
    - The agent no longer assembles the agenda. It reads `user/agendas/current` (already fully populated by the reconciler), re-orders sections and tasks, prepends a short summary/context block above the first section, and writes it back. The `#today` / `#overdue` `listTasksTool` sweeps in [SKILL.md:470-500](apps/server/.claude/skills/tasks/SKILL.md) are replaced by a single `read-document`.
    - Prose placement is already supported: a non-empty prose line while only a section is open closes the section ([agenda-markdown.ts:236-242](apps/server/src/services/agenda/agenda-markdown.ts)), and `ensureLeadingDateHeading` guarantees the `dateHeading` stays first.
    - The tombstone sweep runs here: tasks whose node carries `deleted: true` are dropped from the rewritten doc.
    - `DAILY_AGENDA_PROMPT` at [aop-agents.ts:604](apps/server/src/services/aop/aop-agents.ts) and [agenda-format.md](apps/server/.claude/skills/document-management/agenda-format.md) are rewritten for the new grammar — the latter currently documents neither `taskGroupSection` nor bands.

16. **Invoke with subtree** — `buildInvocationPrompt` at [build-invocation-prompt.ts:33](apps/server/src/services/tasks/build-invocation-prompt.ts)
    - Gains an optional `outline` field: the invoked task's ancestor chain and full descendant subtree, rendered as an indented checklist. The client builds it from the live doc using the sibling-walk already implemented in `expandSelectionForSubtreeDrag` at [expand-subtree-selection.ts](apps/mail/modules/agentCanvas/utils/expand-subtree-selection.ts), which widens a selection to cover every following node of deeper `indentLevel`.
    - Invoking the child of `@pirros / text peter` therefore passes both lines, and invoking the parent passes the whole subtree — the agent gets the structure and resolves the rest.
    - Data after this step:
      ```json
      { "taskId": "t9", "description": "text peter",
        "outline": "- [ ] @[c-pirros]\n  - [ ] text peter" }
      ```

### 3.3 Schema

No database migration is required. `user_tasks` and `task_groups` are unchanged — every new concept (view, band, tombstone, inline chip, parent/child) is either derived at read time or carried in existing ProseMirror node attributes.

Full schema:

```ts
// ─── Agenda view key — replaces the yyyy-MM-dd doc key ──────────────────────
// apps/mail/modules/agentCanvas/types/agenda.ts (new)
export type AgendaView = 'current' | 'future';                          // NEW

// Path: user/agendas/current | user/agendas/future
// Regex: /^user\/agendas\/(current|future)$/                            // CHANGED
// buildDocPath.agenda: (view: AgendaView) => `user/agendas/${view}`     // CHANGED

// ─── Future bands — derived from dueDate on every read, never persisted ─────
// apps/server/src/services/agenda/future-agenda-reconciler.ts (new)
export type AgendaBand =                                                // NEW
  | 'tomorrow'      // today + 1
  | 'day-after'     // today + 2
  | 'next-week'     // today + 3 … +7
  | 'next-month'    // today + 8 … +31
  | 'future';       // today + 32 …

export const BAND_ORDER: readonly AgendaBand[] = [                      // NEW
  'tomorrow', 'day-after', 'next-week', 'next-month', 'future',
];

export function bandForDueDate(dueKey=[redacted], todayKey=[redacted] AgendaBand; // NEW

// ─── ProseMirror node schema ────────────────────────────────────────────────

// dateHeading — apps/mail/modules/agentCanvas/components/DateHeadingNode.tsx
interface DateHeadingAttrs {
  date: string | null;          // yyyy-MM-dd; null on the future doc   // CHANGED
  view: AgendaView;             // drives the label                     // NEW
}

// agendaBandSection — apps/mail/modules/agentCanvas/extensions/AgendaBandSectionNode.tsx (new)
interface AgendaBandSectionAttrs {
  band: AgendaBand;                                                     // NEW
}
// group: 'block'
// content: 'agendaBandHeader (taskGroupSection | agendaTask)*'
// defining: true

// agendaBandHeader — same file (new)
interface AgendaBandHeaderAttrs {
  band: AgendaBand;                                                     // NEW
}
// leaf, selectable: false, draggable: false

// taskGroupSection — apps/mail/modules/agentCanvas/extensions/TaskGroupSectionNode.tsx
interface TaskGroupSectionAttrs {
  taskGroupId: string;          // '' = Misc, '__past__' = past lane
}
// content: 'taskGroupHeader agendaTask*'   // conversationGroup removed  // CHANGED

// taskGroupHeader — same file (unchanged)
interface TaskGroupHeaderAttrs {
  taskGroupId: string;
}

// agendaTask — apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx
interface AgendaTaskAttrs {
  checked: boolean;             // mirrors user_tasks.status === 'done'
  taskId: string | null;        // FK → user_tasks.id
  conversationId: string | null;// FK → crm_conversations.id, OWNING link
  dueDate: string | null;       // yyyy-MM-dd; now serialized to markdown // CHANGED
  localId: string | null;
  indentLevel: number;          // parent/child depth; passed on invoke   // CHANGED (semantics)
  chatThreadId: string | null;
  taskGroupId: string | null;   // FK → task_groups.id, null = Misc
  deleted: boolean;             // true = tombstone; survives reconcile   // CHANGED (semantics)
}
// content: 'inline*' — may contain N conversationNode chips              // CHANGED

// conversationNode — apps/mail/modules/agentCanvas/extensions/ConversationNode.tsx (unchanged)
interface ConversationNodeAttrs {
  conversationId: string | null;// FK → crm_conversations.id, reference
}
// inline atom; markdown `@[<conversationId>]`

// conversationGroup / conversationGroupHeader                            // DELETED
// AgendaDateNav / MultiDayAgenda / loadedPastDates / loadedFutureDates   // DELETED

// ─── Invoke payload ─────────────────────────────────────────────────────────
// apps/server/src/services/tasks/build-invocation-prompt.ts
interface InvocationPromptInput {
  taskId: string;
  description: string;
  conversationId: string | null;
  conversationName: string | null;
  dueDate: string;
  notes: string | null;
  outline?: string;             // ancestor chain + descendant subtree    // NEW
}

// ─── Unchanged, shown for reference ─────────────────────────────────────────
// user_tasks (apps/server/src/db/aop-schema.ts:892)
//   id uuid PK, user_id text NOT NULL, conversation_id uuid NOT NULL,
//   description text, notes text, status text NOT NULL DEFAULT 'todo',
//   due_date timestamp NOT NULL DEFAULT now(),   ← both display AND scheduling
//   completed_at timestamp, task_group_id uuid NULL → task_groups.id,
//   chat_thread_id text, agent_execution_enabled boolean NOT NULL DEFAULT false,
//   task_action_data jsonb, task_created_by text, created_at, updated_at
// task_groups (apps/server/src/db/aop-schema.ts:1239)
//   id uuid PK, user_id text, name text, color text, position int NOT NULL,
//   routing_criteria text, overdue_policy jsonb, agent_visible boolean
```

Relationship diagram:

```text
  ┌──────────────────────────┐          ┌───────────────────────────────┐
  │ documents                │          │ user_tasks                    │
  │  id            uuid PK   │          │  id             uuid PK       │
  │  org_id        uuid      │          │  user_id        text          │
  │  user_id       text      │          │  conversation_id uuid NOT NULL│
  │  document_type 'agenda'  │          │  due_date       timestamp     │◄── the ONLY
  │  path                    │          │  status         text          │    persisted
  │  content_yjs   bytea     │          │  task_group_id  uuid NULL     │    time signal
  │  UNIQUE(org,user,path)   │          │  deleted ⇢ status='deleted'   │
  └────────────┬─────────────┘          └──────┬──────────────┬─────────┘
               │                               │              │
    path ∈ { user/agendas/current,             │ FK           │ FK (null = Misc)
             user/agendas/future }             ▼              ▼
               │                     ┌──────────────────┐  ┌──────────────────┐
               │  exactly 2 per user │ crm_conversations│  │ task_groups      │
               │                     │  id       uuid PK│  │  id     uuid PK  │
               ▼                     │  name            │  │  name            │
  ╔═════════════════════════╗        │  priority        │  │  position int    │
  ║ ▼ contains (Y.js / PM)  ║        └────────┬─────────┘  └────────┬─────────┘
  ║                         ║                 │                     │
  ║  dateHeading            ║                 │ rendered as         │ rendered as
  ║   {date, view}          ║                 │                     │
  ║                         ║                 ▼                     ▼
  ║  agendaBandSection      ║        ┌──────────────────┐  ┌──────────────────┐
  ║   {band}  ── future only║        │ conversationNode │  │ taskGroupSection │
  ║    ▼ contains           ║        │  {conversationId}│  │  {taskGroupId}   │
  ║    taskGroupSection     ║◄───────┤  inline atom, N  │  │  block           │
  ║     {taskGroupId}       ║ 1:N    │  per task        │  └──────────────────┘
  ║      ▼ contains         ║        └────────▲─────────┘
  ║      agendaTask ────────╫─────────────────┘ inline*
  ║       {taskId} ──FK──►  ║   user_tasks.id
  ║       {conversationId}  ║ ──FK──► crm_conversations.id   (OWNING, NOT NULL)
  ║       {taskGroupId} ────╫──FK──► task_groups.id          (null = Misc)
  ║       {dueDate} ────────╫──derives──► agendaBand         (never persisted)
  ║       {indentLevel} ────╫──1:N──► child agendaTask       (invoke outline)
  ║       {deleted} ────────╫──►  status='deleted', node kept as tombstone
  ╚═════════════════════════╝

  Band derivation (read time, per request):
    bandForDueDate(user_tasks.due_date, agendaTodayKey) ──► AgendaBand
    agendaTodayKey=[redacted]s LOCAL today, sent by AgendaDocument
    ⇒ band membership drifts nightly ⇒ reflowBands() moves nodes across sections
```

## 4) Implementation phases

Phase 1 builds the headless driver every later phase is verified through. Phases 2–4 are task-row and context work, independent of the doc restructure, and keep the dated-doc world working. Phases 5–7 perform the Current | Future cutover. Phase 8 moves the agent onto the new surface.

Existing dated `user/agendas/YYYY-MM-DD` rows are a **hard cut**: they stay in the DB, become unreachable, and are not migrated. No task data is lost — tasks live in `user_tasks` and re-derive into the new docs on first read.

### Phase 1 — Headless agenda driver

**Goal:** the whole agenda pipeline — fetch, reconcile, structure, serialize — is drivable and inspectable from the command line, so every later phase is verified against the real code path rather than unit tests alone.

**Amended from the original plan.** This phase was last, and specified an in-process script at `apps/server/src/scripts/agenda-headless.ts`. Both changed:

- **Moved first.** Every later phase is verified through this driver, so it has to exist before them.
- **Reshaped as a `cedar-cli` verb over tRPC-HTTP.** The repo's CLI is explicitly a thin HTTP client that "holds no business logic and opens no DB handle" ([index.ts:1-9](apps/server/src/cli/index.ts), [cli-over-http.md](apps/server/docs/cli-over-http.md)). Driving `documents.getDoc` exercises the same path the app uses instead of a second in-process implementation that could drift. No `pnpm agenda` alias was added — `pnpm cedar-cli agenda` matches every other verb.
- **The view argument accepts both a date key and a view key**, so the driver spans the Phase 5 path cutover unchanged.

- [x] Add [agenda.ts](apps/server/src/cli/agenda.ts) with `dump` (markdown mirror), `tree` (node outline), `json` (raw ProseMirror), and `tasks` (the `user_tasks` rows behind the doc).
- [x] Register the `agenda` verb in the command map at [index.ts:21](apps/server/src/cli/index.ts).
- [x] Decode the base64 `contentYjs` payload with the server's own `ydocToProsemirrorJson` from [hydrate.ts:26](apps/server/src/services/document-saving/hydrate.ts) — **`contentJson` reads null on every live doc**, because the reconcile path in [get-doc.ts](apps/server/src/services/documents/get-doc.ts) refreshes `contentYjs` and the markdown mirror but never that legacy column.
- [x] Give `userTasks.listUserTasks` a headless entry point via `agenda tasks`.
- [x] Extend the thin-client guard at [no-heavy-imports.test.ts](apps/server/src/cli/__tests__/no-heavy-imports.test.ts) to forbid `services/` imports outright, with a narrow documented allowance for the pure `hydrate` codec.

**Tests:**

- [x] Add [agenda.test.ts](apps/server/src/cli/__tests__/agenda.test.ts) covering `outline` (flat task attrs, checked/tombstone flags, inline `@[id]` chips, nested band/group indentation, the Misc lane) and `tally`.
- [x] `pnpm exec vitest run src/cli/__tests__` — 15 passed.
- [x] `pnpm cedar-cli agenda tree 2026-07-21` green against the live server as <email>.

**Baseline captured for later phases** (live data, 2026-07-21): the first read returned 27 tasks / 16 `conversationGroup`s, then converged to a stable **25 tasks, 15 `conversationGroup`s** once the reconcile swept two stale rows and persisted. Of the 16 groups in the first sample, **9 (56%) wrapped exactly one task** — the layering noise Phase 3 removes. Also present: 6 `taskGroupSection`s (4 empty), 3 stray `heading` nodes, and 2 `conversationGroup`s sitting at top level outside any section. Phase 3 should drive `conversationGroup` to 0. The reconcile is idempotent — three consecutive reads return identical counts.

**Environment trap — read this before trusting any headless check.** Several checkouts of this repo run side by side, each with its own `PORT_API`: `cedar-mail-1` holds the default **8787/8788/8789**, this checkout (`cedar-mail-2`) holds **8790/8791/8792** per its root `.env`. But `~/.cedar-cli.json` pins `baseUrl` machine-globally, and in [config.ts:46-62](apps/server/src/cli/config.ts) the store **outranks** the repo's `VITE_PUBLIC_BACKEND_URL`. So `pnpm cedar-cli` run from this checkout talks to *the other checkout's server* — and nothing looks wrong, because both are served from the same database: the ids match, the data is real, HTTP is 200. Only the code answering the call belongs to someone else. Two consequences:

- Always run headless checks with `CEDAR_API_URL=http://localhost:8790`, or verify the `via <baseUrl>` now printed in every `agenda dump`/`agenda tree` header.
- The dev server runs as a **built bundle** (`dist/api-service/index.cjs`) rebuilt by `dev-runtime.mjs`, not `tsx watch` over source — so a source edit is only live once that rebuild lands.

`loadCliConfig` now emits a loud warning on this mismatch ([config.ts](apps/server/src/cli/config.ts), covered in [agenda.test.ts](apps/server/src/cli/__tests__/agenda.test.ts)). The store still wins, because `auth set-key --url` is the documented way to aim the CLI at a remote — the fix is visibility, not a changed contract.

**Gotcha found while building it.** The markdown mirror in `documents.content` has **diverged from `contentYjs`** on the live doc: the mirror renders only the first `conversationGroup` with real `{conversationGroup,…}` syntax and collapses every later group header and task into run-together prose, while the Y.js state holds correct structure throughout. Treat `agenda tree` (Y.js-derived) as the assertion surface and `agenda dump` as advisory. This is independent evidence for the §3.2 step-14 finding that the markdown boundary is lossy, and Phase 3's `dueDate` serialization work should re-check it.

### Phase 2 — Consistent task row, working scrim, tombstone delete

**Goal:** one render branch per task with a fixed-width leading action button, a `backdrop-blur` that actually blurs, and a delete that X's out in place.

Button geometry as built: `inline-flex h-6 w-32 shrink-0 items-center justify-center gap-1 rounded-md text-xs font-medium`, with `bg-action hover:bg-action-hover text-action-foreground` for the primary variant and `bg-sunken hover:bg-muted text-muted-foreground` for `Execute`. The label/variant choice was extracted out of the NodeView into the pure `deriveAgendaActionButton` in [agenda-right-slot-state.ts](apps/mail/modules/agentCanvas/utils/agenda-right-slot-state.ts) so the states are assertable without mounting a ProseMirror editor.

- [x] Add a leading action button in `AgendaTaskNodeView` at [AgendaTaskNode.tsx:548-590](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx), between the checkbox and `NodeViewContent`: `w-32 h-6 shrink-0 rounded-md text-xs font-medium`.
- [x] Style the button from `slot.artifact`: set → `bg-action hover:bg-action-hover text-action-foreground` with label `Open draft` / `Open message` / `Open invite`; unset → `bg-sunken hover:bg-muted text-muted-foreground` with label `Execute`.
- [x] Wire the button's click to `handleOpenArtifact` when `slot.artifact` is set, else `handleInvoke`.
- [x] Delete the second-row action block at [AgendaTaskNode.tsx:633-765](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx), moving its bot icon, Snooze, and Re-do controls into the single right hover overlay.
- [x] Make the right hover overlay at [AgendaTaskNode.tsx:773-866](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx) render for every task regardless of `slot`, containing Snooze / Re-do / tombstone.
- [x] **Amendment: the bot icon is NOT in the hover cluster.** §3.2 step 10 originally put it there, but it is a status indicator, not an action — hiding it until hover would mean hovering every row one by one to find the task waiting on you. It renders in a persistent slot at the row's right edge whenever `slot.bot` is set; only the true actions are hover-mounted.
- [x] Fix the scrim at [AgendaTaskNode.tsx:783-786](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx): drop `-z-10` for `z-0`, add `relative z-10` to the button cluster so the scrim paints above the task text.
- [x] Replace the overlay's `invisible`/`visible` toggle with conditional mounting on the existing `isRowHovered` state at [AgendaTaskNode.tsx:545-546](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx), keeping `isCursorInside` as the second mount condition.
- [x] Change `handleDeleteTask` at [AgendaDocument.tsx:1117-1141](apps/mail/modules/agentCanvas/components/AgendaDocument.tsx) to set `deleted: true` via `tr.setNodeAttribute` instead of `tr.delete`, keeping the `deleteTask(taskId)` sync call.
- [x] Treat a `deleted` `false → true` attr transition as a soft-delete in `agendaTaskSyncHook` at [agenda-task-sync.ts:131-210](apps/server/src/services/document-saving/hooks/agenda-task-sync.ts), writing `status: 'deleted'`.
- [x] Exempt nodes with `deleted: true` from removal in `removeInactiveAgendaTasks` at [daily-agenda-reconciler.ts:594-635](apps/server/src/services/agenda/daily-agenda-reconciler.ts). Implemented in the shared `inactiveTaskId` chokepoint rather than at the three call sites, so the exemption covers section, conversationGroup and top-level pruning in one place.

**Tests:**

- [x] Extend the **existing** [agenda-right-slot-state.test.ts](apps/mail/tests/modules/agentCanvas/agenda-right-slot-state.test.ts) (the path this doc originally guessed, `modules/agentCanvas/utils/__tests__/`, is not where mail keeps its tests) with 8 cases for the new pure `deriveAgendaActionButton` helper: label + variant per artifact, the `Execute` fallback, Review vs Apply, field-approval precedence over an artifact, and the invariant that only `Execute` uses the muted fill.
- [x] Add a case to [agenda-task-sync.test.ts](apps/server/src/services/document-saving/hooks/__tests__/agenda-task-sync.test.ts) asserting a `deleted: false → true` attr change writes `status: 'deleted'`.
- [x] Add a case to [daily-agenda-reconciler.test.ts](apps/server/src/services/agenda/__tests__/daily-agenda-reconciler.test.ts) asserting a tombstoned node survives a reconcile in which its task is inactive.
- [x] `pnpm exec vitest run src/services/agenda src/services/document-saving/hooks` — 48 passed.
- [x] Confirmed the reconcile stays idempotent after the exemption: `CEDAR_API_URL=http://localhost:8790 pnpm cedar-cli agenda tree 2026-07-21` returns identical node counts across three consecutive runs. (First measured against port 8787 — the *other checkout's* server — and therefore meaningless; re-run against this repo's 8790. See the environment trap under Phase 1.)
- [ ] Manually confirm in the running app that the hover scrim blurs task text behind the overlay in Chromium. **Still open — the one item here that cannot be checked headlessly.**

### Phase 3 — Flat tasks, inline conversation chips

**Goal:** retire `conversationGroup` so a single-task conversation is one flat line with an inline `@` chip.

- [x] Remove `conversationGroup` construction from `buildSectionBodyJson` at [daily-agenda-reconciler.ts:197-234](apps/server/src/services/agenda/daily-agenda-reconciler.ts); append tasks flat, keeping the `CONVERSATION_PRIORITY_ORDER` sort.
- [x] Append a trailing `conversationNode` chip to each built task's inline content for its owning `conversationId`.
- [x] Remove the group-folding branch from `insertTasksIntoSection` at [daily-agenda-reconciler.ts:662-695](apps/server/src/services/agenda/daily-agenda-reconciler.ts).
- [x] **Added, not in the original plan: `flattenLegacyConversationGroups`.** Live documents already contain `conversationGroup` nodes (15 on the baseline doc). Simply dropping group support would strand them forever — the client would no longer register the node type and the reconciler would neither create nor prune them. The new pass unwraps each group in place, hoists its tasks, inherits the group's `conversationId` onto any task lacking one, and gives each hoisted task its chip. It runs before the prune so pruning only ever sees flat tasks, and is idempotent.
- [ ] Remove `conversationGroup` pruning from `removeInactiveAgendaTasks` at [daily-agenda-reconciler.ts:594-635](apps/server/src/services/agenda/daily-agenda-reconciler.ts) — deferred until the client can no longer produce a group (Phase 3 mail track), so a stale tab mid-deploy still heals.
- [ ] Narrow `taskGroupSection`'s `content` to `'taskGroupHeader agendaTask*'` at [TaskGroupSectionNode.tsx:203-221](apps/mail/modules/agentCanvas/extensions/TaskGroupSectionNode.tsx).
- [ ] Delete [ConversationGroupNode.tsx](apps/mail/modules/agentCanvas/extensions/ConversationGroupNode.tsx) and its registration in `extraExtensions` at [AgendaDocument.tsx:307-343](apps/mail/modules/agentCanvas/components/AgendaDocument.tsx).
- [ ] Delete the `conversationGroup` pruning branch from `handleDeleteTask` at [AgendaDocument.tsx:1126-1135](apps/mail/modules/agentCanvas/components/AgendaDocument.tsx) and the equivalent branch in the snooze source-removal path at [AgendaDocument.tsx:923-1020](apps/mail/modules/agentCanvas/components/AgendaDocument.tsx).
- [ ] Delete `shouldHideConversationBadge` and the trailing badge block at [AgendaTaskNode.tsx:591-626](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx).
- [ ] Remove `GROUP_HEADER_RE` and all `conversationGroup` parse/serialize handling from [agenda-markdown.ts:44-364](apps/server/src/services/agenda/agenda-markdown.ts) and its mail-side twin at [agenda-markdown.ts](apps/mail/modules/agentCanvas/utils/agenda-markdown.ts).
- [ ] Serialize `dueDate` into the task attr block in `serializeAgendaToMarkdown` and parse it in `parseAgendaMarkdown` at [agenda-markdown.ts:116-135](apps/server/src/services/agenda/agenda-markdown.ts), closing the round-trip data loss.
- [ ] Adopt the first inline `conversationNode` as the owning `conversationId` on create in `agendaTaskSyncHook` at [agenda-task-sync.ts:97-130](apps/server/src/services/document-saving/hooks/agenda-task-sync.ts) when the attr is unset.
- [ ] Remove `groupSelectionFromDragEvent`'s `conversationGroup` handling from [AgendaSubtreeDrag.ts:70-98](apps/mail/modules/agentCanvas/extensions/AgendaSubtreeDrag.ts).

**Scope amended after a reference survey.** `conversationGroup` is referenced by **17 files**, not the 9 this phase originally listed. Several are load-bearing behavior that has to be *rewired*, not deleted — most importantly the `@`-mention flow, which is the exact interaction this change is about:

- [ ] Rewire [handle-agenda-mention-select.ts:83](apps/mail/modules/agentCanvas/utils/handle-agenda-mention-select.ts) to insert an inline `conversationNode` chip at the cursor instead of calling `promoteTaskToGroup`. Today, typing `@pirros` inside a task **wraps that task in a `conversationGroup`** — this single call is what produces the nesting the redesign removes. It must still set the `conversationId` attr so the owning FK is unchanged.
- [ ] Delete [promote-task-to-group.ts](apps/mail/modules/agentCanvas/utils/promote-task-to-group.ts) — its whole purpose is the 4-case task-into-group restructure, which has no meaning once groups are gone.
- [ ] Rewrite `conversationIdForTask` in [agenda-task-walk.ts:48-80](apps/mail/modules/agentCanvas/utils/agenda-task-walk.ts) to read the task's own attr and inline chips instead of walking up to a parent `conversationGroup`.
- [ ] Simplify [insert-under-new-tasks-banner.ts:95-97](apps/mail/modules/agentCanvas/utils/insert-under-new-tasks-banner.ts) to scan for `agendaTask` only.
- [ ] Remove the emptied-parent-group pruning from [CrossEditorDragSourceExtension.ts:103-110](apps/mail/modules/agentCanvas/extensions/CrossEditorDragSourceExtension.ts).
- [ ] Drop `'conversationGroup'` and `'conversationGroupHeader'` from the node-id list at [ensureNodeIdsPlugin.ts:56-57](apps/mail/modules/documents/yjs/ensureNodeIdsPlugin.ts).
- [ ] Update the stale group-collapsing contract in the `onDelete` docstring at [AgendaTaskContext.tsx:40,91](apps/mail/modules/agentCanvas/context/AgendaTaskContext.tsx), plus the comments at [use-agenda-task-sync.ts:97](apps/mail/modules/agentCanvas/hooks/use-agenda-task-sync.ts) and [ConversationMention.ts:38](apps/mail/modules/documents/mention/ConversationMention.ts).

**Tests:**

- [ ] Update [daily-agenda-reconciler.test.ts](apps/server/src/services/agenda/__tests__/daily-agenda-reconciler.test.ts) — the "nests deal-linked tasks" and "prunes the empty group" cases become flat-append assertions.
- [ ] Add `apps/server/src/services/agenda/__tests__/agenda-markdown.test.ts` covering a full parse → serialize → parse round-trip that preserves `dueDate`, `taskId`, and inline `@[id]` chips.
- [ ] Add a case to [agenda-task-sync.test.ts](apps/server/src/services/document-saving/hooks/__tests__/agenda-task-sync.test.ts) asserting a task with no `conversationId` attr but one inline chip persists with that conversation as owner.
- [ ] Delete the `promoteTaskToGroup` describe block (~250 lines) from [agenda-editor.test.ts:939-1190](apps/mail/tests/modules/agentCanvas/agenda-editor.test.ts) and update the mention-select cases to assert an inline chip is inserted rather than a group created.
- [ ] Update [agenda-markdown.test.ts](apps/mail/tests/modules/agentCanvas/agenda-markdown.test.ts) for the removed group grammar.
- [ ] `pnpm exec vitest run src/services/agenda src/services/document-saving/hooks` (server) and `pnpm --filter @zero/mail test -- tests/modules/agentCanvas` (mail runs **jest**, not vitest).
- [ ] `pnpm cedar-cli agenda tree 2026-07-21` must report `conversationGroup: 0` and preserve the task count (25 at baseline).

### Phase 4 — Parent/child task context on invoke

**Goal:** invoking any task passes its ancestor chain and descendant subtree to the agent.

- [x] Add [build-task-outline.ts](apps/mail/modules/agentCanvas/utils/build-task-outline.ts). Signature landed as `buildTaskOutline(rows, index)` over a plain `OutlineRow[]` rather than `(doc, taskPos)` — keeping it free of ProseMirror types makes it unit-testable without mounting an editor, and the caller already has the sibling list. It emits the ancestor chain, the row itself marked `->`, and the subtree, re-based to the shallowest included row. The parent walk deliberately mirrors `expandSelectionForSubtreeDrag` so dragging and invoking agree on what a task's children are.
- [ ] Pass the outline from `handleInvoke` in [AgendaTaskNode.tsx](apps/mail/modules/agentCanvas/extensions/AgendaTaskNode.tsx) through the invoke callback into `agentExecutions.invokeTaskInChat` at [agent-executions.ts:1246](apps/server/src/trpc/routes/agent-executions.ts).
- [x] Add the optional `outline` field to `InvocationPromptInput` and render it in `buildInvocationPrompt` at [build-invocation-prompt.ts:33](apps/server/src/services/tasks/build-invocation-prompt.ts), instructing the agent to read parents as context but act only on the marked row.
- [x] Accept `outline` on `invokeTaskInChat` at [agent-executions.ts:1246](apps/server/src/trpc/routes/agent-executions.ts) and thread it into **both** prompt call sites (reused thread and fresh thread).

**Tests:**

- [x] Add [build-task-outline.test.ts](apps/mail/tests/modules/agentCanvas/build-task-outline.test.ts) — 8 cases: lone flat task (empty), header + child, parent with two children, three-level climb, subtree stopping at the first equal-depth row, exclusion of unrelated preceding rows, indentation re-basing, out-of-range index.
- [x] Add `buildInvocationPrompt` cases asserting the outline renders when present, carries the act-only-on-the-marked-row instruction, and is omitted for undefined / null / empty / whitespace.
- [x] `pnpm --filter @zero/mail test -- tests/modules/agentCanvas` — 8 passed; `pnpm --filter @zero/server exec vitest run src/services/tasks` — 34 passed.
- [ ] Wire `handleInvoke` in AgendaTaskNode to build and pass the outline — deferred until the conversationGroup retirement lands in that file.

### Phase 5 — Path cutover to `current` | `future`

**Goal:** the agenda resolves to two fixed paths; Current holds everything due.

- [ ] Add `AgendaView` in `apps/mail/modules/agentCanvas/types/agenda.ts` and re-export the same union server-side from [agenda-surface.ts](apps/server/src/services/agenda/agenda-surface.ts).
- [ ] Change `userAgendaPath` at [convention-paths.ts:48-50](apps/server/src/services/documents/convention-paths.ts) to take a view key.
- [ ] Change `DAILY_AGENDA_PATH` at [doc-type-registry.ts:156](apps/server/src/services/documents/doc-type-registry.ts) to `/^user\/agendas\/(current|future)$/` and update `dailyAgendaDef.parsePath` at [:173-178](apps/server/src/services/documents/doc-type-registry.ts).
- [ ] Delete the `rolling` flag at [doc-type-registry.ts:183](apps/server/src/services/documents/doc-type-registry.ts) and dispatch on `subPath` instead.
- [ ] Change `buildDocPath.agenda` at [buildDocPath.ts:13](apps/mail/modules/files/store/buildDocPath.ts) to take an `AgendaView`.
- [ ] Rewrite `fetchActiveDailyTasks` at [documents.ts:81-143](apps/server/src/trpc/routes/documents.ts) to branch on view: `current` → `dueDate <= endOfDay(agendaTodayKey)` with **no lower bound**; `future` → `dueDate > endOfDay(agendaTodayKey)`.
- [ ] Delete `ROLLING_LOOKBACK_DAYS` and its lower-bound clause at [documents.ts:81-94](apps/server/src/trpc/routes/documents.ts).
- [ ] Add a `view` attr to `dateHeading` at [DateHeadingNode.tsx:108-123](apps/mail/modules/agentCanvas/components/DateHeadingNode.tsx) and label from it (`Today's agenda` / `Upcoming tasks`).
- [ ] Replace `fallbackDueDateFromSubPath` with `fallbackDueDateForView` at [agenda-task-sync.ts:254-260](apps/server/src/services/document-saving/hooks/agenda-task-sync.ts): `current` → now, `future` → tomorrow noon UTC.
- [ ] Update the `#agendas` shortcut expectation at [path-shortcuts.ts:9](apps/server/src/services/documents/path-shortcuts.ts) and the legacy reconstruction at [virtual-paths.ts:189](apps/server/src/services/documents/virtual-paths.ts).

**Tests:**

- [ ] Add `apps/server/src/services/documents/__tests__/agenda-path.test.ts` covering `DAILY_AGENDA_PATH` accepting `current`/`future` and rejecting a `yyyy-MM-dd` tail.
- [ ] Add a `fetchActiveDailyTasks` test asserting Current has no lower bound and Future excludes today.
- [ ] Add an `agenda-task-sync` case asserting the `current` and `future` due-date fallbacks.
- [ ] `pnpm --filter @zero/server exec vitest run src/services/documents src/services/agenda src/services/document-saving`

### Phase 6 — Future doc and band sections

**Goal:** `user/agendas/future` renders five relative bands that reflow as today advances.

- [ ] Add `AgendaBandSectionNode.tsx` in `apps/mail/modules/agentCanvas/extensions/` defining `agendaBandSection` and `agendaBandHeader`, modelled on [TaskGroupSectionNode.tsx](apps/mail/modules/agentCanvas/extensions/TaskGroupSectionNode.tsx).
- [ ] Register both nodes in `extraExtensions` at [AgendaDocument.tsx:307-343](apps/mail/modules/agentCanvas/components/AgendaDocument.tsx).
- [ ] Add `apps/server/src/services/agenda/future-agenda-reconciler.ts` with `AgendaBand`, `BAND_ORDER`, and the pure `bandForDueDate(dueKey, todayKey)`.
- [ ] Implement `buildFutureAgendaJson(tasks, groups, todayKey)` emitting all five bands, with a `taskGroupSection` inside a band only when that band has a task in that group.
- [ ] Implement `reconcileFutureAgendaYDoc` with `ensureLeadingDateHeading`, `ensureBandSections`, `reflowBands`, `removeInactiveAgendaTasks`, and band-scoped insert.
- [ ] Register the future reconciler on `subPath === 'future'` in [doc-type-registry.ts:190-206](apps/server/src/services/documents/doc-type-registry.ts).
- [ ] Add `{agendaBandSection,band:"…"}` parse and serialize support to both copies of `agenda-markdown.ts`.
- [ ] Retarget [CrossDayDropExtension.ts](apps/mail/modules/agentCanvas/extensions/CrossDayDropExtension.ts) to resolve the enclosing `agendaBandSection` instead of the nearest preceding `dateHeading`, rescheduling to the band's first day.

**Tests:**

- [ ] Add `apps/server/src/services/agenda/__tests__/future-agenda-reconciler.test.ts` covering `bandForDueDate` at every boundary (+1, +2, +3, +7, +8, +31, +32).
- [ ] Add a case asserting all five bands are emitted when empty, and that a group section appears only in bands that have a task in it.
- [ ] Add a `reflowBands` case asserting a task moves from `day-after` to `tomorrow` when `todayKey` advances by one, preserving within-band order.
- [ ] `pnpm --filter @zero/server exec vitest run src/services/agenda`

### Phase 7 — Current | Future toggle in the UI

**Goal:** the agenda surface is a two-segment toggle; all per-day navigation is gone.

- [ ] Add `AgendaViewToggle.tsx` in `apps/mail/modules/agentCanvas/components/`, carrying over the `motion.div layoutId` pill from [AgendaDateNav.tsx:63-69](apps/mail/modules/agentCanvas/components/AgendaDateNav.tsx).
- [ ] Change `AgendaHome` at [AgendaHome.tsx:32-64](apps/mail/modules/agentCanvas/components/AgendaHome.tsx) to hold `view` state and render the title row plus the toggle.
- [ ] Change `AgendaDocument`'s prop from `date: string` to `view: AgendaView` at [AgendaDocument.tsx:91-101](apps/mail/modules/agentCanvas/components/AgendaDocument.tsx), replacing `dayKey` throughout.
- [ ] Replace the 60-day task-query window at [AgendaDocument.tsx:649-657](apps/mail/modules/agentCanvas/components/AgendaDocument.tsx) with a view-derived range.
- [ ] Delete [AgendaDateNav.tsx](apps/mail/modules/agentCanvas/components/AgendaDateNav.tsx).
- [ ] Delete [MultiDayAgenda.tsx](apps/mail/modules/agentCanvas/components/MultiDayAgenda.tsx) and point [conversations/agenda.page.tsx](apps/mail/app/(routes)/conversations/agenda.page.tsx) and [CalendarCanvas.tsx](apps/mail/modules/agentCanvas/components/CalendarCanvas.tsx) at the two-view `AgendaDocument`.
- [ ] Delete `loadedFutureDates` / `loadedPastDates` and their loaders from [documentsSlice.ts:238-242, 1227-1290](apps/mail/modules/files/store/documentsSlice.ts).
- [ ] Delete the cross-editor drag source/target extensions and the `MultiDayEditorRegistryProvider` / `CrossEditorDragBusProvider` wrappers now that at most one agenda editor is mounted.
- [ ] Remove the `agenda:cross-day-nav` router that lived in [MultiDayAgenda.tsx:102-120](apps/mail/modules/agentCanvas/components/MultiDayAgenda.tsx) and its `agenda:focus-edge` emit at [AgendaDocument.tsx:1323](apps/mail/modules/agentCanvas/components/AgendaDocument.tsx).
- [ ] Remove the now-unreachable "Move tasks to present" path at [AgendaDocument.tsx:1244-1267](apps/mail/modules/agentCanvas/components/AgendaDocument.tsx) and its `DateHeadingNode` trigger.
- [ ] Delete the dead `/mail/agenda` redirect route at [mail/agenda/page.tsx](apps/mail/app/(routes)/mail/agenda/page.tsx) if nothing links to it.

**Tests:**

- [ ] Add `apps/mail/modules/agentCanvas/__tests__/AgendaViewToggle.test.tsx` asserting each segment calls `onSelectView` with the right key.
- [ ] `pnpm --filter @zero/mail test -- tests/modules/agentCanvas` (jest)
- [ ] `pnpm types` — confirm no dangling references to `AgendaDateNav`, `MultiDayAgenda`, `conversationGroup`, or `loadedPastDates`.

### Phase 8 — Daily agent reads the populated agenda

**Note on the grammar doc.** [agenda-format.md](apps/server/.claude/skills/document-management/agenda-format.md) was rewritten wholesale rather than patched: it documented `{conversationGroup,…}` as the primary structure, mentioned neither `taskGroupSection` nor bands nor chips, and pointed the agent at `user/agendas/YYYY-MM-DD`. Following it would have actively corrupted the document. It now states that Future is server-derived and must never be written, and that `dueDate` has to be carried through on every line — dropping it strands a task out of every band.


**Goal:** the agent runs once a day against a fully-populated Current doc and only re-orders it and writes the summary.

- [x] Rewrite the "Automated daily agenda" pipeline at [SKILL.md:342-504](apps/server/.claude/skills/tasks/SKILL.md) to a single `read-document(user/agendas/current)` followed by re-order and summary, dropping the `#today` / `#overdue` assembly steps.
- [x] Rewrite [agenda-format.md](apps/server/.claude/skills/document-management/agenda-format.md) for the current grammar: `taskGroupSection`, `agendaBandSection`, flat tasks, inline `@[id]` chips, `dueDate` in the attr block — it presently documents none of these.
- [x] Update `DAILY_AGENDA_PROMPT` at [aop-agents.ts:604](apps/server/src/services/aop/aop-agents.ts) to describe re-ordering and summarizing rather than selecting a Top 5.
- [ ] Update `buildDailyAgendaContent` at [seed-playbook.ts:243](apps/server/src/services/playbook/seed-playbook.ts) to match.
- [ ] Drop tombstoned tasks (`deleted: true`) when the agent rewrites the doc, completing the sweep from Phase 2.
- [x] Point the manual Triage prompt at [AgendaDocument.tsx:1203](apps/mail/modules/agentCanvas/components/AgendaDocument.tsx) at the Current doc instead of a date.

**Tests:**

- [ ] Add a `parseAgendaMarkdown` case asserting an agent-authored summary paragraph above the first section survives the round-trip and the `dateHeading` stays first.
- [ ] `pnpm --filter @zero/server exec vitest run src/services/agenda`
- [ ] Run the daily-agenda subagent end-to-end via the Phase 1 driver (`pnpm cedar-cli agenda tree current`) and diff the resulting doc.