---
name: Email drafting standard — read everything first, re-sweep before compose, lint before save
description: Three-phase mandatory workflow for any draft Joseph might send. Phase 1 intake. Phase 2 freshness re-sweep right before compose (catches new signal). Phase 3 linter chain (claim_audit → draft_audit → voice_check) before save. Hardwired 2026-05-09 after Joseph reported drafts still felt under-cooked despite voice linter.
type: feedback
last_updated: 2026-05-09
originSessionId: 81f95992-93b5-4db5-8fbc-2d5da5aeb321
---

# Email drafting standard

Joseph's standing complaint, restated 2026-05-09:

> "It just feels like drafts are being drafted without really and truly kicking over every stone and combing through every word to really be incredibly effective and also doubling back and making sure that any additional information or context hasn't surfaced somewhere else that may have been missed and not included in the draft before it was drafted in the first place."

The 2026-05-08 voice linter caught phrase-level AI tells (Trevor Pyle rejection). It does not catch the deeper failure: **context loaded at task start goes stale by the time the draft is composed.** A new email, a new HS note, a new transcript, a Rachel veto — any of those can land between intake and compose. If the agent doesn't re-check, the draft ships unaware.

The fix is a three-phase workflow with **structural enforcement at every phase**.

---

## Phase 1 · Intake (read every relevant source, in full, no skips)

Mandatory reads. If any is unavailable, halt and surface the gap to operator before continuing.

| # | Source | What to pull |
|---|---|---|
| 1 | Gmail full inbound thread | Full body of every message, not snippets |
| 2 | Gmail `from:joseph.bowens@skyrun.com to:[recipient]` | Last 10+ sent messages — voice + prior context |
| 3 | Gmail general sent (last 7 days) | Recent voice calibration |
| 4 | HubSpot contact timeline | Engagements, notes, last 30 days minimum |
| 5 | HubSpot live deal stage | `/api/crm/v3/objects/deals/{id}` — never trust memory files for stage |
| 6 | All relevant transcripts | `~/Desktop/SkyRun/Call Transcripts/index.json` — read in full, not just summary |
| 7 | Project memory files | `project_active_deal_*.md`, `project_postmortems.md`, postmortem entries |
| 8 | Knowledge graph | `~/Desktop/SkyRun/knowledge_graph.json` — entity + edges |
| 9 | DNC tier-1 + tier-2 | `dnc_check.py` (current owner) + active-deal seed |
| 10 | Prior decisions | Rachel vetoes (Gmail thread search), closed-lost tags, do-not-outreach memory entries |

**Rule:** No draft may begin composition until all 10 sources have been touched and notes captured.

---

## Phase 2 · Freshness re-sweep (RIGHT BEFORE COMPOSE — non-negotiable)

This is the new layer added 2026-05-09. Between Phase 1 intake and Phase 3 compose, time passes — sometimes 5 min, sometimes 30. **In that gap, new signal can land.** Phase 2 is a fast re-poll of the same sources to confirm nothing has changed since intake.

Required output: a `<draft_audit>` block embedded at the bottom of the draft body (or in a sidecar file passed to `draft_audit.py`). Format:

```
<draft_audit>
recipient: weberst@gmail.com
recipient_name: Stephen Weber
draft_purpose: Follow up on projection email after travel return
compose_started_at: 2026-05-09T17:30:00Z

sources_checked:
  gmail_inbound_thread:
    last_checked_at: 2026-05-09T17:29:14Z
    freshest_message_at: 2026-04-30T15:36:18Z
    new_since_intake: false
  gmail_in_sent_to_recipient:
    last_checked_at: 2026-05-09T17:29:18Z
    messages_pulled: 7
    freshest_sent_at: 2026-04-30T15:36:18Z
  hs_contact_timeline:
    last_checked_at: 2026-05-09T17:29:25Z
    contact_id: 12345
    freshest_engagement_at: 2026-04-30T15:36:18Z
    open_deal_stage: presentationscheduled
  transcripts_relevant:
    last_checked_at: 2026-05-09T17:29:30Z
    files_read: /Call Transcripts/2026-04-27_Weber_Walkthrough.md
    freshest_transcript_at: 2026-04-27T19:00:00Z
  memory_files:
    last_checked_at: 2026-05-09T17:29:35Z
    files_read: project_active_deal_weber.md
    freshest_memory_at: 2026-05-02T18:00:00Z
  dnc_check:
    last_checked_at: 2026-05-09T17:29:40Z
    tier_1: clear
    tier_2_active_deal: this IS the active deal — allowed
  prior_decisions:
    last_checked_at: 2026-05-09T17:29:43Z
    rachel_vetoes: none
    closed_lost: false
  voice_calibration:
    last_checked_at: 2026-05-09T17:29:18Z
    last_sent_to_recipient_pulled: 2026-04-30T15:36:18Z

new_signal_discovered_during_audit: none
re_intake_required: false
ready_to_compose: true
</draft_audit>
```

### The 10-minute freshness window

Every `last_checked_at` timestamp must be **within 600 seconds** of when the draft is saved. If any slips beyond, the linter rejects.

### If new signal is discovered during the re-sweep

If the re-sweep finds anything material that wasn't in the intake — a new inbound message, a new HS note, a new transcript, a Rachel veto — set `re_intake_required: true` and **start over from Phase 1**. Do not patch the draft incrementally. The freshness window is intentionally narrow so this happens before composition, not after.

### Required sources (linter-enforced)

