Skip to content

Briefing: split to-dos (act) from topics (FYI) #474

@jayzalowitz

Description

@jayzalowitz

Briefing: split "to-dos" (act) from "topics" (be-aware)

Context

The daily briefing is the user's morning surface. A reference AI-inbox product
opens with two distinct buckets — a short "suggested to-dos" list (items that need
the user to act) above a longer "topics to catch up on" list (awareness-only,
grouped by area). SkyTwin's briefing produces prose organized by Meetings / Tasks /
Signals importance and never makes the act-vs-aware distinction explicit. That
distinction is the decision engine's core job: a signal that generated an
actionable CandidateAction is a to-do; a signal that did not is FYI. Surfacing it
costs almost nothing because the data already exists upstream.

Current State

Verified 2026-06-06.

  • packages/policy-prompts/prompts/briefing-prose/v1.md:47-51 — the output
    constraints are: open with a one-sentence summary; ## headers for Meetings,
    Tasks, Signals
    ; bold the single most important item; return valid JSON. There is
    no rule that separates action-required from awareness-only, and no notion of "this
    signal produced a candidate action."
  • packages/policy-prompts/prompts/briefing-prose/v1.md:40-45 — output schema is
    { "briefing": string (Markdown, ≤600 words), "highlight_count": number }.
  • packages/policy-prompts/prompts/briefing-prose/v1.md:11 — deterministic fallback
    is pass-through (no LLM → raw passthrough of inputs).
  • packages/db/src/repositories/briefing-repository.ts — persists daily/weekly
    briefings; packages/db/src/migrations/042-briefing-domain.sql adds per-Lifebook
    domain scoping. Storage is prose-blob shaped (the briefing string), so a
    structured to-do/FYI split needs either new columns or a structured payload field.
  • The decision pipeline already distinguishes actionable from non-actionable:
    packages/decision-engine/src/decision-maker.ts produces CandidateAction[] and a
    DecisionOutcome with autoExecute / requiresApproval.
  • Gap found during review (the main work of this spec): the briefing generator
    (apps/worker/src/jobs/briefing-generator.ts) does NOT currently read decision
    outcomes at all. Its inputs are appSuggestionRepository.getPendingForUser() and a
    tier-promotionResult query — there is no join to DecisionOutcome /
    CandidateAction. So "tag each item with actionRequired" is not a free derivation
    today; it requires a NEW query/data-flow from the decisions store into the briefing
    generator. This is a real architectural step, not a relabel. The implementation must
    define that query (see Implementation Details step 0).

Proposed Change

Make the briefing emit two explicit, ordered sections:

  1. To-dos — signals whose decision outcome is "action required from the user"
    (escalated-to-approval candidate actions, or signals the engine flagged
    actionable but could not auto-resolve). Each line: one-sentence imperative +
    source reference + (if present) deadline.
  2. Topics to catch up on — everything else, grouped by domain, awareness-only.

The classifier is upstream data, not new LLM judgment: a briefing input item is a
to-do iff it carries an associated CandidateAction that did NOT auto-execute
(i.e. requires user action). This is a deterministic mapping from existing pipeline
output.

Implementation Details

  1. Source the decision outcomes (do this first — it's the blocker). Add a query
    that fetches, for the briefing window, each signal's DecisionOutcome
    (autoExecute / requiresApproval) from the decisions store, and join it to the
    briefing inputs in briefing-generator.ts. actionRequired = requiresApproval === true (escalated to the user). Decide the join key (signal id / decision id) and
    whether this is a new repository method on the decisions repo. Without this step
    the rest of the spec has no actionRequired to render.

  2. New prompt version packages/policy-prompts/prompts/briefing-prose/v2.md
    (do not mutate v1 — prompts are versioned per the package contract). Changes vs v1:

    • Input gains a per-item actionRequired: boolean and optional deadline and
      sourceRef fields.
    • Output schema becomes:
      {
        "briefing": "string (Markdown, ≤600 words)",
        "todos": [{ "text": "string", "sourceRef": "string", "deadline": "string|null" }],
        "topics": [{ "domain": "string", "items": [{ "text": "string", "sourceRef": "string" }] }],
        "highlight_count": "number"
      }
    • Constraints: To-dos section first, max 7 items, each a single imperative
      sentence. Topics second, grouped by domain. Never put an actionRequired:true
      item in topics. Deterministic fallback: render to-dos as a bullet list, topics
      grouped by domain, no prose.
  3. Caller wiring — the briefing generator (the code that fills {{events}} /
    {{pending_tasks}}) tags each input item with actionRequired derived from the
    item's decision outcome. Source: whether the signal produced an escalated
    CandidateAction. Pure boolean derivation, no model call.

  4. Storage — add a nullable structured_payload JSONB column to the briefing
    table (new migration) holding { todos, topics }. Keep the briefing prose
    column for back-compat and plain-text/email rendering.

  5. Render — web/mobile briefing views read structured_payload when present,
    render the two-section layout; fall back to the prose blob when null (old rows).

Acceptance Criteria

  1. A briefing generated from a fixture containing 3 actionable signals and 7
    awareness signals renders exactly 3 items under "To-dos" and 7 under "Topics",
    with zero actionable items appearing in Topics.
  2. No item appears in both sections (dedup by sourceRef).
  3. To-dos section is rendered above Topics in both web and mobile.
  4. When the LLM is unavailable, the deterministic fallback still produces the
    two-section split (it is a data partition, not a generative step).
  5. Old briefing rows with structured_payload = NULL still render via the prose
    blob (no regression for historical briefings).
  6. v1 prompt is untouched and still selectable.
  7. Tests written and passing.
  8. No degradation of existing functionality.

Testing Plan

Layer What Count
Unit actionRequired derivation from decision outcome; partition logic; dedup by sourceRef +6
Unit Deterministic fallback produces both sections +2
Integration Briefing generator → v2 prompt → structured_payload persisted +2
Integration Render path: structured payload present vs. NULL (back-compat) +2

Rollback Plan

The change is additive: v2 prompt is a new file, structured_payload is nullable,
renderers fall back to the prose blob. Roll back by pointing the briefing generator
at briefing-prose v1 and ignoring structured_payload. No data migration to
reverse; the new column can stay unused.

Effort Estimate

  • v2 prompt + schema: ~2h
  • Caller wiring (actionRequired derivation): ~2h
  • Migration + repository field: ~1h
  • Web + mobile render: ~3h
  • Tests: ~2h

Total: ~1 day.

Files Reference

File Change
packages/policy-prompts/prompts/briefing-prose/v2.md New: two-section prompt + schema
packages/policy-prompts/prompts/briefing-prose/v1.md Unchanged (versioned, keep)
packages/db/src/migrations/0NN-briefing-structured-payload.sql New: nullable structured_payload JSONB
packages/db/src/repositories/briefing-repository.ts Read/write structured_payload
briefing generator (caller filling {{events}}/{{pending_tasks}}) Tag items with actionRequired
web + mobile briefing views Two-section render with NULL fallback

Out of Scope

  • Generating new candidate actions for awareness items (that's the act layer).
  • Topic grouping quality inside the Topics section — clustering is spec 04.
  • Deadline extraction to populate the deadline field — spec 03.

Related

  • 02 (commitment extraction) and 03 (deadline extraction) enrich the To-dos bucket.
  • 04 (topic clustering) reshapes the Topics bucket.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions