External-recipient draft SOP
Hardwired 2026-05-11 PM after a full-day session that burned through 8+ failed draft attempts to ship one email to the Devine prospects. Every failure shape in that session is now structurally blocked by one of the 6 gates below. An agent that follows this SOP in order cannot reproduce any of today's failures.
The six gates
Each gate produces or consumes a cert file. The Claude Code PreToolUse hook at ~/.claude/hooks/gmail_draft_audit_gate.py blocks create_draft unless ~/Library/Application Support/SkyRun/draft_audit_pass.json is current AND was produced by claim_audit_passer.py with ALL gates satisfied.
| # | Gate | Helper script | Cert / output |
|---|---|---|---|
| 1 | Claim audit — every factual claim about a 3rd party has a primary source cited THIS turn | claim_audit_passer.py (--claims-file) | inline in audit pass |
| 2 | Latest-inbound cert — replyToMessageId is the recipient's actual most-recent message | latest_inbound_check.py | <inbound_cert>.json |
| 3 | No-stacking cert (MCP-authoritative) — Gmail MCP list_drafts shows zero existing drafts in this thread/recipient | gmail_draft_purge.py (consumes Gmail MCP JSON) | <nostack_cert>.json, verification_path=gmail_mcp_list_drafts, schema_version=2 |
| 4 | Voice check — zero AI tells (section labels, performative phrases, consultant-speak, marketing copy) | voice_check.py (invoked inside audit-passer) | inline verdict |
| 5 | Commitment audit — every forward commitment in body has a verified deliverable backing, OR body has zero forward commitments | commitment_audit.py | inline detection + optional <commitments_backing>.json |
| 6 | Body persistence verify (POST-create) — after create_draft returns success, Gmail MCP get_thread FULL_CONTENT confirms body actually persisted (not "loading empty") | draft_body_verify.py | <body_verify_cert>.json |
The full workflow
STEP 1 — Compose body
Write the body text to disk at /tmp/<deal>_body.txt
Body must contain ZERO forward commitments unless each has a verified
deliverable file/data/capability that exists right now.
STEP 2 — Identify every factual claim
Write claims JSON to /tmp/<deal>_claims.json with each claim mapped to
a primary source citation (gmail:, transcript:, hs:, sot:, track:,
keydata:, calendar:, sms:, walkthrough:, operator_directive:,
public_record:, sla_capability:, or memory_file:+verified).
STEP 3 — Latest-inbound cert
Call Gmail MCP get_thread on the candidate thread to verify the
most-recent inbound message ID. Run:
latest_inbound_check.py
--recipient <email>
--expected-msg-id <msg_id>
--out /tmp/<deal>_inbound_cert.json
STEP 4 — No-stacking cert (MCP-AUTHORITATIVE — NOT UI-based)
Call Gmail MCP list_drafts with query "to:<recipient>".
Persist the JSON response to /tmp/<deal>_mcp_drafts.json.
If the response shows ANY existing drafts: operator deletes them manually
via Gmail UI, then re-fetch list_drafts and re-persist.
Then run:
gmail_draft_purge.py
--recipient <email>
--thread-id <thread_id>
--gmail-mcp-drafts-json /tmp/<deal>_mcp_drafts.json
--out /tmp/<deal>_nostack_cert.json
STEP 5 — Full 5-gate audit
Run:
claim_audit_passer.py
--body-file /tmp/<deal>_body.txt
--claims-file /tmp/<deal>_claims.json
--to <to_email> --cc <cc_email>
--reply-to-message-id <msg_id>
--latest-inbound-cert-file /tmp/<deal>_inbound_cert.json
--no-stacking-cert-file /tmp/<deal>_nostack_cert.json
--no-forward-commitments (if body has zero forward commits)
OR --commitments-backing-file /tmp/<deal>_backing.json
Audit-passer writes draft_audit_pass.json valid for 180s.
STEP 6 — create_draft within 180-second pass window
Hook checks pass file; if valid, allows create_draft.
Returns Gmail draft ID.
STEP 7 — POST-create body persistence verification
Call Gmail MCP get_thread (FULL_CONTENT) on the thread.
Persist response to /tmp/<deal>_thread_postcreate.json (<120s).
Run:
draft_body_verify.py
--expected-body-file /tmp/<deal>_body.txt
--thread-json-file /tmp/<deal>_thread_postcreate.json
--draft-id <draft_id>
--out /tmp/<deal>_body_verify_cert.json
Three possible statuses:
verified — plaintextBody matches; safe to send
lagging — plaintextBody not yet indexed but snippet shows match;
operator visually verifies in Gmail UI
empty — body did NOT persist; delete draft, retry or pivot to
manual-paste path
STEP 8 — Operator opens Gmail UI, eyeballs, sends
Never assert "the draft is in Gmail and complete" until operator
visually confirms.
Anti-patterns this SOP closes (today's failures, indexed)
| Failure | Where it was caught (or would have been) |
|---|---|
| Fabricated SkyRun-partner-firm claim ("happy to make the intro") | Gate 1 claim audit — no primary source for the claim |
replyToMessageId pointing at stale thread | Gate 2 latest-inbound cert — refuses pass unless msg_id matches verified latest |
| Two drafts in same thread (V5 + V6 stacking) | Gate 3 no-stacking cert (MCP path) — refuses pass if Gmail MCP shows any existing drafts |
| Phantom stacking that fooled UI-rowcount cert | Gate 3 schema v2 — requires verification_path: gmail_mcp_list_drafts, rejects UI-based or skip-purge certs |
| "On peak:" / "the lever in" / "writing flattens" AI tells | Gate 4 voice_check — phrase blacklist + section-label structural patterns |
| "I'd rather pull our verified 90-day median before the call" | Gate 5 commitment audit — refuses pass with forward commit not backed by deliverable |
| V7 went out empty (body did not persist) | Gate 6 body verify — refuses send unless body verified or operator-acknowledged lagging |
| "$103,713 × midpoint compounding = $128K" (invented methodology) | R-31 (no invented methodology in chat to operator) |
| "I can't easily pull Dec-Jan market data" (fabricated blocker) | R-30 (KeyData full access) + R-17 (no fabricated capability blockers) |
What to do when a gate refuses
- Gate 1 refusal → either find a primary source for the claim or remove the claim from the body.
- Gate 2 refusal → re-query Gmail MCP get_thread for the recipient, find the actual latest inbound, regenerate cert with correct msg_id.
- Gate 3 refusal → existing drafts present. Operator manually deletes via Gmail UI, re-fetch list_drafts, re-run purge script.
- Gate 4 refusal → rewrite the offending phrases. The error message names every phrase + the structural reason. Convert section labels to prose; replace performative phrases with direct statements.
- Gate 5 refusal → either remove the forward commitment from body OR produce a verified deliverable + add it to a commitments backing file.
- Gate 6 empty status → delete the draft (operator-side), switch to manual-paste path: print body inline in chat, operator pastes into Gmail Web reply UI directly.
What this SOP does NOT cover
- Calendar conflict pre-check before proposing meeting times. If a body proposes specific meeting times, the agent should ideally query Calendar via the Calendar MCP to verify Joseph isn't already booked. This is a known gap — should be added as a Gate 0 pre-flight if it becomes a recurring failure shape. (Today's draft proposed times without verifying availability; Joseph can self-correct if conflicts exist.)
- Operator-side chat-output discipline. R-29 covers the agent's chat responses to Joseph (mandatory verified_this_turn block, zero guessing words, inline citations). R-31 specifically covers methodology claims in chat. Together they discipline what the agent says to Joseph, but
response_gate.pyis the operator-runnable audit, not a structural gate.
Maintenance
- Any new failure shape that slips through these 6 gates → add to the failure inventory in this file + identify which gate should have caught it + extend or add a gate.
- Gate scripts live at
~/Library/Application Support/SkyRun/:
claim_audit_passer.py (5-gate orchestrator)
- latest_inbound_check.py
- gmail_draft_purge.py (v2, MCP-authoritative)
- voice_check.py
- commitment_audit.py
- draft_body_verify.py
- Hook script at
~/.claude/hooks/gmail_draft_audit_gate.py - Audit history at
~/Library/Application Support/SkyRun/audit_history/(every audit pass record preserved)
Cross-references
- RULES.md — R-23 (primary source), R-29 (mandatory response format), R-30 (KeyData access), R-31 (no invented methodology)
- pre_task_checklist.md — pre-task 5-question gate
- feedback_drafting_standard.md — Phase 1/2/3 drafting discipline (Phase 2 freshness re-sweep, Phase 3 linter chain)
- reference_voice_anti_patterns.md — canonical anti-pattern catalog