- `gmail_inbound_thread`
- `gmail_in_sent_to_recipient`
- `hs_contact_timeline`
- `transcripts_relevant`
- `memory_files`
- `dnc_check`
- `prior_decisions`
- `voice_calibration`

### Optional sources (linter warns if missing, doesn't reject)

- `knowledge_graph`
- `calendar`
- `smartlead_status`
- `postcard_ledger`

---

## Phase 3 · Linter chain before save (mandatory gate)

Run all three linters in order. **Any FAIL aborts the save.**

```bash
# 1. Outbound claim audit (R-23 — primary-source-cited claims about third parties)
python3 ~/Library/Application\ Support/SkyRun/outbound_claim_audit.py /tmp/draft.txt
# 2. Draft audit (this rule — Phase-2 freshness)
python3 ~/Library/Application\ Support/SkyRun/draft_audit.py /tmp/draft.txt
# 3. Voice check (post-Trevor — anti-AI-tell phrase + structure linter)
python3 ~/Library/Application\ Support/SkyRun/voice_check.py /tmp/draft.txt
```

Exit code semantics (consistent across all three):
- `0` clean — proceed
- `1` REJECT — fix and re-run before save
- `2` WARN — review each warning, then proceed if warranted

Only when **all three return ≤2 with reviewed warnings** does the agent call `mcp__1cba481a-0c62-4959-a458-873418d0b402__create_draft`.

---

## Why this exists (canonical incidents)

| Date | Incident | Root cause | What this rule prevents |
|---|---|---|---|
| 2026-04-28 | Devine status flagged as "overdue/blocked" | Stale ledger; Calendar + Gmail had fresh anchor | Phase 2 catches Calendar/Gmail signal |
| 2026-04-30 | Adam Phase 1 falsely reported "sent" | transcript-scan ≠ delivery proof | Phase 2 forces Gmail in:sent re-check |
| 2026-05-02 | Tim Beegle 30% reply drafted | Rachel's 4/27 veto in Gmail not re-checked | Phase 2 prior_decisions |
| 2026-05-06 | Trevor Pyle "super AI generated" | Voice linter wasn't yet wired | voice_check (now wired) |
| 2026-05-07 | Weber active-deal cold email | tier-2 active-deal gate didn't exist | Phase 2 dnc_check tier-2 |
| 2026-05-09 | Joseph: "kicking over every stone" feeling missing | Phase-2 freshness re-sweep didn't exist | This rule |

## What "kicking over every stone" means in practice

Not poetic — operational. For Stephen Weber as worked example:
- Gmail full thread from him + entire sent history to him (every word, in full)
- HS contact timeline + open deal stage live-fetched
- Walkthrough transcript 2026-04-27 read end-to-end
- `project_active_deal_weber.md` read end-to-end
- `~/Desktop/SkyRun/DNC_active_deals.json` confirms Weber is active-deal — outreach to HIM (the active prospect) is allowed; cold-list outreach with him on it is not
- KG entity + recent edges
- `from:rachel to:joseph weber` Gmail search for any context Rachel sent
- Sent mail patterns to similar archetypes (recent tech-buyer/owner-operator profiles)

**Then** compose — with specific proper-noun callbacks from at least 3 of those sources. **Then** Phase 2 re-sweep + audit block. **Then** the linter chain.

---

## ⛔ HARDWIRED 2026-05-08 — VOICE LINTER GATE (still mandatory, layered with Phase 2 above)

After the Trevor Pyle draft (Gmail id `19dfe060b5bea9b6`, 2026-05-06) was rejected as "super AI generated and not at all in my authentic voice," every draft passes `voice_check.py`. Phase 3 above runs voice_check as the third linter. Anti-pattern blacklist: see `reference_voice_anti_patterns.md`.

**The Trevor draft (canonical "what NOT to write"):**
- Numbered list of "things I can do" (`1. Send you a stabilized revenue projection... 2. Walk you through the agreement...`)
- "single point of contact going forward"
- "stabilized revenue projection ... real numbers to weigh against any other quotes you've gotten"
- "Whichever (or both) is useful, let me know"
- "I'll keep it tight"
- 5 em-dashes in 154 words (3.2 per 100w density — well above the 2 cap)
- Stale signature ("Winter Park, Colorado" — should be "Grand County, Colorado")
- Zero personal callback (no detail from Trevor's actual context)

**The Kina email (canonical voice that PASSES):**
- Specific personal callback: "Hope the bunk-bed mattress made it from Silverthorne."
- Long flowing paragraphs threaded with proper-noun details (welcome book, Tabernash honey, Fraser Valley moose, friend's spices from Morrison)
- "Two thoughts that came up on my side after the call, neither urgent, both for you to use however helps:" — followed by PROSE, not bullets
- Recommendation with conviction: "Either works — I lean toward open-with-guardrails because…"
- Open-ended close: "Whenever you're up here next, I'd love to come by"
- 119 words, 2 em-dashes — passes linter

---

## Voice patterns from sent mail (Joseph → Rachel, canonical)

- Opens with "Hey Rachel," (warm, casual)
- Short declarative sentences, no padding
- Conversational asks embedded in paragraphs — not bulleted question lists
- Specific proper nouns and details (names, addresses, dollar amounts, dates)
- "Here's where my head is at" / "Want to make sure we keep that energy going" / "Ring a bell?"
- Closes with "Anything I'm missing?" or "Let me know your thoughts"
- Signs "— Joseph" internally, no title block
- Bullets only when genuinely listing multiple discrete items; prefers paragraphs for connected thoughts
