aboutsummaryrefslogtreecommitdiff
path: root/.ai/workflows
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-06 21:59:52 -0500
committerCraig Jennings <c@cjennings.net>2026-05-06 21:59:52 -0500
commitd81b23ad6b6e437dfe3c338a00a4be39bc555146 (patch)
tree2d4b0d7890fd1fc70d81282b81fed2808c28a106 /.ai/workflows
parent201377f57430ef28d02e703a2191434bbee55c75 (diff)
downloadrulesets-d81b23ad6b6e437dfe3c338a00a4be39bc555146.tar.gz
rulesets-d81b23ad6b6e437dfe3c338a00a4be39bc555146.zip
chore(ai): initialize project notes and Claude tooling surfaces
Replace the seed notes.org with project-specific context (layout, install modes, task tracker location, recent inflection point). Bring in the synced template surfaces (protocols, workflows, scripts, references, retrospectives, someday-maybe) as tracked content for this content/documentation project.
Diffstat (limited to '.ai/workflows')
-rw-r--r--.ai/workflows/INDEX.org75
-rw-r--r--.ai/workflows/add-calendar-event.org190
-rw-r--r--.ai/workflows/create-workflow.org360
-rw-r--r--.ai/workflows/cross-agent-comms.org334
-rw-r--r--.ai/workflows/daily-prep.org767
-rw-r--r--.ai/workflows/delete-calendar-event.org190
-rw-r--r--.ai/workflows/edit-calendar-event.org186
-rw-r--r--.ai/workflows/email-assembly.org183
-rw-r--r--.ai/workflows/extract-email.org116
-rw-r--r--.ai/workflows/find-email.org122
-rw-r--r--.ai/workflows/first-session.org87
-rw-r--r--.ai/workflows/journal-entry.org218
-rw-r--r--.ai/workflows/page-me.org173
-rw-r--r--.ai/workflows/process-meeting-transcript.org306
-rw-r--r--.ai/workflows/read-calendar-events.org216
-rw-r--r--.ai/workflows/retrospective.org94
-rw-r--r--.ai/workflows/send-email.org198
-rw-r--r--.ai/workflows/startup.org178
-rw-r--r--.ai/workflows/status-check.org178
-rw-r--r--.ai/workflows/summarize-emails.org243
-rw-r--r--.ai/workflows/sync-email.org108
-rw-r--r--.ai/workflows/task-review.org216
-rw-r--r--.ai/workflows/wrap-it-up.org235
23 files changed, 4973 insertions, 0 deletions
diff --git a/.ai/workflows/INDEX.org b/.ai/workflows/INDEX.org
new file mode 100644
index 0000000..3750e91
--- /dev/null
+++ b/.ai/workflows/INDEX.org
@@ -0,0 +1,75 @@
+#+TITLE: Workflow Index
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-04-25
+
+* Purpose
+
+Single-source catalog of every workflow in this directory, with the trigger phrases that should invoke it. Read this file before =ls=-ing the workflows directory — it tells you which file handles which phrase, so you don't have to read each workflow's "When to Use" section to route correctly.
+
+* Drift Check
+
+This index must list every =.org= file in =.ai/workflows/= except this one. Startup verifies the index matches the directory and flags drift (missing entries or stale entries pointing at deleted files).
+
+* Catalog
+
+** Session lifecycle
+
+- =startup.org= — runs automatically at session start. No manual trigger.
+- =first-session.org= — initialize =.ai/= for a brand-new project.
+ - Triggers: "this is a new project", "let's set this project up". Auto-runs if =.ai/sessions/= is empty.
+- =wrap-it-up.org= — end-of-session: write summary, archive, commit, push.
+ - Triggers: "wrap it up", "that's a wrap", "let's call it a wrap"
+- =retrospective.org= — post-mortem after a tough session.
+ - Triggers: "let's do a retrospective", "retrospective time"
+
+** Tasks and planning
+
+- =task-review.org= — list all open tasks (list mode) or pick the next task (next mode).
+ - Triggers: "what's next", "what should I work on", "list open tasks", "show me all tasks", "what's on my plate", "task review", "show me my tasks", "I need a recommendation"
+- =daily-prep.org= — prep brief for the next workday. Two modes: full-prep (default) or standup-only.
+ - Full-prep triggers: "let's prep for tomorrow", "daily prep"
+ - Standup-only triggers: "what's my standup report", "let's do the daily standup report", "give me the standup brief"
+- =journal-entry.org= — capture a daily journal entry.
+ - Triggers: "let's do a journal entry", "create a journal entry"
+
+** Calendar
+
+- =add-calendar-event.org= — create a calendar event.
+ - Triggers: "create an event", "add appointment", "schedule a meeting", "add to my calendar", "calendar event for..."
+- =read-calendar-events.org= — read / summarize calendar.
+ - Triggers: "what's on my calendar", "show me appointments", "summarize my schedule", "what do I have today", "calendar for this week", "any meetings tomorrow"
+- =edit-calendar-event.org= — modify an existing event.
+ - Triggers: "edit the meeting", "change my appointment", "reschedule", "update the event", "move my appointment"
+- =delete-calendar-event.org= — cancel / remove an event.
+ - Triggers: "delete the meeting", "cancel my appointment", "remove the event", "clear my calendar for..."
+
+** Email
+
+- =sync-email.org= — pull and index new mail.
+ - Triggers: "sync email", "sync mail", "pull new mail", "check for new email"
+- =find-email.org= — search local maildir for emails.
+ - Triggers: "find email about [topic]", "search for emails from [person]", "do I have an email about [subject]?", "look for [shipping/receipt/confirmation] email"
+- =summarize-emails.org= — filter noise, summarize what matters.
+ - Triggers: "summarize my emails", "what emails do I have", "anything important in my inbox", "email summary", "any unread emails", "any starred emails", "emails from [person]", "emails also sent to [person]"
+- =extract-email.org= — extract content / attachments from an inbox EML.
+ - Triggers: "extract the email", "get the attachment from [email]", "pull the info from [email]", "process the email in inbox"
+- =send-email.org= — compose and send an email.
+ - Triggers: "send an email", "email workflow", "email [person] about [topic]", "send [file] to [person]"
+- =email-assembly.org= — gather documents into an email package.
+ - Triggers: "assemble an email", "email assembly workflow", "gather documents for an email", "I need to send [person] some documents"
+
+** Tools and meta
+
+- =process-meeting-transcript.org= — record → transcript → labeled archive.
+ - Triggers: "process the transcript", "process the recording". Auto: new files in =~/sync/recordings/=.
+- =page-me.org= — set a timed notification.
+ - Triggers: anything containing the word "page" used as a verb ("page me", "page me in 10 minutes", "page me at 3pm")
+- =status-check.org= — proactive long-running-job updates.
+ - Triggers: "keep me posted on this", "provide status checks on this job", "let me know when it's done", "monitor this for me". Auto: any job estimated 10+ min.
+- =create-workflow.org= — define a new workflow.
+ - Triggers: "let's create/define/design a workflow for [activity]", or unmatched workflow request after this index returns no hit.
+- =cross-agent-comms.org= — protocol for cross-project agent coordination via =inbox/from-agents/= (file-based IPC, GPG-signed, supports cross-machine over Tailscale). Auto: when =cross-agent-watch= detects a new inbound message, or when an agent decides to initiate a cross-project conversation. Operational scripts (=cross-agent-send=, =-recv=, =-watch=, =-status=, =-discover=, =-halt=, =-resume=) and their READMEs live at =.ai/scripts/cross-agent-comms/=.
+
+* Living Document
+
+Add a row when a new workflow lands in =.ai/workflows/=. Remove the row when a workflow is deleted. Update triggers when a workflow's "When to Use" section changes. The startup drift check is the safety net — it catches forgotten updates but doesn't substitute for keeping this file current.
diff --git a/.ai/workflows/add-calendar-event.org b/.ai/workflows/add-calendar-event.org
new file mode 100644
index 0000000..2650fb7
--- /dev/null
+++ b/.ai/workflows/add-calendar-event.org
@@ -0,0 +1,190 @@
+#+TITLE: Add Calendar Event Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-02-01
+
+* Overview
+
+Workflow for creating calendar events. Uses the Google Calendar MCP server (preferred) or gcalcli (fallback, personal account only).
+
+* Triggers
+
+- "create an event"
+- "add appointment"
+- "schedule a meeting"
+- "add to my calendar"
+- "calendar event for..."
+
+* Prerequisites
+
+- Google Calendar MCP server configured and authenticated (=@cocal/google-calendar-mcp=)
+- Two accounts available: =personal= (Craig Google) and =work= (Craig Deepsat)
+- Fallback: gcalcli installed (personal account only)
+
+* CRITICAL: Check All Calendars Before Scheduling
+
+Before creating any event, ALWAYS check for conflicts across ALL calendars. Use the MCP =get-freebusy= tool to check availability, or query multiple sources:
+
+1. *MCP server* — check both personal and work accounts via =list-events= or =get-freebusy=
+2. *Emacs org files* — for Proton calendar (not accessible via MCP or gcalcli):
+
+#+begin_src bash
+grep "2026-02-18" ~/.emacs.d/data/pcal.org # Proton calendar
+#+end_src
+
+Always verify the time slot is free across all calendars before creating.
+
+* Workflow Steps
+
+** 1. Parse Natural Language Input
+
+Interpret the user's request to extract:
+- Event title
+- Date/time (natural language like "tomorrow 3pm", "next Tuesday at 2")
+- Any mentioned location
+- Any mentioned description
+
+Examples:
+- "Create an event tomorrow at 5pm called Grocery Shopping"
+- "Add a meeting with Bob on Friday at 10am"
+- "Schedule dentist appointment next Wednesday at 2pm at Downtown Dental"
+
+** 2. Apply Defaults
+
+| Field | Default Value |
+|------------+----------------------------------|
+| Calendar | Craig (default Google Calendar) |
+| Reminders | 5 minutes before, at event time |
+| Duration | NONE - always ask user |
+| Location | None (optional) |
+
+** 3. Gather Missing Information
+
+*Always ask for:*
+- Duration (required, no default)
+
+*Ask if relevant:*
+- Location (if not provided and seems like an in-person event)
+
+*Never assume:*
+- Duration - this must always be explicitly confirmed
+
+** 4. Show Event Summary
+
+Present the event in plain English (NOT the gcalcli command):
+
+#+begin_example
+Event: Grocery Shopping
+When: Tomorrow (Feb 2) at 5:00 PM
+Duration: 1 hour
+Location: (none)
+Reminders: 5 min before, at event time
+Calendar: Personal
+#+end_example
+
+** 5. Explicit Confirmation
+
+Ask: "Create this event? (yes/no)"
+
+*Do NOT create the event until user confirms.*
+
+** 6. Execute
+
+Once confirmed, create the event.
+
+*** MCP (preferred)
+
+Use the =create-event= MCP tool:
+- =account_id=: "personal" (default) or "work"
+- =calendar_id=: calendar name or ID (default: primary)
+- =summary=: event title
+- =start=, =end=: ISO 8601 datetime (e.g., "2026-02-15T14:00:00-06:00")
+- =location=: location string (optional)
+- =description=: event notes (optional)
+- =reminders=: custom reminders (optional)
+
+*** gcalcli (fallback, personal account only)
+
+#+begin_src bash
+gcalcli --calendar "Calendar Name" add \
+ --title "Event Title" \
+ --when "date and time" \
+ --duration MINUTES \
+ --where "Location" \
+ --description "Description" \
+ --reminder 5 \
+ --reminder 0 \
+ --noprompt
+#+end_src
+
+** 7. Verify
+
+Confirm the event was created:
+
+*** MCP
+Use =search-events= or =list-events= to verify.
+
+*** gcalcli (fallback)
+#+begin_src bash
+gcalcli --calendar "Calendar Name" search "Event Title"
+#+end_src
+
+Report success or failure to user.
+
+* Calendars
+
+| Calendar | Access | Account | Notes |
+|---------------------------+--------+----------+--------------------------------|
+| Craig Google | owner | personal | Default — use for most events |
+| Christine | owner | personal | Christine's calendar |
+| Craig Deepsat | owner | work | DeepSat work calendar |
+| Todoist | owner | personal | Todoist integration |
+| Craig Jennings (TripIt) | reader | personal | View only, no create |
+| Holidays in United States | reader | personal | View only |
+| Craig Proton | reader | personal | View only (no API access) |
+
+* Time Formats
+
+MCP tools use ISO 8601: =2026-02-15T14:00:00-06:00=
+
+gcalcli accepts natural language times:
+- "tomorrow 3pm"
+- "next Tuesday at 2"
+- "2026-02-15 14:00"
+- "Feb 15 2pm"
+- "today 5pm"
+
+* Duration
+
+MCP uses explicit start/end times (no duration field — calculate end time from start + duration).
+
+gcalcli duration shortcuts:
+
+| Input | Minutes |
+|--------+---------|
+| 30m | 30 |
+| 1h | 60 |
+| 1.5h | 90 |
+| 2h | 120 |
+| 90 | 90 |
+
+* Error Handling
+
+** MCP Authentication Error
+Use =manage-accounts= MCP tool with =action: "add"= and the account nickname to re-authenticate.
+
+** gcalcli Authentication Error
+Run =gcalcli init= to re-authenticate.
+
+** Calendar Not Found
+MCP: Use =list-calendars= to see available calendars.
+gcalcli: Use =gcalcli list=.
+
+** Invalid Time Format
+MCP: Use ISO 8601 format: =YYYY-MM-DDTHH:MM:SS±HH:MM=
+gcalcli: Use explicit date format: =YYYY-MM-DD HH:MM=
+
+* Related
+
+- [[file:read-calendar-events.org][Read Calendar Events]] - view events
+- [[file:edit-calendar-event.org][Edit Calendar Event]] - modify events
+- [[file:delete-calendar-event.org][Delete Calendar Event]] - remove events
diff --git a/.ai/workflows/create-workflow.org b/.ai/workflows/create-workflow.org
new file mode 100644
index 0000000..e6587c8
--- /dev/null
+++ b/.ai/workflows/create-workflow.org
@@ -0,0 +1,360 @@
+#+TITLE: Creating New Workflows
+#+AUTHOR: Craig Jennings
+#+DATE: 2025-11-01
+
+* Overview
+
+This document describes the meta-workflow for creating new workflows. When we identify a repetitive workflow or collaborative pattern, we use this process to formalize it into a documented workflow that we can reference and reuse.
+
+Note the definitions: "sessions" are the time Claude spends with the user, i.e., the user starts a "session" with Claude, does some work, then ends the "session". A workflow is a routine or pattern of doing tasks within a session to accomplish a goal.
+
+Workflows are living documents that capture how we work together on specific types of tasks. They build our shared vocabulary and enable efficient collaboration across multiple work sessions.
+
+* Problem We're Solving
+
+Without a formal workflow creation process, we encounter several issues:
+
+** Inefficient Use of Intelligence
+- Craig leads the process based solely on his knowledge
+- We don't leverage Claude's expertise to improve or validate the approach
+- Miss opportunities to apply software engineering and process best practices
+
+** Time Waste and Repetition
+- Craig must re-explain the workflow each time we work together
+- No persistent memory of how we've agreed to work
+- Each session starts from scratch instead of building on previous work
+
+** Error-Prone Execution
+- Important steps may be forgotten or omitted
+- No checklist to verify completeness
+- Mistakes lead to incomplete work or failed goals
+
+** Missed Learning Opportunities
+- Don't capture lessons learned from our collaboration
+- Can't improve processes based on what works/doesn't work
+- Lose insights that emerge during execution
+
+** Limited Shared Vocabulary
+- No deep, documented understanding of what terms mean
+- "Let's do a refactor workflow" has no precise definition
+- Can't efficiently communicate about workflows
+
+*Impact:* Inefficiency, errors, and lost opportunity to continuously improve our collaborative workflows.
+
+* Exit Criteria
+
+We know a workflow definition is complete when:
+
+1. **Information is logically arranged** - The structure makes sense and flows naturally
+2. **Both parties understand how to work together** - We can articulate the workflow
+3. **Agreement on effectiveness** - We both agree that following this workflow will lead to exit criteria and resolve the stated problem
+4. **Tasks are clearly defined** - Steps are actionable, not vague
+5. **Problem resolution path** - Completing the tasks either:
+ - Fixes the problem permanently, OR
+ - Provides a process for keeping the problem at bay
+
+*Measurable validation:*
+- Can we both articulate the workflow without referring to the document?
+- Do we agree it will solve the problem?
+- Are the tasks actionable enough to start immediately?
+- Does the workflow get used soon after creation (validation by execution)?
+
+* When to Use This Workflow
+
+Trigger this workflow creation workflow when:
+
+- You notice a repetitive workflow that keeps coming up
+- A collaborative pattern emerges that would benefit from documentation
+- Craig says "let's create/define/design a workflow for [activity]"
+- You identify a new type of work that doesn't fit existing workflows
+- An existing workflow needs significant restructuring (treat as creating a new one)
+
+Examples:
+- "Let's create a workflow where we inbox zero"
+- "We should define a code review workflow"
+- "Let's design a workflow for weekly planning"
+
+* Approach: How We Work Together
+** Phase 0: Context Hygiene
+
+Before starting, write out the session context file and check with Craig whether we could compact the context. This might be a long process. If the context window collapses, we may forget important details. Writing out the session context prevents this data loss.
+
+** Phase 1: Question and Answer Discovery
+
+Walk through these four core questions collaboratively. Take notes on the answers.
+
+*IMPORTANT: Save answers as you go!*
+
+The Q&A phase can take time—Craig may need to think through answers, and discussions can be lengthy. To prevent data loss from terminal crashes or process quits:
+
+1. Create a draft file at =.ai/workflows/[name]-draft.org= after deciding on the name
+2. After each question is answered, save the Q&A content to the draft file
+3. If workflow is interrupted, you can resume from the saved answers
+4. Once complete, the draft becomes the final workflow document
+
+This protects against losing substantial thinking work if the workflow is interrupted.
+
+*** Question 1: What problem are we solving in this type of workflow?
+
+Ask Craig: "What problem are we solving in this type of workflow? What would happen without this workflow?"
+
+The answer reveals:
+- Overview and goal of the workflow
+- Why this work matters (motivation)
+- Impact/priority compared to other work
+- What happens if we don't do this work
+
+Example from refactor workflow:
+#+begin_quote
+"My Emacs configuration isn't resilient enough. There's lots of custom code, and I'm even developing some as Emacs packages. Yet Emacs is my most-used software, so when Emacs breaks, I become unproductive. I need to make Emacs more resilient through good unit tests and refactoring."
+#+end_quote
+
+*** Question 2: How do we know when we're done?
+
+Ask Craig: "How do we know when we're done?"
+
+The answer reveals:
+- Exit criteria
+- Results/completion criteria
+- Measurable outcomes
+
+*Your role:*
+- Push back if the answer is vague or unmeasurable
+- Propose specific measurements based on context
+- Iterate together until criteria are clear
+- Fallback (hopefully rare): "when Craig says we're done"
+
+Example from refactor workflow:
+#+begin_quote
+"When we've reviewed all methods, decided which to test and refactor, run all tests, and fixed all failures including bugs we find."
+#+end_quote
+
+Claude might add: "How about a code coverage goal of 70%+?"
+
+*** Question 3: How do you see us working together in this kind of workflow?
+
+Ask Craig: "How do you see us working together in this kind of workflow?"
+
+The answer reveals:
+- Steps or phases we'll go through
+- The general approach to the work
+- How tasks flow from one to another
+
+*Your role:*
+- As steps emerge, ask yourself:
+ - "Do these steps lead to solving the real problem?"
+ - "What is missing from these steps?"
+- If the answers aren't "yes" and "nothing", raise concerns
+- Propose additions based on your knowledge
+- Suggest concrete improvements
+
+Example from refactor workflow:
+#+begin_quote
+"We'll analyze test coverage, categorize functions by testability, write tests systematically using Normal/Boundary/Error categories, run tests, analyze failures, fix bugs, and repeat."
+#+end_quote
+
+Claude might suggest: "Should we install a code coverage tool as part of this process?"
+
+*** Question 4: Are there any principles we should be following while doing this?
+
+Ask Craig: "Are there any principles we should be following while doing this kind of workflow?"
+
+The answer reveals:
+- Principles to follow
+- Decision frameworks
+- Quality standards
+- When to choose option A vs option B
+
+*Your role:*
+- Think through all elements of the workflow
+- Consider situations that may arise
+- Identify what principles would guide decisions
+- Suggest decision frameworks from your knowledge
+
+Example from refactor workflow:
+#+begin_quote
+Craig: "Treat all test code as production code - same engineering practices apply."
+
+Claude suggests: "Since we'll refactor methods mixing UI and logic, should we add a principle to separate them for testability?"
+#+end_quote
+
+** Phase 2: Assess Completeness
+
+After the Q&A, ask together:
+
+1. **Do we have enough information to formulate steps/process?**
+ - If yes, proceed to Phase 3
+ - If no, identify what's missing and discuss further
+
+2. **Do we agree following this approach will resolve/mitigate the problem?**
+ - Both parties must agree
+ - If not, identify concerns and iterate
+
+** Phase 3: Name the Workflow
+
+Decide on a name for this workflow.
+
+*Naming convention:* Action-oriented (verb form)
+- Examples: "refactor", "inbox-zero", "create-workflow", "review-code"
+- Why: Shorter, natural when saying "let's do a [name] workflow"
+- Filename: =.ai/workflows/[name].org=
+
+** Phase 4: Document the Workflow
+
+Write the workflow file at =.ai/workflows/[name].org= using this structure:
+
+*** Recommended Structure
+1. *Title and metadata* (=#+TITLE=, =#+AUTHOR=, =#+DATE=)
+2. *Overview* - Brief description of the workflow
+3. *Problem We're Solving* - From Q&A, with context and impact
+4. *Exit Criteria* - Measurable outcomes, how we know we're done
+5. *When to Use This Workflow* - Triggers, circumstances, examples
+6. *Approach: How We Work Together*
+ - Phases/steps derived from Q&A
+ - Decision frameworks
+ - Concrete examples woven throughout
+7. *Principles to Follow* - Guidelines from Q&A
+8. *Living Document Notice* - Reminder to update with learnings
+
+*** Important Notes
+- Weave concrete examples into sections (don't separate them)
+- Use examples from actual workflows when available
+- Make tasks actionable, not vague
+- Include decision frameworks for common situations
+- Note that this is a living document
+
+** Phase 5: Update Project State
+
+Update =notes.org=:
+1. Add new workflow to "Available Workflows" section
+2. Include brief description and reference to file
+3. Note creation date
+
+Example entry:
+#+begin_src org
+,** inbox-zero
+File: =.ai/workflows/inbox-zero.org=
+
+Workflow for processing inbox to zero:
+1. [Brief workflow summary]
+2. [Key steps]
+
+Created: 2025-11-01
+#+end_src
+
+** Phase 6: Cleanup
+Write out the session context file before proceeding any further
+
+** Phase 7: Validate by Execution
+
+*Critical step:* Use the workflow soon after creating it.
+
+- Schedule the workflow for immediate use
+- Follow the documented workflow
+- Note what works well
+- Identify gaps or unclear areas
+- Update the workflow document with learnings
+
+*This validates the workflow definition and ensures it's practical, not theoretical.*
+
+* Principles to Follow
+
+These principles guide us while creating new workflows:
+
+** Collaboration Through Discussion
+- Be proactive about collaboration
+- Suggest everything on your mind
+- Ask all relevant questions
+- Push back when something seems wrong, inconsistent, or unclear
+- Misunderstandings are learning opportunities
+
+** Reviewing the Whole as Well as the Pieces
+- May get into weeds while identifying each step
+- Stop to look at the whole thing at the end
+- Ask the big questions: Does this actually solve the problem?
+- Verify all pieces connect logically
+
+** Concrete Over Abstract
+- Use examples liberally within explanations
+- Weave concrete examples into Q&A answers
+- Don't just describe abstractly
+- "When nil input crashes, ask..." is better than "handle edge cases"
+
+** Actionable Tasks Over Vague Direction
+- Steps should be clear enough to know what to do next
+- "Ask: how do you see us working together?" is actionable
+- "Figure out the approach" is too vague
+- Test: Could someone execute this without further explanation?
+
+** Validate Early
+- "Use it soon afterward" catches problems early
+- Don't let workflow definitions sit unused and untested
+- Real execution reveals gaps that theory misses
+- Update immediately based on first use
+
+** Decision Frameworks Over Rigid Steps
+- Workflows are frameworks (principles + flexibility), not recipes
+- Include principles that help case-by-case decisions
+- "When X happens, ask Y" is a decision framework
+- "Always do X" is too rigid for most workflows
+
+** Question Assumptions
+- If something doesn't make sense, speak up
+- If a step seems to skip something, point it out
+- Better to question during creation than discover gaps during execution
+- No assumption is too basic to verify
+
+* Living Document
+
+This is a living document. As we create new workflows and learn what works (and what doesn't), we update this file with:
+
+- New insights about workflow creation
+- Improvements to the Q&A process
+- Better examples
+- Additional principles discovered
+- Refinements to the structure
+
+Every time we create a workflow, we have an opportunity to improve this meta-process.
+
+** Updates and Learnings
+
+*** 2025-11-01: Save Q&A answers incrementally
+*Learning:* During emacs-inbox-zero workflow creation, we discovered that Q&A discussions can be lengthy and make Craig think deeply. Terminal crashes or process quits can lose substantial work.
+
+*Improvement:* Added guidance in Phase 1 to create a draft file and save Q&A answers after each question. This protects against data loss and allows resuming interrupted workflows.
+
+*Impact:* Reduces risk of losing 10-15 minutes of thinking work if workflow is interrupted.
+
+*** 2025-11-01: Validation by execution works!
+*Learning:* Immediately after creating the emacs-inbox-zero workflow, we validated it by actually running the workflow. This caught unclear areas and validated that the 10-minute target was realistic.
+
+*Key insight from validation:* When Craig provides useful context during workflows (impact estimates, theories, examples), that context should be captured in task descriptions. This wasn't obvious during workflow creation but became clear during execution.
+
+*Impact:* Validation catches what theory misses. Always use Phase 6 (validate by execution) soon after creating a workflow.
+
+* Example: Creating the "Create-Workflow" Workflow
+
+This very document was created using the process it describes (recursive!).
+
+** The Q&A
+- *Problem:* Time waste, errors, missed learning from informal processes
+- *Exit criteria:* Logical arrangement, mutual understanding, agreement on effectiveness, actionable tasks
+- *Approach:* Four-question Q&A, assess completeness, name it, document it, update notes.org, validate by use
+- *Principles:* Collaboration through discussion, review the whole, concrete over abstract, actionable tasks, validate early, decision frameworks, question assumptions
+
+** The Result
+We identified what was needed, collaborated on answers, and captured it in this document. Then we immediately used it to create the next workflow (validation).
+
+* Conclusion
+
+Creating workflows is a meta-skill that improves all our collaboration. By formalizing how we work together, we:
+
+- Build shared vocabulary
+- Eliminate repeated explanations
+- Capture lessons learned
+- Enable continuous improvement
+- Make our partnership more efficient
+
+Each new workflow we create adds to our collaborative toolkit and deepens our ability to work together effectively.
+
+*Remember:* Workflows are frameworks, not rigid recipes. They provide structure while allowing flexibility for case-by-case decisions. The goal is effectiveness, not perfection.
diff --git a/.ai/workflows/cross-agent-comms.org b/.ai/workflows/cross-agent-comms.org
new file mode 100644
index 0000000..ccf1739
--- /dev/null
+++ b/.ai/workflows/cross-agent-comms.org
@@ -0,0 +1,334 @@
+#+TITLE: Cross-Agent Communication Workflow (v5)
+#+AUTHOR: Craig Jennings & Claude (homelab + career sessions)
+#+DATE: 2026-04-27
+#+VERSION: 5
+
+* Status
+
+Draft. Iterating between the homelab and career sessions through a multi-round design discussion. Awaiting Craig's review for promotion to =~/projects/claude-templates/.ai/workflows/=.
+
+v5 changes from v4:
+- *Script absorption.* Seven operational scripts (=cross-agent-send=, =cross-agent-recv=, =cross-agent-watch=, =cross-agent-status=, =cross-agent-discover=, =cross-agent-halt=, =cross-agent-resume=) now own most implementation detail. Their READMEs are the operational source of truth. The spec stays declarative.
+- *Failsafe halt.* Layered HALT-file mechanism stops all cross-agent activity on a machine within ~5 min, without visiting individual sessions or restarting Claude Code. =cross-agent-halt= and =cross-agent-resume= are the convenience entry points; every other component checks the HALT file independently.
+- *Identity.* Messages are GPG-signed by sender and verified by receiver. Combined with POSIX permissions on =from-agents/= and Tailscale-level network auth, identity becomes a three-layer story.
+- *Atomic writes.* Writers MUST use temp-file + rename. =cross-agent-send= handles this; the spec just states the contract.
+- *Dedup.* Sequence-collision dedup is now binary SHA-256 equality, not a fuzzy ">90% match" threshold.
+- *Cold-start handling.* Layered: =cross-agent-watch= (push notifications via =inotifywait=) is the primary mechanism; startup-workflow check and user-direct-injection are coverage layers.
+- *Spec stays roughly the same length but does more protocol work.* Operational detail (rsync retry numbers, inotifywait recipes, peers.toml schema, GPG flags, dedup mechanics) moved to the script READMEs. The spec adds new protocol elements (identity layer, atomic-writes contract, SHA-256 dedup, =escalate= type, =RELEASE_STATUS= values, =REQUIRES_TOOLS= optional field) in the freed space. Total documentation surface (spec + seven READMEs ≈ 1000 lines) is larger than v4's 259 lines, but the spec and the READMEs serve different audiences — protocol-thinkers and CLI-users — and a reader of just the spec can comprehend the protocol without consulting any README.
+
+* When to use
+
+When two Claude sessions in different projects (same machine or different machines on the same Tailscale tailnet) need to coordinate on a shared task that one session can't complete alone — typically because one has tooling, context, or MCP access the other doesn't.
+
+Examples that fit:
+- Session A asks session B to apply a workflow patch in B's project, then verify it.
+- Session A runs a long task and needs session B to monitor results in B's domain.
+- Two sessions co-design a workflow.
+
+Examples that don't fit:
+- A simple file handoff that doesn't require iteration.
+- A task one session can do alone.
+- Cross-tailnet or cross-organization. The protocol is local-tailnet-scoped.
+
+* Protocol
+
+** File location
+
+Each project has =inbox/from-agents/= as its agent-comms mailbox. Create the directory if it doesn't exist; set permissions =chmod 700= and ownership to the user.
+
+- Sender writes to receiver's =inbox/from-agents/=.
+- Receiver polls (or watches) =inbox/from-agents/=, *not* the parent =inbox/=.
+- The parent =inbox/= stays reserved for human-triage items.
+- Out-of-band artifacts (PDFs, datasets) live at =inbox/from-agents/artifacts/=. Reference by relative path in the message body.
+
+The user does NOT write directly to =from-agents/=. To inject input into a running conversation, the user tells one of the agents in that agent's session; the agent writes the input as a normal message attributed to the user.
+
+** File naming
+
+=YYYYMMDDTHHMMSSZ-from-<sender>-<short-conv-id>.org=
+
+- Timestamp is UTC ISO 8601 compact. The trailing =Z= is mandatory.
+- =from-<sender>= prefix.
+- =<short-conv-id>= is a stable kebab-case slug across the back-and-forth. Reusable across time; ordering relies on filename timestamps.
+
+Frontmatter =#+TIMESTAMP= carries the same instant in local time with explicit offset. The two MUST refer to the same instant.
+
+The implementation (=cross-agent-send=) generates the canonical filename from the message's frontmatter (=CONVERSATION_ID=, current UTC time) and the sender's project context. Senders supply only the message body file; the script handles naming. Senders MUST NOT pre-name files in this format and pass them through; the script overwrites with its own canonical name to ensure consistency and enable the sender-side max-seen sequence-collision-reduction scan.
+
+GPG signatures live in a sibling file =YYYYMMDDTHHMMSSZ-from-<sender>-<short-conv-id>.org.asc=. Receivers verify before processing. See =* Writes are atomic= for the two-file delivery ordering rule.
+
+** Frontmatter
+
+Required:
+
+#+begin_example
+#+TITLE: <human-readable subject>
+#+CONVERSATION_ID: <stable across the thread>
+#+MESSAGE_TYPE: <see types below>
+#+SEQUENCE: <integer hint>
+#+TIMESTAMP: <ISO 8601 with explicit offset>
+#+PROTOCOL_VERSION: 5
+#+end_example
+
+Optional:
+
+#+begin_example
+#+REQUIRES_TOOLS: <comma-separated tool/MCP slugs, e.g. gmail-mcp, slack-mcp>
+#+RELEASE_STATUS: <see release-statuses; valid only on MESSAGE_TYPE: release>
+#+WORKFLOW_VERSION: <sender's version of cross-agent-comms.org; informational only in v5 — no enforcement>
+#+end_example
+
+Receiver sanity-checks frontmatter before acting. Missing or malformed frontmatter → surface to user, don't proceed. Mismatched =PROTOCOL_VERSION= → receiver writes a =query= asking the originator to upgrade.
+
+** Identity
+
+Messages are GPG-signed by the sender. Receivers verify the detached signature before processing the message body.
+
+The implementation (=cross-agent-send=) signs automatically with the sender's configured key (the user's primary GPG key by default; configurable via =--key= flag or environment). Receivers verify automatically against the keys in their GPG keyring.
+
+Identity is a three-layer story:
+
+1. *Tailscale layer.* Only tailnet members can reach the rsync-over-SSH endpoint at all.
+2. *POSIX layer.* =chmod 700= on =from-agents/= means only processes running as the directory's owner can write.
+3. *GPG layer.* Sender's signature on each message proves the message originated from a process holding the key.
+
+Three independent layers. Per-user GPG (using existing keys) gives a correctness check more than a security boundary — unsigned messages are almost certainly bugs, not attackers. That's still load-bearing.
+
+** Writes are atomic
+
+Writers MUST use a temp-file + rename pattern (=mktemp= + =mv= within the same filesystem) so receivers never see partial files. The implementation script (=cross-agent-send=) handles this.
+
+Receivers ignore =.tmp.*= files, processing only the final renamed name.
+
+*Two-file ordering.* When a message has a sibling GPG signature file (=.org.asc=), the writer MUST rename the =.asc= to its final name *before* renaming the =.org=. Two =mv= operations are not atomic together — without this ordering, a receiver could read the =.org= in the window between the two renames and fail GPG verify because the =.asc= hasn't landed yet. The rule: receiver only acts on =.org= files, and a =.org= without a corresponding =.asc= means the signature is genuinely missing (not still in flight).
+
+** Sequence numbering
+
+=#+SEQUENCE= is a *hint*, not a strict counter. Canonical order is =#+TIMESTAMP=. Sequences may collide under rapid back-and-forth (both sides write what they think is sequence N near-simultaneously). Treat collision as a normal protocol event.
+
+*Receiver-side dedup rule.* When a new file shares =CONVERSATION_ID= + =SEQUENCE= with an already-processed message, compare SHA-256 hashes. Identical hashes → silent dedup, treat as a retry. Different hashes → process both, ordered by =#+TIMESTAMP=.
+
+*Sender-side collision-reduction (best-effort).* Before picking sequence, scan the receiver's =from-agents/= for the highest existing sequence in this conversation across both sender prefixes. Use =max(seen) + 1=.
+
+** Message types
+
+- *request* — a side asks for work, input, or a decision. Sequence 1 is always =request=.
+- *progress* — work-in-progress checkpoint. "Here's where I am, no action needed from you, more coming." Originator's poll loop should NOT page the user on progress messages.
+- *query* — either side asks a clarifying question that blocks further work. Originator's poll loop SHOULD surface this immediately. Originator answers and work continues.
+- *pushback* — receiver formally disagrees with the request and has *not* started the work. Carries reasoning. Distinct from =query= because the originator's response path differs.
+- *complete* — receiver signals the requested work is done. Triggers verification.
+- *release* — terminal type. Originator writes after verifying =complete=. Carries =RELEASE_STATUS= to disambiguate the closure mode.
+- *escalate* — punts the conversation to the user for adjudication. Both sides pause polling on =escalate=; the user resolves.
+
+Reply expectation is implied by type: =request=, =query=, =pushback=, =escalate= expect a reply; =progress=, =complete=, =release= don't.
+
+** Conversation lifecycle
+
+A conversation is a directed loop between an originator (issued sequence 1) and a receiver:
+
+1. Originator writes =request= (sequence 1). Begins polling for replies.
+2. *Optional acknowledgment.* Receiver may write a =progress= at sequence 2 to acknowledge receipt and set expectations. Required if work will take >5 minutes (so the originator's poll loop doesn't waste wakes).
+3. *Optional echo-back.* For ambiguous or large requests, receiver writes a =progress= that restates work items and announces "starting now unless you push back within N minutes."
+4. Receiver works. May write =progress= updates. =query= mid-work if blocked. =pushback= if the request is wrong.
+5. Receiver writes =complete=. Begins polling for =release=.
+6. Originator reads, *verifies the deliverable directly*. For subjective deliverables, verification is the originator's editorial accept.
+7. If verified: =release= with =RELEASE_STATUS: complete=. If problems: new =request= (next sequence number).
+8. Receiver sees =release=, stops polling.
+
+The verification step is load-bearing. =complete= is a *claim*; =release= is *verification*.
+
+** Pushback path
+
+On receiving a =pushback=, the originator chooses:
+
+1. *Revise* — new =request= with adjusted scope.
+2. *Insist* — new =request= addressing the pushback's reasoning, standing by direction.
+3. *Withdraw* — =release= with =RELEASE_STATUS: withdrawn-after-pushback=.
+
+*Deadlock cap.* After two pushback-insist exchanges, the next message MUST be =MESSAGE_TYPE: escalate=. Both agents pause polling; the user resolves.
+
+** =RELEASE_STATUS= values
+
+| Status | Meaning |
+|---+---|
+| =complete= | Goal achieved, originator verified |
+| =cancelled= | Originator changed their mind mid-conversation |
+| =withdrawn-after-pushback= | Originator chose option 3 on receiver's =pushback= |
+| =abandoned-after-escalation= | User adjudicated and chose to close the conversation |
+| =abandoned-after-timeout= | Receiver auto-closed after originator never returned to verify |
+
+** Async fallback
+
+If the originator session ends between =request= and =complete=, the receiver's =complete= goes unverified. Receiver behavior:
+
+- Polls for =release= up to ~24 hours of cycles (implementation default).
+- After timeout, writes a final =progress= message ("treating as terminal-without-verification; originator never returned to release") and stops polling. Receiver does NOT write =release= itself — that would contradict the lifecycle rule that =release= is the originator's terminal action.
+- Next time the originator project starts, the unreleased =complete= is surfaced as a startup item. The user can issue a late =release= (with whichever =RELEASE_STATUS= fits) or open a fresh conversation to revisit. =RELEASE_STATUS: abandoned-after-timeout= is used at that point if the user wants to formally close the orphaned thread.
+
+** Escalation
+
+A side writes =escalate= when:
+- Pushback-insist deadlock cap reached.
+- Conversation has stalled (no productive movement in N exchanges).
+- A reply-expecting message has gone unanswered past timeout.
+
+Body summarizes both sides' positions in 60 seconds of reading. Both agents pause polling; the user resolves.
+
+* Implementation notes
+
+This sub-section describes how to operate the protocol. Operational detail lives in the seven scripts' READMEs.
+
+** Recommended scripts
+
+| Script | Replaces user action | README |
+|---+---+---|
+| =cross-agent-send <dest> <msg>= | Filename generation, GPG sign, atomic write, peer lookup, rsync push, retry+backoff, failure surfacing — seven mechanical sender-side steps. Frontmatter and message body are still author-supplied. | =cross-agent-send.md= |
+| =cross-agent-recv <msg>= | Frontmatter sanity-check, =PROTOCOL_VERSION= verify, GPG verify, SHA-256 dedup, =REQUIRES_TOOLS= check — five mechanical receiver-side steps. Output is a structured decision (=process= / =dedup= / =query= / =reject=) the agent acts on. | =cross-agent-recv.md= |
+| =cross-agent-watch= | Manually checking inboxes; "did I get a message?" | =cross-agent-watch.md= |
+| =cross-agent-status= | Walking each project to count pending messages | =cross-agent-status.md= |
+| =cross-agent-discover= | Remembering project topology and reachability | =cross-agent-discover.md= |
+| =cross-agent-halt [reason] [--tailnet]= | Visiting each session to stop polling, restarting Claude Code, or hand-killing processes when comms go runaway. =--tailnet= propagates HALT to all peers. | =cross-agent-halt.md= |
+| =cross-agent-resume [--tailnet]= | Manually clearing the HALT state and restarting the watcher. Per-session polling does NOT auto-resume — the user re-engages each session explicitly. | =cross-agent-resume.md= |
+
+The scripts are tools the user runs from any terminal. They do not depend on agent context — =cross-agent-status= run from a fresh shell works.
+
+A reader can comprehend this protocol from this spec alone. Script READMEs add operational detail that makes the protocol practical to use, but understanding the protocol's semantics requires only this document.
+
+** Polling
+
+Default cadence: 270 seconds (≈4.5 min). Sits just under the 5-minute prompt-cache TTL.
+
+If a side needs to slow down (heads-down work, idle wait), it writes a =progress= message saying so in prose. The other side adapts. There are no named polling modes.
+
+After ~12 empty polls in a row, the poll loop surfaces the silence to the user.
+
+A future runtime with native filesystem-event support could replace polling for active sessions; =cross-agent-watch= already provides event-driven notifications outside active sessions.
+
+** User multi-tasking
+
+- *Deferral.* If the user's last message in the agent's session was less than 60 seconds ago AND a poll fires, queue the inbox check until either the user sends another message OR 5 minutes pass without further input.
+- *Surfacing.* On the next user-facing response: "While we were working on X, a cross-agent message landed from <project>. It's a =<type>= — want me to handle it now or after we finish?"
+- *Mid-question.* Answer the user first.
+- *Project switch.* If the user moves to the receiver project mid-conversation, the receiver agent surfaces the in-flight thread on first user prompt.
+- *Conversation state.* Always include in any response that mentions a cross-agent thread: "<conv-id> at sequence N, awaiting <event>."
+
+** Failure modes
+
+The seven scripts surface most failures with concrete error messages. Spec-level failure modes:
+
+- *Malformed frontmatter on a received file.* Surface to user; do not act.
+- *Mismatched =PROTOCOL_VERSION=.* Receiver writes =query= asking originator to upgrade.
+- *Missing or invalid GPG signature.* Receiver surfaces "unsigned/unverified message"; refuses to act.
+- *Sequence collision* with non-matching SHA-256. Process both, ordered by timestamp.
+- *Required tool unavailable.* Receiver checks =REQUIRES_TOOLS= during frontmatter-sanity-check (before any work begins). On a missing tool, receiver writes =query= asking the originator to reframe the request to avoid the unavailable tool. Originator may revise (new =request=) or withdraw (=release= with =RELEASE_STATUS: cancelled=). =query= is the right type rather than =pushback= because missing-tool is a capability gap, not disagreement.
+- *Runaway resource usage.* User invokes =cross-agent-halt= globally (or =cross-agent-halt --tailnet= for cross-machine). HALT file stops all components within one polling cycle (~5 min). See =* Halt mechanism= for the layered checks.
+- *User halts mid-conversation.* Both sides write a final =progress= note ("HALT fired; pausing"); polling stops within one cadence; conversations resume on explicit per-session re-engage after HALT clears.
+- *HALT file accidentally created* (typo, errant =touch=). =cross-agent-status= prominently flags HALT active; user clears with =cross-agent-resume=. Cost: no messages send during the typo window.
+- *HALT file unreadable* (perms wrong, partial write). Each component fails-closed (treats as halted) and reports "HALT file present but unreadable; treat as halted." Safer than fail-open.
+
+Operational failures (rsync push fails, watcher dies, peer unreachable) live in the script READMEs' failure-mode tables.
+
+* Halt mechanism
+
+A failsafe to stop all cross-agent activity on a machine without visiting individual sessions or restarting Claude Code. Designed for the runaway-polling case: an agent has spun up conversations with N other agents, polling is eating CPU, and the user needs to stop everything *now*.
+
+** The HALT file
+
+Path: =~/.config/cross-agent-comms/HALT=.
+
+Existence triggers halt across all components on the machine. The file's body may carry an optional human-readable reason (reviewed by the user later when deciding to resume).
+
+User commands:
+
+#+begin_example
+$ touch ~/.config/cross-agent-comms/HALT # halt
+$ rm ~/.config/cross-agent-comms/HALT # resume
+#+end_example
+
+Or via convenience scripts (=cross-agent-halt= / =cross-agent-resume=) that also handle the watcher service and cross-machine propagation.
+
+** Layered checks (the failsafe property)
+
+Every component MUST check the HALT file. The "any one component stops the system independently" property is what makes this failsafe — the system doesn't depend on a single point doing the right thing.
+
+| Component | Check timing | Behavior on HALT |
+|---+---+---|
+| =cross-agent-send= | At start of send + between =.asc= and =.org= rsync + between retry iterations | Refuse to start new send; complete current step then exit. Worst case: one in-flight send finishes within a few seconds. |
+| =cross-agent-recv= | Before any verify or dedup | Leave inbound message in place — do NOT dedup, reject, or move. Resume picks it up via cold-start handling. |
+| =cross-agent-watch= | At iteration start | Suppress notifications; log only. Continues running, no-op until HALT clears. |
+| =cross-agent-status= | At start | Print prominent "⚠ HALT ACTIVE" banner before normal output. Read-only, continues. |
+| =cross-agent-discover= | At start | Print HALT banner; continue read-only enumeration. |
+| Agent polling loop | First action on every wake | Write a final =progress= note to any active conversation ("HALT fired; pausing"), do NOT reschedule, surface "halt active" to user. Polling decays within one cadence (~5 min). |
+| Agent user-facing responses | Every response while HALT is set | Append "(HALT active; cross-agent comms paused)" to the response. On HALT clear, the next response says "(HALT cleared; cross-agent comms ready to resume — say so to re-engage polling)." Persistent, not just first-response — keeps awareness alive. |
+| Conversation initiator | Before writing sequence 1 of any new conversation | Refuse and surface to user. |
+| Startup workflow | Phase A on session start | If HALT exists, surface immediately and skip cross-agent inbox checks. |
+
+The agent polling-loop check is the load-bearing one for "stops eating CPU." Wake-ups already scheduled fire, but each wake on-HALT is a no-op + reschedule-prevention. Within one polling cadence (~5 min) all polling stops.
+
+*Fail-closed on unreadable HALT.* If the HALT file exists but is unreadable (wrong permissions, partial write), components MUST treat as halted. Safer than fail-open.
+
+** Resume asymmetry (deliberate)
+
+Halt is automatic everywhere. Resume requires explicit user intent per-session.
+
+When the user removes HALT (or runs =cross-agent-resume=), components stop refusing to act, but agent polling does NOT auto-resume. The user must open each session and tell that agent to resume polling for its conversations.
+
+The asymmetry exists because:
+
+1. Auto-resume could silently invert intentional kills. If the user halted because a session was misbehaving, removing HALT shouldn't quietly revive it.
+2. Per-session resume forces the user to look at each session and confirm the situation is resolved before re-engaging.
+
+** Cross-machine halt
+
+=cross-agent-halt --tailnet= iterates =peers.toml= and SSH-touches HALT on each peer. Same shape for resume.
+
+Reports per-peer status with non-zero exit on partial halt:
+
+#+begin_example
+$ cross-agent-halt --tailnet
+Halting velox.local ✓ (HALT file written)
+Halting bastion.local ✗ (ssh exit 255: no route to host)
+Halting locally ✓ (HALT file written)
+
+PARTIAL HALT: 2/3 machines halted. bastion.local needs manual halt.
+Exit 1.
+#+end_example
+
+Scripting can detect partial halt via the exit code. Same pattern for =--tailnet= on resume.
+
+* Limitations
+
+- *Local-tailnet only.* Filesystem IPC + rsync over SSH. Cross-tailnet or cross-organization is out of scope.
+- *Identity has three layers (Tailscale + POSIX + GPG)* but no message-content encryption. Confidentiality is not the goal; signing is correctness, not secrecy.
+- *Single-receiver per conversation.* Fan-out to multiple receivers requires manually orchestrating multiple parallel conversations.
+- *Polling is best-effort.* A wake may be delayed by an in-flight tool call until the runtime is idle. =cross-agent-watch= mitigates by offering event-driven notifications.
+- *Project-extension drift.* If two projects' =.ai/project-workflows/= modify shared workflow definitions in incompatible ways, cross-agent assumptions can diverge silently. The optional =#+WORKFLOW_VERSION= advisory field is informational only in v5 — no implementation reads or acts on it. A future version may add enforcement on mismatch (e.g. receiver writes =query= asking which side is stale). Today, alignment is verified manually before high-stakes conversations.
+
+* Persistence after release
+
+Conversation files persist by default. The conversation log is the audit trail.
+
+Manual archival is fine if the inbox grows unmanageable. Suggested cadence: once the conversation has been =release='d AND the work it produced has shipped, archive both projects' message files into =.ai/sessions/cross-agent/= as a flat directory — no per-conversation subdirectories. Rename each archived file to lead with the conversation-id so messages from the same conversation cluster on =ls=: =<conv-id>-<TIMESTAMP>-from-<sender>.org= (and the matching =.asc= sibling, if present). Inbox filenames lead with the timestamp because chronological arrival is what matters in =from-agents/=; archives invert that because grouping by conversation is what matters when reading history. Keep the =.asc= signatures alongside the =.org= files in archive — they're small and document the GPG verification chain.
+
+Old messages don't affect protocol behavior (=cross-agent-status='s pending semantics correctly ignore released messages) but the =from-agents/= directory grows indefinitely without manual archival. =cross-agent-status= performance degrades noticeably when a project's =from-agents/= exceeds a few hundred files. =cross-agent-init= (deferred to v6) would include an archival sub-command.
+
+* Open questions
+
+- *=cross-agent-init= and =cross-agent-compose= helper scripts.* =-init= would be one-command project bootstrap (creates =inbox/from-agents/= with =chmod 700=, installs the =cross-agent-watch= systemd path unit, validates peer config, runs a discovery probe). =-compose= would be interactive frontmatter authoring (prompts for required fields, produces a draft message file). Both deferred to v6. Current onboarding requires manual =mkdir= + systemd setup per =cross-agent-watch.md='s install recipe; current message authoring requires writing the file by hand or via a small in-agent template.
+- *Hard conversation timeout.* The async-fallback timeout is implementation-default ~24 hours. Right number depends on use case; tighten as patterns emerge.
+- *=paused= polling state.* Today there's no clean signal for "pause without ending." Add when first user complaint surfaces.
+- *Multi-LLM context.* If we ever bring in a non-Claude agent, the protocol's natural-language framing may need formalization.
+
+* Examples
+
+** =prep-fixup= conversation (2026-04-26 → 2026-04-27)
+
+Eleven exchanges between homelab and career produced the v4 spec by iterative critique-and-simplification. Three real-time sequence collisions during the conversation drove the sequence-as-hint rule that landed in v4 and persists in v5.
+
+Files at =~/projects/{homelab,career}/inbox/from-agents/= named =*-prep-fixup.org=. Worth re-reading when designing future cross-agent flows.
+
+** =comms-cold-start-discovery= conversation (2026-04-27)
+
+The follow-up that produced this v5 spec. Cold-start, watcher tooling, agent discovery, GPG identity, sha256 dedup, atomic writes, POSIX perms, script absorption, and process-vs-text simplification. Tonight's first cold-start in real time (career session went dormant after =prep-fixup= release; Craig's user-injection re-engaged it) is the worked demonstration of the v5 user-injection rule.
+
+Files at =~/projects/{homelab,career}/inbox/from-agents/= named =*-comms-cold-start-discovery.org=.
diff --git a/.ai/workflows/daily-prep.org b/.ai/workflows/daily-prep.org
new file mode 100644
index 0000000..1d5da0f
--- /dev/null
+++ b/.ai/workflows/daily-prep.org
@@ -0,0 +1,767 @@
+#+TITLE: Daily Prep Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-02-23
+
+* Overview
+
+This workflow prepares Craig for the next workday by reviewing scheduled meetings, identifying priorities, and blocking time on the calendar. It ensures Craig walks into every meeting prepared and has a plan for the day's focused work.
+
+This is typically run the evening before or morning of the workday in question.
+
+* Problem We're Solving
+
+Without daily prep, Craig risks:
+- Walking into meetings without the right context loaded
+- Missing opportunities to drive key decisions or raise important points
+- Losing track of action items owed to or from specific people
+- Not having focused work time protected on the calendar
+- Reactive instead of proactive days
+
+*Impact:* Unprepared meetings waste everyone's time. Unplanned days let urgent displace important.
+
+* Exit Criteria
+
+- All meetings for the day are reviewed with prep notes
+- 1:1 meetings have talking points drawn from todo.org and session history
+- Day's priorities are identified and confirmed by Craig
+- Time blocks are placed on the calendar for focused work
+- Any quick tasks (< 5 min) are done during prep itself
+- Craig feels ready for the day
+
+* When to Use This Workflow
+
+- When Craig says "let's prep for tomorrow" or "daily prep" or similar
+- During evening wrap-up if there are meetings the next day
+- Morning of a workday before meetings start
+- When Craig asks for the standup brief alone ("what's my standup report", "let's do the daily standup report", "give me the standup brief") — see Standup-only mode below
+
+* Modes
+
+The workflow runs in one of two modes based on the trigger phrase. Pick the mode at workflow start. Don't switch mid-run.
+
+** Full-prep mode (default)
+
+Trigger: "let's prep for tomorrow", "daily prep", or any general prep-cycle phrase.
+
+Runs Phase A → Phases 1-5 → Phase 6 (only if a standup is on the calendar) → Phase 7 → Phase 8 → Phase 9.
+
+** Standup-only mode
+
+Trigger: "what's my standup report", "let's do the daily standup report", "give me the standup brief", or similar standup-specific phrasing.
+
+Runs *only* a slim Phase A + Phase 6 + a conditional Phase 8 + Phase 9. Skip Phases 1-5 and Phase 7 entirely.
+
+*** Slim Phase A (standup-only)
+
+Fetch only what Phase 6 needs:
+
+1. =todo.org= — for WAITING items (blockers) and DONE-since-cutoff (completed work).
+2. Previous prep doc — anchor for the lookback (check =inbox/= then =daily-prep/=, take most recent).
+3. Most recent =.ai/sessions/= summary — for the "what did I do" question.
+
+Skip the calendar fetches, the Proton grep, and the [#A]/[#B] todo pull. Phase 6 Step 1's sweep handles the rest.
+
+*** Where the brief lands
+
+- If a prep doc for today already exists at =inbox/YYYY-MM-DD-daily-prep.org=, append or replace its =* Standup Brief= section.
+- If no prep doc for today exists, create =inbox/YYYY-MM-DD-daily-prep.org= with only the =* Standup Brief= section populated. Full prep can be added later by re-running in full-prep mode.
+
+*** Phase 8 in standup-only mode
+
+Only run if a stale prep doc exists in =inbox/= that's older than yesterday. Otherwise skip — there's nothing to archive.
+
+Use standup-only mode when Craig wants the brief fast (e.g., right before standup) without rebuilding the day's plan.
+
+* Prep Doc Structure
+
+The prep doc has a fixed set of top-level sections. Don't invent new ones. If substantive content surfaces that doesn't fit one of these headings, mention it in chat after the workflow finishes rather than adding it to the prep doc.
+
+Canonical sections (full-prep mode produces all; standup-only produces just =* Standup Briefs=):
+
+| Section | Phase that writes it | Purpose |
+|----------------------------------+----------------------+----------------------------------------------------------------------------|
+| =* Heads-up= | Phase 7 | Substantial situational context that changes Craig's frame for the day |
+| =* Day's Priorities= | Phase 3 | Actionable items for today (with Email/Slack/Linear Response sub-sections) |
+| =* Meetings / Work Blocks= | Phase 4 | Chronological schedule for the day (meetings + work blocks interleaved) |
+| =* Standup Briefs= | Phase 6 | Yesterday / Today / Blockers content for the standup meeting |
+| =* Upcoming Deadlines= | Phase 7 | Next 4-6 weeks of relevant deadlines, briefly |
+| =* [Next day]'s Anchor Tasks= | Phase 7 | Explicit forward commitments for tomorrow. Phase 2 of next-day's prep reads this |
+
+Order in the prep doc follows the table top-to-bottom: Heads-up first (frame-setter), Day's Priorities next (action surface), then schedule, then standup, then deadlines, then forward-commitment.
+
+* Approach: How We Work Together
+
+** Continuous flow (no mid-phase gates)
+
+Phases A through 7 run continuously. Don't stop between phases for Craig's input or confirmation. Information surfaced in later phases (especially Phase 3 email/Slack/Linear scans) frequently reframes what Craig wants from earlier phases — meeting goals, carry-forward decisions, priority order, time blocking. Stopping at Phase 1 to ask "what do you want from this meeting?" means asking Craig to react with stale context, before scans surface the email that might change his answer.
+
+Build all phases through Phase 7, then present the assembled draft and surface every question and proposed adjustment at the final review.
+
+Exception: stop only if a scan turns up something that *blocks* further building. Examples: a meeting was canceled and the whole day's structure changes; a security or compliance issue Craig must adjudicate before email triage proceeds. Otherwise keep going.
+
+This rule applies to all phases including Phase 1's "collaborative review" line and Phase 2's planned-vs-actual review. Both are framed as collaborative in their phase descriptions; the collaboration happens once, at the end, against the assembled prep doc.
+
+** Phase A: Data Gathering (one parallel batch)
+
+Before any synthesis or interaction, pull every source the prep doc needs in a *single batch of parallel tool calls*. These reads are all independent — issue them in one message, not as a sequential round-trip per source:
+
+1. =mcp__google-calendar__list-events= for the *personal* account, scoped to the day being prepped.
+2. =mcp__google-calendar__list-events= for the *work* account, same scope.
+3. Grep =~/.emacs.d/data/pcal.org= for items on that day. This is the Proton Calendar export (=pcal= = personal calendar). The same directory has =gcal.org= and =dcal.org= for Google personal + DeepSat, but those are already covered by the MCP queries in steps 1 and 2 — only =pcal.org= adds non-redundant items.
+4. Read =todo.org= — collect [#A] and [#B] tasks plus anything with DEADLINE: or SCHEDULED: touching the day.
+5. List + read the *previous* prep doc. Check =inbox/= first (active prep docs not yet archived), then =daily-prep/= (the archive). Take the most recent file by date — usually yesterday's, but may be older if Craig was off (PTO, weekend, missed days). The lookback for the standup brief is anchored on this file's date, so accuracy matters.
+6. Read the most recent =.ai/sessions/= summary file (for the standup brief's "What did I do since last standup?" question).
+
+Phases 1-5 below all work from this in-memory snapshot. *Do NOT re-query the calendars in Phase 4* — the time-blocking pass uses the same data Phase A fetched. A typical daily-prep used to run 4-5 sequential round-trips just for data gathering; this collapses to one.
+
+** Phase 1: Meeting Review
+
+Working from the Phase A calendar snapshot, present each meeting with:
+
+1. *Time and duration*
+2. *Meeting name*
+3. *Owner* (who scheduled/owns the meeting)
+4. *Official agenda* (from calendar description)
+5. *What Craig needs from this meeting* -- decisions to drive, points to make, information to convey, people to persuade
+
+For the last item, Claude may not know Craig's goals for the meeting. Present what's known and Craig will clarify during review.
+
+*** 1:1 Meeting Prep
+
+1:1 meetings get deeper preparation:
+
+1. Check todo.org for tasks tagged with or related to the person
+2. Review session histories since the last 1:1 with that person for relevant events, decisions, and context
+ - Default lookback: since the last 1:1 with that person
+ - Craig may ask to go further back for long-running items
+3. Identify:
+ - Status updates Craig should share (progress on things they care about)
+ - Questions Craig needs to ask
+ - Decisions that need their input
+ - Action items owed to or from them
+
+Capture the meeting list with what's known about each (time, owner, official agenda, who's accepted vs declined, what Craig might need). Don't stop here for Craig's input. Bring questions about meeting goals to the final review at the end of Phase 7, when the cross-source picture from Phase 3 is in hand. The collaborative review of the meeting list happens once, against the assembled prep doc, not as a mid-flow gate.
+
+** Phase 2: Planned vs. Actual Review
+
+Before setting tomorrow's priorities, review what was planned for today against what actually happened. This catches work that slipped and prevents it from silently disappearing.
+
+1. Pull the current day's prep doc (if one exists). If it has a populated =* [Today]'s Anchor Tasks= section (written by yesterday's Phase 7 as the explicit handoff), use that as the canonical carry-forward list. Otherwise fall back to inferring carry-forward by listing the day's planned work blocks.
+2. For each block, note: completed, partially done, or not started
+3. For items that didn't happen, identify why (took longer than expected, blocked, deprioritized, meetings ran over)
+4. Carry forward anything that still matters into the priority list for the next day, with updated time estimates based on what we learned today
+5. Add all carried-forward items as =TODO= entries in the new prep doc's Day's Priorities section — don't just note them in the Planned vs Actual table. Unfinished tasks from yesterday become today's tasks automatically unless Craig cuts them.
+
+This step keeps the daily prep honest. If a 1-hour task consistently takes 3 hours, the estimates need to change. If work keeps getting bumped by meetings, that's a pattern worth raising.
+
+** Phase 3: Day's Priorities
+
+Assemble priorities automatically from multiple sources. No interactive confirmation. Craig reviews the prep doc as a whole when it's done.
+
+Write the assembled list as =TODO= entries under the prep doc's =* Day's Priorities= section. Order by urgency — most time-sensitive or blocking first. If Craig wants to add or remove a priority, he edits the prep doc directly.
+
+Sources contributing to Day's Priorities: todo.org, email, Slack, Linear.
+
+*Don't create empty response sub-section headers.* Only emit =** Email Response=, =** Slack Response=, or =** Linear Response= when there's at least one item to put under it. The =# Sources checked:= footer at the top of the prep doc shows which sources were scanned (✓ = ran, ✗ = skipped) — Craig already knows from the footer that Slack and Linear were checked even when those subsections aren't in the doc. Empty headers add visual noise and imply something was missed.
+
+*Linear digest / notification emails don't get their own TODO.* If sub-step 3b returns a Linear digest email saying "you have N unread notifications," that's not an Action item — Linear notifications surface either via 3e (the direct Linear query) or via the underlying per-notification emails Linear sends. Classify the digest as Noise-keep (or Noise-trash if the per-notification emails are also flowing through) and don't add a separate "Linear notifications check" entry to Day's Priorities.
+
+*** Recommended Approach Pattern (used by sub-steps 3b, 3d, 3e)
+
+When an item involves a non-trivial decision or judgment that benefits from analysis, include a =recommended approach= subheader before the response draft. The approach is the executor's analysis — situation, options, recommendation with rationale, considerations Craig should weigh — so Craig can evaluate the recommendation itself, not just the wording.
+
+Skip the approach subheader for routine acknowledgments, simple status replies, or "yep, looks good" approvals.
+
+Source-specific examples of when to *include* the approach subheader:
+
+- *Email:* decisions like grant applications, partnership replies, public-facing or sensitive responses
+- *Slack:* @mentions with explicit asks, items where someone is blocked waiting on Craig
+- *Linear:* @mentions requesting input, Blocked tickets Craig owns, Needs-Review items where the call isn't trivially obvious
+
+Format (consistent across all three sources):
+
+#+begin_example
+***** YYYY-MM-DD Day @ HH:MM:SS -ZZZZ recommended approach
+<situation, options, recommendation with rationale, considerations>
+***** YYYY-MM-DD Day @ HH:MM:SS -ZZZZ recommended response
+<draft response text, OR proposed action like a state change>
+#+end_example
+
+Generate timestamps with =date "+%Y-%m-%d %a @ %H:%M:%S %z"= so they're accurate, not estimated.
+
+*** Sub-step 3a: From todo.org
+
+1. Pull all [#A] tasks from todo.org.
+2. Add time-sensitive items (DEADLINE: or SCHEDULED: touching the day).
+3. Carry forward unfinished items surfaced by Phase 2 — but skip any item already added by step 1 or 2 above. A task that was [#A] yesterday and didn't finish gets carried forward by Phase 2 and would also re-appear in step 1's [#A] pull. Dedupe by org-mode heading text or =:ID:= property if present.
+
+Priorities typically include:
+- Messages to send (Slack, email)
+- Meetings to schedule
+- Documents to send out for review
+- Follow-ups to make
+- Prep work needed before a meeting
+
+*** Sub-step 3b: From email
+
+Scan email since the prior prep doc's *mtime* (when the file was last written, not the date in its filename or title) — both accounts, unread or unanswered, with the =summarize-emails=-style noise filter (=NOT flag:list=, addressed-to-Craig). Express the cutoff to Gmail as =after:YYYY/MM/DD HH:MM:SS= so messages dated *between the prior prep's write time and now* land in scope. Anchoring on filename date or Gmail's day-granular =after:= operator drifts when a prep doc is generated late at night for the next day — the wrong window catches a full extra calendar day or misses several hours.
+
+*Track every message path returned by the scan* (Action, FYI, and Noise alike). Sub-step 3c uses this list to mark-read only the emails actually processed, avoiding a race condition with emails arriving mid-workflow.
+
+For each email, classify:
+
+- *Action* — explicit ask, deadline, request for decision, or Craig is the bottleneck
+- *FYI* — informational, no action required (note in session-context.org if substantive, otherwise drop)
+- *Noise-keep* — automated/CC-only with no expected response, but has residual value (mark read; keep in inbox archive)
+- *Noise-trash* — automated/CC-only with no expected response AND no residual value (trash it; see criterion below)
+
+*Trash criterion.* Trash if both are true:
+
+1. No transactional / financial / security record value (would I ever search for this in 6 months?)
+2. Not direct human-to-human correspondence
+
+Otherwise mark-read but keep.
+
+*Per-account bias.* DeepSat work account biases toward keeping — defense engineering audit-trail value, storage is free. Personal Gmail biases toward trashing — high volume, low residual value, search clutter.
+
+Common *Noise-trash* patterns:
+- Newsletter / blog digest content (Substack subscriptions you don't actually read, daily/weekly roundups)
+- Retail / SaaS / EDC marketing promos
+- Social-network engagement bait (LinkedIn social digests, Instagram followers, Bandcamp DMs)
+- Aggregate notification digests redundant with their source app (Linear digest emails, Notion/Miro daily/weekly engagement pings)
+- Wrong-recipient mail (mailing-list typo addressed to a different person)
+- Past-event calendar artifacts (cancellations/updates for events that already happened)
+- Stock-tip teasers, deal alerts, review-request prompts (Hotels.com / Airbnb "rate your stay")
+
+Common *Noise-keep* patterns:
+- Financial: dividend statements, monthly statements, payment confirmations, invoices (paid or unpaid)
+- Account security: new-login alerts, new payment method added, password reset, OAuth grants
+- Domain / registrar admin (renewal notices, WHOIS contact reminders)
+- Direct human correspondence (even if FYI, even if you didn't reply)
+- Booking confirmations for trips actually being taken
+- PR / code review notifications (audit-trail value on the work account)
+- CI / deploy failure alerts (incident archive)
+
+*Email link format.* Every email surfaced in the prep doc must link directly to the actual message in the right Gmail account, not just to a generic Gmail tab. Construct the URL as:
+
+#+begin_example
+https://mail.google.com/mail/u/<account-index>/#all/<thread-id>
+#+end_example
+
+Two pieces matter:
+
+- *Account index.* The =u/<n>= segment selects which Gmail account opens the link. Indexes are stable per Craig's setup:
+ - =u/0= → personal account (=craigmartinjennings@gmail.com=, served by the =google-docs= MCP)
+ - =u/1= → DeepSat work account (=craig.jennings@deepsat.com=, served by the =google-docs-work= MCP)
+ Pick the index that matches the source MCP. A work email linked with =u/0= opens personal Gmail and shows nothing - that's the failure mode this rule prevents.
+- *Thread ID.* Use the =threadId= field from =listMessages= or =getMessage=, not the =messageId=. Gmail's web URL routes by thread. Use the =#all/= view (works whether the message is still in the inbox or has been archived) - don't use =#inbox/=, since the link breaks the moment the message moves out of inbox.
+
+If a project routes mail through a different account layout (other tenants, additional accounts), record the index mapping in that project's =.ai/notes.org= and override the defaults above.
+
+For each Action item:
+
+1. *Star the email in Gmail* so it's flagged in Craig's inbox outside the prep doc:
+ #+begin_src bash
+ python3 .ai/scripts/maildir-flag-manager.py star --reindex /path/to/message.eml
+ #+end_src
+2. *Read* the email content (use =eml-view-and-extract-attachments.py= in stdout mode if needed).
+3. *Capture substantive content* in the right place based on what kind of information it is:
+ - =deepsat/knowledge.org= (or the project's equivalent) — persistent facts the project should remember: new contacts to add to the roster, system or infrastructure details, strategic decisions, transcription corrections, vendor relationships.
+ - =.ai/session-context.org= — today's session context: what was read, decisions made or pending, follow-ups identified for later in the session.
+ When in doubt, default to session-context. The next session can promote anything worth keeping into knowledge.org during wrap-up.
+4. *People-Context Check (runs before the recommended response is drafted).* When the Action item involves a specific person (sender, recipient, or a named third party referenced in the body), look that person up in =deepsat/knowledge.org= (Key People table, plus the Team Details section if present). Pull role and reporting line, relationships to other key people (family, prior employer, advisor vs. employee), tone and working-style signals, and recent context that affects how Craig should phrase the response. If the person isn't in =knowledge.org=, capture what's known there before writing the draft — the people layer is persistent context, every future prep gets faster when this layer is complete. Skip the check for purely transactional senders (Mercury, GitHub notifications, Linear bots). The recommended-approach + recommended-response then incorporates the people-context: tone, channel choice (email vs Slack vs in-person), and framing should reflect the relationship.
+5. *Add to the prep doc* under Day's Priorities as an =Email Response= sub-section:
+ #+begin_example
+ ** Email Response
+ *** [[mail-link][From X: Subject]]
+ - One-line description of why action is needed.
+ - Suggested action: reply with status / approve / decline / schedule.
+ <recommended approach + recommended response per the Recommended Approach Pattern above>
+ #+end_example
+
+For each FYI item: read it, capture substantive content into knowledge.org or session-context per the routing above, then drop it from further processing. Don't add to the prep doc.
+
+For each Noise-keep item: no per-message action beyond the read-state pass at sub-step 3c. Don't add to the prep doc.
+
+For each Noise-trash item: trash it. Trashing removes the message from INBOX and parks it in Trash for 30 days before Gmail's auto-purge, so no separate mark-read is needed at sub-step 3c.
+
+#+begin_src bash
+# MCP path (preferred — works directly against Gmail):
+mcp__google-docs__trashMessage --messageId <id> # personal account
+mcp__google-docs-work__trashMessage --messageId <id> # DeepSat account
+
+# maildir path (fallback for offline / mu4e workflow):
+python3 .ai/scripts/maildir-flag-manager.py trash --reindex /path/to/message.eml
+#+end_src
+
+Don't duplicate Email Response items as standalone Day's Priorities entries — the =Email Response= sub-section IS the priority surface for those items.
+
+*** Sub-step 3c: Mark processed email as read
+
+After 3b is fully complete, mark *only the message paths 3b processed* (Action, FYI, Noise-keep) as read — not all unread INBOX emails. Noise-trash items are already handled at 3b and don't need a read-state pass.
+
+#+begin_src bash
+# MCP path (preferred):
+mcp__google-docs__modifyMessageLabels --messageId <id> --removeLabelIds '["UNREAD"]'
+
+# maildir path (fallback):
+python3 .ai/scripts/maildir-flag-manager.py mark-read --reindex /path/to/msg1 /path/to/msg2 ...
+#+end_src
+
+Scoped mark-read avoids a race where an email arriving between 3b's scan and 3c's mark-read would be silently marked read without being processed.
+
+Stars persist (separate flag), so Action items remain findable in Gmail by their star, not by unread state.
+
+*Before exiting Phase 3, verify the triage actions actually executed:* every Action item has a Gmail star AND is marked read; every FYI and Noise-keep item is marked read; every Noise-trash item is in Trash. The Phase 3 audit footer at sub-step 3g is the forcing function for source coverage; this verification is the parallel for triage execution. Producing only the audit footer without running the API calls leaves Craig with an unread inbox even though every message was *seen* in the prep doc — defeats the inbox-zero purpose of the workflow. Count what was processed (e.g., "44 trashed, 5 noise-keep marked-read, 19 starred + marked-read, 30 FYI marked-read") and confirm the totals match the classification.
+
+*** Sub-step 3d: From Slack
+
+Query Slack since the prior prep doc's *mtime* (when the file was last written, not its filename date). Three streams (filters out general channel chatter Craig wasn't directly addressed in):
+
+1. *DMs Craig hasn't replied to.* Call =mcp__slack-deepsat__conversations_unreads= with =channel_types='dm'= and =max_channels=100=. Each row is a DM message Craig hasn't read.
+2. *Channel @mentions of Craig.* Call =mcp__slack-deepsat__conversations_unreads= with =mentions_only=true= and =max_channels=100=. This is the *only reliable* way to find @mentions in this MCP — see the search-doesn't-work-for-mentions note below. Limitation: this only catches *unread* @mentions; ones Craig already saw are out of scope, which is the right trade-off (already-read mentions were seen and either actioned or consciously skipped).
+3. *Thread replies in threads Craig started or last commented in.* No direct API for "threads I started." Practical approach: in the unreads pulled by streams 1 and 2, look at the =ThreadTs= column — non-empty values mean the message is a thread reply. Use =mcp__slack-deepsat__conversations_history= on the parent channel to fetch the full thread context if any reply looks substantive.
+
+*Slack search caveat (don't waste time here).* =mcp__slack-deepsat__conversations_search_messages= does *not* index =<@USERID>= tokens as searchable text. Searches for =<@U0A8AJTEM9V>=, =@craig.jennings=, or even Craig's plain display name return empty even when @mentions exist. Slack's own search has the same limitation — the "Mentions & Reactions" panel in the Slack app uses the unreads-with-mentions API, not search. Don't fall back to =conversations_search_messages= to find @mentions; use =conversations_unreads(mentions_only=true)= as above. The search tool is fine for keyword searches in message text (e.g. "find any message mentioning 'STRATFI'").
+
+For each result, classify:
+
+- *Action* — explicit ask, decision needed, or Craig is the bottleneck
+- *FYI* — informational, no action required (capture in knowledge.org or session-context if substantive, otherwise drop)
+- *Noise* — bot pings, automated alerts, off-topic mentions (drop silently)
+
+For each Action item:
+
+1. *Read* the message and surrounding thread context.
+2. *Capture substantive content* in =deepsat/knowledge.org= (or the project's equivalent) for persistent project facts, or =.ai/session-context.org= for today's context. Same routing rules as Sub-step 3b — default to session-context when unsure.
+3. *People-Context Check* — same as sub-step 3b's step 4. When the message involves a specific person, look them up in =knowledge.org= before drafting. Skip for transactional bot pings.
+4. *Add to the prep doc* under Day's Priorities as a =Slack Response= sub-section:
+ #+begin_example
+ ** Slack Response
+ *** [[slack-permalink][From X in #channel: brief description]]
+ - One-line description of why action is needed.
+ - Suggested action: reply / react / move to thread / schedule.
+ <recommended approach + recommended response per the Recommended Approach Pattern above>
+ #+end_example
+
+For each FYI item: read it, capture substantive content per the routing above, then drop. No prep-doc entry.
+
+After processing all items, mark *only the specific DMs and @mentions we touched* as read in Slack — not channel-wide. The Slack MCP should expose a per-message mark-read capability; if the available MCP doesn't support it, skip this step and let Craig manage Slack read state himself. Do NOT mark channel chatter as read; only the items the query returned.
+
+Don't duplicate Slack Response items as standalone Day's Priorities entries — the =Slack Response= sub-section IS the priority surface for those items.
+
+*** Sub-step 3e: From Linear
+
+Query Linear since the prior prep doc's *mtime* (when the file was last written, not its filename date), using =updatedAt= as the timestamp anchor. Three streams:
+
+1. Tickets *assigned to Craig* with state changes or new activity.
+2. Tickets *created by Craig* with state changes or new comments (someone else moved or replied).
+3. Tickets where Craig is *@mentioned* in a recent comment.
+
+Use Linear MCP =list_issues= with the appropriate filters; =get_issue= and =list_comments= for individual reads.
+
+For each result, classify:
+
+- *Action* — ticket in "Needs Review" assigned to Craig; @mention requesting input; new comment with a question; deadline within ~48h; ticket assigned to Craig in a "blocked-on-me" state
+- *FYI* — state change on Craig's ticket by someone else; comment that's a status update; related-ticket activity worth knowing
+- *Noise* — bot updates, automated transitions (drop silently)
+
+For each Action item:
+
+1. *Read the ticket* — description, recent comments, state history.
+2. *Capture substantive content* in =deepsat/knowledge.org= (people roles, system facts, strategic decisions) or =.ai/session-context.org= (today's review notes). Same routing rules as Sub-step 3b — default to session-context when unsure.
+3. *People-Context Check* — same as sub-step 3b's step 4. When the ticket involves a specific person (assignee, reviewer, commenter, @mentioned), look them up in =knowledge.org= before drafting. Skip for bot updates and automated transitions.
+4. *Add to the prep doc* under Day's Priorities as a =Linear Response= sub-section:
+ #+begin_example
+ ** Linear Response
+ *** [[https://linear.app/...][SE-NNN]] Ticket title (state, assignee)
+ - Why action needed: one-liner (e.g., "Vrezh moved to Needs Review, awaiting Craig").
+ - Suggested action: review code / post comment / change state to X.
+ <recommended approach + recommended response per the Recommended Approach Pattern above>
+ #+end_example
+
+ Linear's =recommended response= can be a draft comment, a proposed state change with rationale, or a combined action like "comment + reassign to Vrezh" — the response is "what Craig should do," not just reply text.
+
+For each FYI item: read it, capture substantive content per the routing above, then drop. No prep-doc entry.
+
+*Two deliberate divergences from email/Slack:*
+
+1. *No mark-as-read step.* Linear has no "unread" concept. The processed signal is implicit — once Craig acts on the ticket (comments, changes state), it stops appearing in the next query. Items he doesn't act on re-surface tomorrow, which is correct.
+2. *No "star" or "save" equivalent.* Linear ticket URLs in the prep doc are enough.
+
+*Volume note:* if a query returns >20 tickets, surface the count and prioritize Action signals first (Needs Review assigned to Craig, @mentions, blocked tickets, deadlines).
+
+Don't duplicate Linear Response items as standalone Day's Priorities entries — the =Linear Response= sub-section IS the priority surface for those items.
+
+*** Sub-step 3f: From Open PRs
+
+Scan open pull requests on the project's primary repo (per-project; for DeepSat the canonical repo is `~/code/deepsat/orchestration_dashboard_mvp`). Use `gh pr list` to enumerate, classify each, and surface Action items in the prep doc.
+
+Scan command (single round-trip):
+
+#+begin_src bash
+gh pr list --repo <owner>/<repo> --state open --json number,title,author,reviewRequests,isDraft,updatedAt,headRefName,additions,deletions,url
+#+end_src
+
+For each result, classify:
+
+- *Action* — Craig is in `reviewRequests`, OR Craig was a reviewer and the author force-pushed since Craig's last review (re-review needed), OR the PR is blocked on Craig's response in a thread.
+- *FYI* — open PRs Craig isn't reviewing but worth noting (a teammate's branch in flight, draft PRs, Craig's own PRs awaiting others).
+- *Noise* — bot PRs (dependabot, renovate, etc.). Drop silently.
+
+For each Action item:
+
+1. *Read the PR* — diff summary, recent commits, review state, any unresolved threads.
+2. *People-Context Check* — same as sub-step 3b's step 4 for the PR author and the requesting reviewer.
+3. *Add to the prep doc* under Day's Priorities as a `** PR Review` sub-section:
+
+#+begin_example
+** PR Review
+*** [[PR-URL][PR #N — title (author, branch)]]
+- Why action needed: review requested / re-review after force-push / blocked on Craig's response.
+- Suggested action: full review / quick re-review / unblock thread.
+<recommended approach + recommended response per the Recommended Approach Pattern above>
+#+end_example
+
+For FYI items: include a short *Craig's own PRs awaiting review (FYI, not action)* sub-list under the PR Review section so the queue stays visible without inflating the action surface. One line per PR: number, short title, who's been requested.
+
+*Two deliberate divergences from email/Slack:*
+
+1. *No mark-as-read step.* PR review state is implicit — once Craig submits a review or a comment, the PR's review-status changes and re-classification happens on the next prep.
+2. *No "star" equivalent.* PR URLs in the prep doc are enough; Craig's review queue is already filterable in the GitHub UI.
+
+*Per-project repo configuration.* The repo path is project-specific. For projects without a primary repo (personal documentation projects, etc.), skip this sub-step and mark `prs ✗ (no primary repo)` in the audit footer. For projects with multiple repos in active development, scan each in turn; surface Action items with the repo name prefixed in the title.
+
+Don't duplicate PR Review items as standalone Day's Priorities entries — the `PR Review` sub-section IS the priority surface for those items.
+
+*** Sub-step 3g: Cross-source dedup and urgency re-sort
+
+After 3a-3f have written all their entries to Day's Priorities, do a final cleanup pass.
+
+*Cross-source dedup.* Scan the Email Response, Slack Response, Linear Response, and PR Review sub-sections for items that reference the same conversation or topic. Common cases:
+
+- Vrezh DMs Craig about a Linear ticket *and* comments on the ticket itself — both surface as separate drafts
+- An email points at "let's discuss on the ticket" — the ticket also has activity
+- A Slack thread is the followup to an email exchange
+
+For each candidate duplicate pair, surface to Craig:
+
+#+begin_quote
+These two items look like the same conversation: [Email link] and [Linear link]. Should I collapse them under one source, or keep both?
+#+end_quote
+
+Wait for Craig's call. If he says collapse, keep the source he picks and add a cross-reference link in the kept item ("see also: [other-link]"). Don't auto-collapse.
+
+*Urgency re-sort.* Items currently appear in sub-step order (todo.org, then Email Response, then Slack Response, then Linear Response). Re-order all top-level entries under =* Day's Priorities= by urgency:
+
+1. Items with deadlines today or already overdue
+2. Items where Craig is blocking someone (Slack blocked-on-Craig, Linear Blocked tickets, Needs-Review assigned to Craig)
+3. Items with deadlines within ~48 hours
+4. Other [#A] tasks
+5. Time-sensitive but lower-stakes items
+6. Everything else
+
+Within each tier, preserve the source-section structure (Email Response, Slack Response, Linear Response sub-sections stay grouped) — just re-order the top-level entries that aren't already sub-sectioned.
+
+Craig can re-order further when he reviews the prep doc; this just gives him a sane starting order.
+
+*** Sub-step 3h: Phase 3 audit footer (forcing function)
+
+After 3a-3g are done, write a single comment line at the top of the prep doc — directly below the =#+DATE:= header — recording which sources actually got checked:
+
+#+begin_example
+# Sources checked: todo.org ✓ | email-personal ✓ | email-deepsat ✓ | Slack ✓ | Linear ✓ | prs ✓
+#+end_example
+
+Replace the ✓ with ✗ for any source that was skipped, and add a parenthetical reason after each ✗ (e.g. =Slack ✗ (MCP disconnected)= or =email-personal ✗ (auth scope error)=). If a source was scanned but returned no items, keep the ✓ — empty is a valid scan result; "didn't run at all" is what this line catches.
+
+This footer is the canary that surfaces silent skips. The reason it's a forcing function: writing the line means deciding what mark each source gets, which means actually checking that each source ran. A skipped sub-step now requires an explicit ✗, not a silent omission of a section.
+
+** Phase 4: Time Blocking
+
+Once priorities are confirmed, block time on the calendar to accomplish them. Phase 4 also writes the prep doc's =* Meetings / Work Blocks= section — a single chronological list interleaving meetings with focused-work blocks, top to bottom in time order.
+
+*** Quick Tasks (< 5 minutes)
+Tasks like "schedule a meeting with Ryan" or "send a Slack message" should be done during the prep workflow itself, not scheduled separately. Draft the message or create the calendar event on the spot.
+
+*** Focused Work Tasks
+For anything requiring more than a few minutes:
+
+1. Ask Craig for a time estimate on each item
+2. Use the Phase A calendar snapshot — *do not re-query the calendars*. The same events are already in context.
+3. *Compute the day's active window.* Default 10:00 to 17:00, every day of the week (weekends included). If the prep is being run *same-day* (the prep doc's date matches today's date) and the workflow start-time is later than 10:00, the window starts at the start-time instead. Existing calendar appointments, known personal commitments (guests arriving, travel, off-time blocks Craig flagged), and the last-30-min next-day-prep reservation remove time from the window. Whatever's left is fillable with priorities. Don't auto-treat Saturday or Sunday as "off." Fill the window with tasks. If Craig wants personal time on a weekend, he marks it on the calendar or flags it during prep.
+4. List the schedule with open slots clearly marked. Show the active window's start, end, and remaining fillable minutes after subtracting appointments.
+5. Propose when each task fits, considering:
+ - Meeting proximity (prep work should be near the meeting it supports)
+ - Energy/focus (harder tasks earlier if possible)
+ - A half hour around lunchtime to eat when possible (protein shake or leftovers is fine -- this can be compressed or skipped on heavy days)
+ - Always reserve the last 30 minutes of the workday for daily prep for the following day. This is non-negotiable -- it's what keeps the cycle going.
+6. Craig confirms or adjusts the proposed schedule
+
+*** Placing Calendar Blocks
+- Place time blocks on Craig's personal Google calendar (=Craig Google=)
+- Can also place blocks on DeepSat calendar directly using MCP server with =account_id: "work"=
+- Use the calendar event workflows (add-calendar-event.org) for creating events
+
+** Phase 5: Prep Work
+
+With the schedule set, work on anything that needs to be ready for the day's meetings. Examples:
+
+- Watch tutorial videos and take notes
+- Draft or polish documents for review
+- Prepare talking points or materials
+- Research topics that will come up in meetings
+- Draft messages that are part of the day's priorities
+
+This phase is where the bulk of the session time goes. Work through items in priority order, with meeting-related prep taking precedence based on meeting time.
+
+** Phase 6: Standup Brief
+
+Generate the standup brief late in the workflow so the rest of the day's analysis is already done. The brief draws from Phase 2 (Planned vs Actual), Phase 3 (Day's Priorities), and the activity sweep below. Phase 6 writes the prep doc's =* Standup Briefs= section.
+
+Skip this phase entirely on days with no standup on the calendar.
+
+*** Step 1: Sweep recent activity for off-Claude signals
+
+Before drafting, check these sources for activity since the previous prep doc's date (the lookback anchor from Phase A step 5):
+
+1. *Sent email* — both accounts. Outgoing threads from Craig: decisions communicated, replies to action items, follow-ups, intros made.
+2. *Slack* — Craig's recent messages across channels and DMs. Decisions, status updates, threads where Craig participated.
+3. *todo.org* — items marked DONE since the cutoff. New TODOs added that hint at fresh context (new commitments, new blockers).
+4. *Linear* — tickets Craig moved to a new state, commented on, or created. Use the Linear MCP to query.
+
+These surface team-visible work that lives outside session history (work done in mu4e, Slack, the Linear UI, or off-Claude conversations).
+
+*** Step 2: Draft the brief
+
+Combine session history + the Step 1 sweep + Phase 3 priorities + todo.org WAITING items into the three-question structure below.
+
+*Meeting selection.* Recurring meetings are excluded from both the Yesterday and Today sections — they're not news to the team. For non-recurring meetings:
+
+- *Yesterday section:* include only meetings Craig actually attended. Filter on Craig's own response status from the calendar event:
+ - =accepted= → include
+ - =tentative= or =declined= → exclude (don't assume attendance)
+- *Today section:* include all non-recurring meetings regardless of response status. Craig still needs to communicate what he plans to attend or decide between.
+
+The Google Calendar MCP returns =responseStatus= per attendee — filter on Craig's own response, not the event's overall status. Recurring events expose =recurringEventId= or =recurrence=; treat the presence of either as the recurring marker.
+
+*Content rules.*
+
+- *Stay first-person.* Report what Craig did, said, or decided. Don't volunteer others for work or report what others said they would do — that's their standup, not Craig's. If a teammate's commitment is relevant context, frame it as something Craig is waiting on, not as a status update on their behalf.
+- *Match deadline precision to what's actually known.* If a deadline is "early next week," don't tighten it to "Monday." If the commitment is "before the proposal goes out," don't sharpen it to "Friday EOD." Vague is honest when vague is the truth.
+
+1. *What did I do since last standup?* Anchor the lookback at the previous prep doc's date (Phase A step 5). Pull session history, session-context.org, completed tasks, and the Step 1 sweep results from that date forward. Since DeepSat standup is a workday-recurring meeting, the previous prep doc's date is the previous standup date even if intervening days were weekends or PTO. Use explicit day references ("Monday" not "yesterday") since the prep doc may be written the evening before.
+2. *What am I doing today?* Pull from the day's priorities (Phase 3 output). Keep it to 2-3 items max.
+3. *Blockers: mine or yours?* The bar is "did this actually stop me from making progress?", not "is someone else involved?" A WAITING item in todo.org is only a blocker if Craig already tried to move forward and got stopped. Mere dependencies don't qualify unless they've already impaired progress. Default to under-reporting — draft without borderline items and let Craig add them back in Step 3. FYI items (decisions or context worth flagging that aren't blockers) come after blockers and stay loose.
+
+The brief should be concise enough to read aloud in under 60 seconds. Include enough context that Craig doesn't have to think on his feet, but not so much that it turns into a status report.
+
+*** Step 3: Present the draft and ask what's missing
+
+Show Craig the full draft brief — Yesterday, Today, Blockers/FYI sections all populated. Then ask:
+
+#+begin_quote
+Anything I missed? Common categories: off-Claude meetings or phone calls, paperwork or forms submitted, intros made or received, vendor conversations, anything else not captured in session/email/Slack/Linear.
+#+end_quote
+
+Recognition is faster than recall. Craig can react to "did you make any intros?" much faster than to "what did you do?" The categories are prompts, not a checklist.
+
+*** Step 4: Refine and finalize
+
+Apply Craig's additions and any wording adjustments. The brief is now ready for standup.
+
+*** Step 5: Offer to capture learnings
+
+After Step 4, scan Craig's refinements for non-obvious patterns:
+
+- Did he cut a category of item the workflow said to include?
+- Did he add a category the workflow didn't tell you to look for?
+- Did he change wording in a way that suggests a phrasing rule (e.g., "say 'continued focus on X' instead of 'worked on X'")?
+
+If a refinement looks like a generalizable rule, offer it back to Craig:
+
+#+begin_quote
+I noticed you [removed all 1:1 meetings from Yesterday / added the off-Claude phone call with Ryan / changed "completed proposal draft" to "continued focus on proposal"]. Want me to add this to the workflow? Proposed rule: "[concrete rule text]"
+#+end_quote
+
+Wait for explicit confirmation before editing the workflow. Never edit on your own judgment.
+
+If Craig confirms, append the rule to the *Updates and Learnings* section at the bottom of this file with today's date and a one-line description. If the new rule supersedes existing text inside Phase 6, also update the inline text and note "(updated YYYY-MM-DD — see Updates and Learnings)" in the affected section so the audit trail is complete.
+
+If the refinement looks like a one-off, don't propose a rule. Default to under-proposing — false-positive rules clutter the workflow. The bar is "would I want this guidance the next time I draft a brief?" If yes, propose. If no, drop it.
+
+** Phase 7: Final Section Writeups
+
+Three sections remain after Phase 6: =* Heads-up=, =* Upcoming Deadlines=, and the next day's =* [Day]'s Anchor Tasks=. Phase 7 writes them, drawing on what earlier phases already surfaced.
+
+Order doesn't matter between the three sub-steps. They write to distinct sections and don't depend on each other.
+
+*** Sub-step 7a: Heads-up
+
+Write a top-level =* Heads-up= section near the top of the prep doc (above =* Day's Priorities=).
+
+Heads-up is the executive summary of what would change Craig's frame for the day. Terse, situational, day-shaping. Pull from sources earlier phases surfaced:
+
+- *Substantial FYIs* from Phase 3 sub-steps 3b/3d/3e — items like "Eric quietly progressed two partnership threads on Apr 24" or "Vrezh force-pushed all three branches overnight" that affect what Craig should expect today
+- *Schedule changes* from Phase 1 — "Arusyak 15:00-16:00 is being rescheduled" or "DeepSat GTM declined for tonight"
+- *Urgent deadlines bubbling up* — items in =* Upcoming Deadlines= that hit within ~2 days, surfaced as standalone Heads-up items so Craig sees them immediately
+- *Active Reminders* (from Phase A's notes.org read) that frame today specifically — "first thing in the morning, register for SOFWeek"
+
+Format: bullet list, one situational note per line, no sub-bullets. Keep each entry to one sentence. If a Heads-up item needs more context, link to the source (email, ticket, prep section) instead of inlining.
+
+Lightweight FYIs (status pings, "FYI we shipped", small acknowledgments) stay in =.ai/session-context.org= and don't surface in Heads-up.
+
+*** Sub-step 7b: Upcoming Deadlines
+
+Write a top-level =* Upcoming Deadlines= section.
+
+Source: scan todo.org for =DEADLINE:= entries plus deadlines mentioned in notes.org or knowledge.org. Filter to roughly the next 4-6 weeks. Order chronologically.
+
+Format: bullet list, one deadline per line, format =- [Day YYYY-MM-DD] — short description=. Mention the owner if it isn't Craig and the deadline still concerns him (e.g., "Subbu owns; Craig's technical content due ahead of this").
+
+Volume control: if more than ~10 deadlines fit the window, narrow the window or surface only the ones that are blocking, externally-imposed, or have non-trivial prep ahead.
+
+Don't duplicate deadlines that are already today's =* Day's Priorities= entries. Day's Priorities is for action; Upcoming Deadlines is for awareness.
+
+*** Sub-step 7c: Next day's Anchor Tasks
+
+Write a top-level =* [Day]'s Anchor Tasks= section. Compute the next day's name from today's date — *don't* skip weekends. For a Friday prep doc this becomes =* Saturday's Anchor Tasks=, for a Saturday prep doc it becomes =* Sunday's Anchor Tasks=, etc. Use known PTO markers if any (a fully-blocked PTO day can be skipped to the next available day).
+
+Source: items Craig is explicitly committing to do tomorrow. Pull from:
+
+- Items planned for today that didn't get done (from Phase 2's Planned vs Actual)
+- Items that genuinely fit tomorrow better than today (criterion below)
+- Prep work needed before tomorrow's meetings
+- Anything Craig flagged during today's session as "I'll do this tomorrow"
+
+*Criterion for pushing an item to tomorrow rather than fitting it today.* Push only if at least one is true:
+
+1. *Hard-blocked today* — waiting on a person, a system, or a deadline that hasn't passed yet
+2. *Needs a contiguous block* today's window can't provide
+3. *Prep work for a tomorrow-only meeting* (do the prep close to the meeting)
+4. *Team-dependent* and the team isn't available today (e.g., Craig's at one location and the team's at another)
+
+If none of those apply, the item stays in today's Day's Priorities and gets a time block in Phase 4. Don't auto-punt independent reading, registration, local-machine work, or "I'll get to it tomorrow" items just because today is a weekend or feels light.
+
+Items further out than tomorrow (Monday-only items written on a Saturday, for example) stay in =todo.org= with appropriate priority and SCHEDULED markers. Don't list them in the prep doc — they'll resurface in the relevant day's prep via Phase 3 sub-step 3a.
+
+Format: bullet list or =** TODO= headings, with optional time estimates (e.g., "— 1.5 hrs"). Each item should be a clear task — passive monitoring or context goes elsewhere (Heads-up or session-context).
+
+This section is the explicit handoff to next-day's Phase 2. If it's empty, write the header with "(none flagged)" so the next day's prep doesn't mistake an empty section for a missing one.
+
+** Phase 8: Archive Older Prep Docs
+
+After the new prep doc is written, archive any prep docs in =inbox/= older than yesterday's. Yesterday's prep doc stays in =inbox/= because the new prep doc may still need to reference it (Planned vs Actual, talking-point carry-forward).
+
+#+begin_src bash
+# Move any inbox/*-daily-prep.org file dated before yesterday into the archive.
+mv inbox/YYYY-MM-DD-daily-prep.org daily-prep/
+#+end_src
+
+The archive lives at =daily-prep/= at repo root. Don't put prep docs in =deepsat/meetings/= — the prep doc covers personal calendar, all 1:1s, and all projects, not just DeepSat work.
+
+If =daily-prep/= doesn't exist yet (new project), create it. If a stale prep doc lives in =assets/= (an older convention), move it to =daily-prep/= as part of this archive pass.
+
+This step keeps =inbox/= clean. The previous-day's prep is the only one that still has consumers.
+
+** Phase 9: Project Extension
+
+If =.ai/project-workflows/daily-prep.org= exists, read and execute its instructions as additional steps appended to this workflow. The project file contains add-ons specific to the project. It is *not* a replacement for this template — it picks up where this workflow's main flow ends.
+
+Surface the extension once per session ("Project has additional daily-prep steps — running them now") so the choice is visible.
+
+This is the project-extension hook from [[file:startup.org][startup.org]]'s workflow discovery rule, made explicit at the workflow level so it's not buried in discovery instructions. Runs in both full-prep and standup-only modes.
+
+* Principles to Follow
+
+** Prep Supports Action
+The goal is for Craig to walk into every meeting and task with what he needs. If prep doesn't lead to better outcomes, it's wasted time.
+
+** Craig Drives Priorities
+Claude surfaces information and proposes priorities, but Craig decides what matters. Don't assume -- ask.
+
+** Quick Tasks: Just Do Them
+If something takes less than 5 minutes, do it during prep rather than scheduling it. Draft the Slack message, create the calendar invite, send the email.
+
+** Respect the Calendar
+When proposing time blocks, respect existing commitments across all calendars. Don't double-book. Account for transition time between meetings.
+
+** First-Person Perspective
+When preparing 1:1 talking points, frame everything from Craig's perspective -- what does Craig need to communicate, ask, or decide? Not what the other person needs.
+
+* Living Document
+
+Update this workflow based on what works in actual daily prep sessions. Track learnings below.
+
+** Updates and Learnings
+
+*** 2026-02-23: Initial creation
+Created during first daily prep session. Validated against tomorrow's schedule (2026-02-24: Vrezh 1:1, Standup/IPM, Product Review, Backlog Planning).
+
+*** 2026-02-23: Use explicit day references, not relative terms
+When referring to past meetings or events, always include the explicit day (e.g., "from Monday's meeting" not "from this morning's meeting"). The prep doc may be written the day before and read the day of -- relative references like "this morning" or "today" become ambiguous. Include the day name so Craig doesn't have to calculate days in his head while focused on making a point.
+
+*** 2026-02-23: Prep doc output
+The prep is written to =inbox/YYYY-MM-DD-daily-prep.org= where the date is the day being prepped for.
+The startup workflow checks for this file and asks whether to open it. Note: need to resolve where the daily prep reference in startup.org lives so template syncing doesn't overwrite it (see todo.org task).
+
+*** 2026-02-23: Check for dependencies between tasks and meetings
+Tasks and meetings often depend on each other. SkyFi prep needs to happen before the Vrezh 1:1 so Craig can ask informed questions; Linear learning needs to happen before the backlog planning meeting where they'll use it. Always check for these dependencies and ask Craig when unsure.
+
+*** 2026-02-23: Energy management matters as much as time management
+Front-load high-effort, high-stakes work early in the day (about an hour after waking). Save research, reading, and lighter tasks for late afternoon when energy dips. Craig tries to eat lunch and have coffee/tea around 12:30 PM to sustain energy for the afternoon. This principle should guide time block placement alongside calendar constraints.
+
+*** 2026-02-23: Link references in the prep doc
+When listing tasks, documents, or directories in the prep doc, include links to the source
+(todo.org line numbers, file paths, directories). Craig shouldn't have to search for context
+when he's in the middle of a meeting or working through the day.
+
+*** 2026-02-23: Note dependencies between time blocks
+When an earlier time block feeds into a later meeting, call it out explicitly in the prep doc
+(e.g., "by this point, should have already checked Okta access"). Helps Craig verify he's
+on track as the day progresses.
+
+*** 2026-02-23: Prep doc is a living document through the day
+Craig may annotate the prep doc with "cj:" comments as he works through it. Process those
+when asked or at the start of the next session. Remove the comment markers after acting on them.
+
+*** 2026-02-23: Ask probing questions about task nature
+When a task like "SkyFi outreach" could mean either a 5-minute email or a 1-hour call, ask. The answer often splits the task into prep + action, which schedule differently. These questions are very helpful and should be a regular part of the workflow.
+
+*** 2026-03-08: Keep Day's Priorities, drop separate Time Blocking table
+The output prep doc should have a "Day's Priorities" section followed by a single chronological
+"Meetings / Work Blocks" section that interleaves meetings, prep blocks, and focused work blocks
+in time order. No separate Time Blocking table — the chronological Meetings / Work Blocks section
+serves that purpose.
+
+*** 2026-03-09: Day's Priorities use org-mode TODO headings, not numbered lists
+Each priority is a =** TODO= heading under =* Day's Priorities=, not a numbered list item.
+Format: =** TODO Task name — description. Links to todo.org or other files. ~time estimate.=
+Do not use bold or italic markup in the prep doc — org headings, TODO keywords, and plain text
+provide enough structure.
+Mark completed items as =** DONE= with a =CLOSED:= timestamp on the next line.
+Order by urgency and priority — most important/time-sensitive first. Craig should be able to
+work top-to-bottom and know he's tackling the right thing next.
+This makes priorities trackable in org-mode (agenda, todo filtering) and lets Craig toggle
+status directly in Emacs as the day progresses.
+
+The workflow phases (identify priorities, propose time blocks) still happen during the prep
+conversation — the change is only to the output format.
+
+*** 2026-03-08: Always include Planned vs Actual
+Craig finds the Planned vs Actual review table valuable. Always include it in the prep doc
+when a previous day's prep doc exists. This is Phase 2 of the workflow and should never be skipped.
+
+*** 2026-04-01: Always use human-readable ticket titles
+When referencing Linear tickets (or any issue tracker IDs), always use the human-readable
+title with the ID in parentheses — e.g., "Setup Database for dev environment (SE-93)" not
+just "SE-93." Craig can't remember what ticket IDs map to, and bare IDs force him to look
+them up. Use the Linear MCP tools to fetch the title if needed.
+
+*** 2026-03-27: Standup briefs — only team-visible goals, not personal productivity
+Only include work that left Craig's local environment: pushed to a repo, shared with
+the team, posted in Slack, changed something in Linear, or shifts what the team believes
+or plans. Exclude work whose output lives entirely in Craig's local files (knowledge.org,
+todo.org, session notes, transcript processing, local tooling setup, MCP server config).
+Filter: "If I didn't mention this, would someone on the team make a worse decision or
+duplicate the work?" If no, cut it.
diff --git a/.ai/workflows/delete-calendar-event.org b/.ai/workflows/delete-calendar-event.org
new file mode 100644
index 0000000..5bb92a1
--- /dev/null
+++ b/.ai/workflows/delete-calendar-event.org
@@ -0,0 +1,190 @@
+#+TITLE: Delete Calendar Event Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-02-01
+
+* Overview
+
+Workflow for deleting calendar events. Uses the Google Calendar MCP server (preferred) or gcalcli (fallback, personal account only).
+
+* Triggers
+
+- "delete the meeting"
+- "cancel my appointment"
+- "remove the event"
+- "clear my calendar for..."
+
+* Prerequisites
+
+- Google Calendar MCP server configured and authenticated (=@cocal/google-calendar-mcp=)
+- Two accounts available: =personal= (Craig Google) and =work= (Craig Deepsat)
+- Fallback: gcalcli installed (personal account only)
+- Event must exist on calendar
+
+* Note: Calendar Visibility
+
+The MCP server can delete events from both personal and work Google calendars. Proton calendar events are visible in =~/.emacs.d/data/pcal.org= but cannot be modified from here.
+
+* Workflow Steps
+
+** 1. Parse User Request
+
+Extract:
+- Which event (title, partial match, or date hint)
+- Date context (if provided)
+
+Examples:
+- "Delete the dentist appointment" → search for "dentist"
+- "Cancel tomorrow's meeting" → search tomorrow's events
+- "Remove the 3pm call" → search by time
+
+** 2. Search for Event
+
+*** MCP (preferred)
+Use =search-events= or =list-events= MCP tool with appropriate =account_id= ("personal" or "work").
+
+*** gcalcli (fallback, personal only)
+#+begin_src bash
+gcalcli --calendar "Calendar Name" search "event title"
+gcalcli --calendar "Calendar Name" agenda "date" "date 11:59pm"
+#+end_src
+
+** 3. Handle Multiple Matches
+
+If search returns multiple events:
+
+#+begin_example
+Found 3 events matching "meeting":
+
+1. Team Meeting - Feb 3, 2026 at 9:00 AM
+2. Project Meeting - Feb 4, 2026 at 2:00 PM
+3. Client Meeting - Feb 5, 2026 at 10:00 AM
+
+Which event do you want to delete? (1-3)
+#+end_example
+
+** 4. Display Full Event Details
+
+Show the event that will be deleted:
+
+#+begin_example
+Event to Delete:
+================
+Event: Team Meeting
+When: Monday, Feb 3, 2026 at 9:00 AM
+Duration: 1 hour
+Location: Conference Room A
+Description: Weekly sync
+Calendar: Work
+#+end_example
+
+** 5. Explicit Confirmation
+
+Ask clearly:
+
+#+begin_example
+Delete this event? (yes/no)
+#+end_example
+
+*Do NOT delete until user explicitly confirms with "yes".*
+
+** 6. Execute Delete
+
+*** MCP (preferred)
+Use the =delete-event= MCP tool:
+- =account_id=: "personal" or "work"
+- =calendar_id=: calendar name or ID
+- =event_id=: event ID (obtained from search/list results)
+
+*** gcalcli (fallback, personal only)
+
+gcalcli delete requires interactive confirmation. Pipe "y" to confirm:
+
+#+begin_src bash
+echo "y" | gcalcli --calendar "Calendar Name" delete "Event Title"
+#+end_src
+
+Use a date range to narrow matches:
+
+#+begin_src bash
+echo "y" | gcalcli --calendar "Calendar Name" delete "Event Title" 2026-02-14 2026-02-15
+#+end_src
+
+** 7. Verify
+
+Confirm the event is gone:
+
+*** MCP
+Use =search-events= or =list-events= to verify the event no longer appears.
+
+*** gcalcli (fallback)
+#+begin_src bash
+gcalcli --calendar "Calendar Name" search "Event Title"
+#+end_src
+
+Report success or failure to user.
+
+* Recurring Events
+
+*Warning:* Deleting a recurring event deletes ALL instances.
+
+For recurring events:
+1. Warn the user that all instances will be deleted
+2. Ask for confirmation specifically mentioning "all occurrences"
+3. Consider if they only want to delete one instance (not supported by simple delete)
+
+#+begin_example
+This is a recurring event. Deleting it will remove ALL occurrences.
+
+Delete all instances of "Weekly Standup"? (yes/no)
+#+end_example
+
+* Error Handling
+
+** Event Not Found
+- Verify spelling
+- Try partial match
+- Check date range
+- May have already been deleted
+
+** Delete Failed
+- Check calendar permissions
+- Verify event exists
+- Try with --calendar flag
+
+** Wrong Event Deleted
+- Cannot undo gcalcli delete
+- Would need to recreate the event manually
+
+* Safety Considerations
+
+1. *Always show full event details* before asking for confirmation
+2. *Never delete without explicit "yes"* from user
+3. *Warn about recurring events* before deletion
+4. *Verify deletion* by searching after
+5. *Read-only calendars* (like Christine's) cannot have events deleted
+
+* Read-Only Calendars
+
+Some calendars are read-only:
+
+| Calendar | Can Delete? | Account |
+|---------------------------+-------------+----------|
+| Craig Google | Yes | personal |
+| Christine | Yes | personal |
+| Craig Deepsat | Yes | work |
+| Todoist | Yes | personal |
+| Craig Jennings (TripIt) | No | personal |
+| Holidays in United States | No | personal |
+| Craig Proton | No | personal |
+
+If user tries to delete from read-only calendar:
+
+#+begin_example
+Cannot delete from "Craig Proton" - this is a read-only calendar.
+#+end_example
+
+* Related
+
+- [[file:add-calendar-event.org][Add Calendar Event]] - create events
+- [[file:read-calendar-events.org][Read Calendar Events]] - view events
+- [[file:edit-calendar-event.org][Edit Calendar Event]] - modify events
diff --git a/.ai/workflows/edit-calendar-event.org b/.ai/workflows/edit-calendar-event.org
new file mode 100644
index 0000000..662f0b4
--- /dev/null
+++ b/.ai/workflows/edit-calendar-event.org
@@ -0,0 +1,186 @@
+#+TITLE: Edit Calendar Event Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-02-01
+
+* Overview
+
+Workflow for editing existing calendar events. Uses the Google Calendar MCP server (preferred) or gcalcli (fallback, personal account only).
+
+The MCP server supports direct event updates via =update-event= — no delete-and-recreate needed. gcalcli fallback still uses delete-and-recreate since gcalcli's edit command is interactive.
+
+* Triggers
+
+- "edit the meeting"
+- "change my appointment"
+- "reschedule"
+- "update the event"
+- "move my appointment"
+
+* Prerequisites
+
+- Google Calendar MCP server configured and authenticated (=@cocal/google-calendar-mcp=)
+- Two accounts available: =personal= (Craig Google) and =work= (Craig Deepsat)
+- Fallback: gcalcli installed (personal account only)
+- Event must exist on calendar
+
+* CRITICAL: Check All Calendars Before Rescheduling
+
+When rescheduling an event, ALWAYS check for conflicts at the new time across ALL calendars:
+
+1. *MCP server* — check both personal and work accounts via =list-events= or =get-freebusy=
+2. *Emacs org files* — for Proton calendar (not accessible via MCP or gcalcli):
+
+#+begin_src bash
+grep "TARGET_DATE" ~/.emacs.d/data/pcal.org # Proton calendar
+#+end_src
+
+Verify the new time is free across all calendars before rescheduling.
+
+* Workflow Steps
+
+** 1. Parse User Request
+
+Extract:
+- Which event (title, partial match, or date hint)
+- What to change (if mentioned)
+
+Examples:
+- "Edit the dentist appointment" → search for "dentist"
+- "Reschedule tomorrow's meeting" → search tomorrow's events
+- "Change the 3pm call to 4pm" → search by time
+
+** 2. Search for Event
+
+*** MCP (preferred)
+Use =search-events= or =list-events= MCP tool with appropriate =account_id= ("personal" or "work").
+
+*** gcalcli (fallback, personal only)
+#+begin_src bash
+gcalcli --calendar "Calendar Name" search "event title"
+gcalcli --calendar "Calendar Name" agenda "date" "date 11:59pm"
+#+end_src
+
+** 3. Handle Multiple Matches
+
+If search returns multiple events:
+
+#+begin_example
+Found 3 events matching "meeting":
+
+1. Team Meeting - Feb 3, 2026 at 9:00 AM
+2. Project Meeting - Feb 4, 2026 at 2:00 PM
+3. Client Meeting - Feb 5, 2026 at 10:00 AM
+
+Which event do you want to edit? (1-3)
+#+end_example
+
+** 4. Display Full Event Details
+
+Show the current event state:
+
+#+begin_example
+Event: Team Meeting
+When: Monday, Feb 3, 2026 at 9:00 AM
+Duration: 1 hour
+Location: Conference Room A
+Description: Weekly sync
+Reminders: 5 min, 0 min
+Calendar: Craig
+#+end_example
+
+** 5. Ask What to Change
+
+Options:
+- Title
+- Date/Time
+- Duration
+- Location
+- Description
+- Reminders
+
+Can change one or multiple fields.
+
+** 6. Show Updated Summary
+
+Before applying changes:
+
+#+begin_example
+Updated Event:
+Event: Team Standup (was: Team Meeting)
+When: Monday, Feb 3, 2026 at 9:30 AM (was: 9:00 AM)
+Duration: 30 minutes (was: 1 hour)
+Location: Conference Room A
+Description: Weekly sync
+Reminders: 5 min, 0 min
+Calendar: Craig
+
+Apply these changes? (yes/no)
+#+end_example
+
+** 7. Explicit Confirmation
+
+*Do NOT apply changes until user confirms.*
+
+** 8. Execute Edit
+
+*** MCP (preferred — direct update)
+Use the =update-event= MCP tool:
+- =account_id=: "personal" or "work"
+- =calendar_id=: calendar name or ID
+- =event_id=: event ID (from search/list results)
+- Only pass the fields that changed (summary, start, end, location, description, etc.)
+
+*** gcalcli (fallback, personal only — delete + recreate)
+
+Since gcalcli edit is interactive, use delete + add:
+
+#+begin_src bash
+# Delete original
+gcalcli --calendar "Calendar Name" delete "Event Title" --iamaexpert
+
+# Recreate with updated fields
+gcalcli --calendar "Calendar Name" add \
+ --title "Updated Title" \
+ --when "new date/time" \
+ --duration NEW_MINUTES \
+ --where "Location" \
+ --description "Description" \
+ --reminder 5 \
+ --reminder 0 \
+ --noprompt
+#+end_src
+
+*Warning:* The gcalcli delete+recreate approach deletes ALL instances of a recurring event. The MCP =update-event= tool handles this more gracefully.
+
+** 9. Verify
+
+*** MCP
+Use =search-events= or =get-event= to verify the update.
+
+*** gcalcli (fallback)
+#+begin_src bash
+gcalcli --calendar "Calendar Name" search "Updated Title"
+#+end_src
+
+Report success or failure.
+
+* Error Handling
+
+** Event Not Found
+- Verify spelling
+- Try partial match
+- Check date range
+
+** Multiple Matches
+- Show all matches
+- Ask user to select one
+- Use more specific search terms
+
+** MCP Authentication Error
+Use =manage-accounts= MCP tool with =action: "add"= to re-authenticate.
+
+* Related
+
+- [[file:add-calendar-event.org][Add Calendar Event]] - create events
+- [[file:read-calendar-events.org][Read Calendar Events]] - view events
+- [[file:delete-calendar-event.org][Delete Calendar Event]] - remove events
diff --git a/.ai/workflows/email-assembly.org b/.ai/workflows/email-assembly.org
new file mode 100644
index 0000000..003459c
--- /dev/null
+++ b/.ai/workflows/email-assembly.org
@@ -0,0 +1,183 @@
+#+TITLE: Email Assembly Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-01-29
+
+* Overview
+
+This workflow assembles documents for an email that will be sent via Craig's email client (Proton Mail). It creates a temporary workspace, gathers relevant documents, drafts the email, and cleans up after sending.
+
+Use this workflow when Craig needs to send an email with multiple attachments that require gathering from various locations in the project.
+
+* When to Use This Workflow
+
+When Craig says:
+- "assemble an email" or "email assembly workflow"
+- "gather documents for an email"
+- "I need to send [person] some documents"
+
+* The Workflow
+
+** Step 0: Context Window Hygiene
+- Write out the session context file.
+- Inform the user that you've written out the session context file and ask if they want to compact the context now before beginning.
+
+** Step 1: Create Temporary Workspace
+
+Create a temporary folder at the project root:
+
+#+begin_src bash
+mkdir -p ./tmp
+#+end_src
+
+This folder will hold:
+- Copies of all attachments
+- The draft email text
+
+** Step 2: Identify Required Documents
+
+Discuss with Craig what documents are needed. Common categories:
+- Legal documents (deeds, certificates, agreements)
+- Financial documents (statements, invoices)
+- Correspondence (prior emails, letters)
+- Identity documents (death certificates, ID copies)
+
+For each document:
+1. Locate it in the project
+2. Confirm with Craig it's the right one
+3. Open it in zathura for Craig to verify if needed
+
+** Step 3: Copy Documents to Workspace
+
+**IMPORTANT: Always COPY, never MOVE documents.**
+
+#+begin_src bash
+cp /path/to/original/document.pdf ./tmp/
+#+end_src
+
+After copying, list the workspace contents to confirm:
+
+#+begin_src bash
+ls -lh ./tmp/
+#+end_src
+
+** Step 4: Draft the Email
+
+Create a draft email file in the workspace:
+
+#+begin_src bash
+./tmp/email-draft.txt
+#+end_src
+
+Include:
+- To: (recipient email)
+- Subject: (clear, descriptive subject line)
+- Body: (context, list of attachments, contact info)
+
+The body should:
+- Provide context for why documents are being sent
+- List all attachments with brief descriptions
+- Include Craig's contact information
+
+** Step 5: Open Draft in Emacs
+
+Open the draft for Craig to review and edit:
+
+#+begin_src bash
+emacsclient -n ./tmp/email-draft.txt
+#+end_src
+
+Wait for Craig to finish editing before proceeding.
+
+** Step 6: Craig Sends Email
+
+Craig will:
+1. Open his email client (Proton Mail)
+2. Create a new email using the draft text
+3. Attach documents from the tmp folder
+4. Send the email
+
+** Step 7: Process Sent Email
+
+Once Craig confirms the email was sent:
+
+1. Craig saves the sent email to the project inbox
+2. Use the **extract-email workflow** to process it:
+ - Create extraction directory
+ - Copy email to extraction directory
+ - Run extraction script
+ - Rename with server timestamp: =YYYY-MM-DD_HHMMSS_description.ext=
+ - Refile to appropriate location
+ - Clean up extraction directory
+
+See [[file:extract-email.org][extract-email workflow]] for full details.
+
+** Step 8: Clean Up Workspace
+
+Delete the temporary folder:
+
+#+begin_src bash
+rm -rf ./tmp/
+#+end_src
+
+** Step 9: Update Context Window
+Update the session context file before exiting this workflow.
+
+* Best Practices
+
+** Document Verification
+
+Before copying documents:
+- Open each one in zathura for Craig to verify
+- Confirm it's the correct version
+- Check that sensitive information is appropriate to send
+
+** Email Draft Structure
+
+A good email draft includes:
+
+#+begin_example
+To: recipient@example.com
+Subject: [Clear Topic] - [Property/Case Reference]
+
+Hi [Name],
+
+[Opening - context for why you're sending this]
+
+[Middle - explanation of what's attached and why]
+
+Attached are the following documents:
+
+1. [Document name] - [brief description]
+2. [Document name] - [brief description]
+3. [Document name] - [brief description]
+
+[Closing - next steps, request for confirmation, offer to provide more]
+
+Thank you,
+
+Craig Jennings
+510-316-9357
+c@cjennings.net
+#+end_example
+
+** Filing Conventions
+
+When refiling sent emails:
+- Use format: =YYYY-MM-DD_HHMMSS_description.ext= (server timestamp)
+- File in the most relevant project folder (check project's notes.org for conventions)
+- Clean up extraction directory after refiling
+
+* Example Usage
+
+Craig: "I need to send Seabreeze the documents for the HOA refund"
+
+Claude:
+1. Creates ./tmp/ folder
+2. Discusses needed documents (death certificate, closing docs, purchase agreement)
+3. Locates and opens each document for verification
+4. Copies verified documents to ./tmp/
+5. Drafts email and opens in emacsclient
+6. Craig edits, then sends via Proton Mail
+7. Craig saves sent email to inbox
+8. Claude extracts, reads, renames, and refiles email
+9. Claude deletes ./tmp/ folder
diff --git a/.ai/workflows/extract-email.org b/.ai/workflows/extract-email.org
new file mode 100644
index 0000000..3a70bea
--- /dev/null
+++ b/.ai/workflows/extract-email.org
@@ -0,0 +1,116 @@
+#+TITLE: Extract Email Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-02-06
+
+* Overview
+
+Extract email content and attachments from an EML file, rename with a consistent naming convention, and refile to =assets/=.
+
+* When to Use This Workflow
+
+When Craig says:
+- "extract the email"
+- "get the attachment from [email]"
+- "pull the info from [email]"
+- "process the email in inbox"
+
+* Sources
+
+The EML file may come from two places:
+
+** Already in =inbox/=
+
+Emails dropped into the project's =inbox/= directory via Syncthing, manual copy, or other means. These are ready for extraction immediately.
+
+** From =~/.mail/=
+
+Emails in the local maildir managed by mbsync/mu. Use the [[file:find-email.org][find-email workflow]] to locate the message, then copy (don't move) it into =inbox/= before proceeding. Never modify =~/.mail/= directly.
+
+* The Workflow
+
+** Step 0: Context Hygiene
+
+Before starting, write out the session context file and check with Craig whether we could compact the context. If there are a lot of emails, this will be a long process. If the context window collapses, we may forget important details. Writing out the session context prevents this data loss.
+
+** Step 1: Run Extraction Script
+
+Run the extraction script with =--output-dir= to perform the full pipeline (create temp dir, parse, auto-rename, extract attachments, refile, clean up):
+
+#+begin_src bash
+python3 .ai/scripts/eml-view-and-extract-attachments.py inbox/message.eml --output-dir assets/
+#+end_src
+
+The script automatically:
+- Parses email headers, body, and attachments
+- Generates filenames using the naming convention (see below)
+- Creates =.eml= (renamed copy), =.txt= (body text), and attachment files
+- Checks for filename collisions in the output directory
+- Moves all files to =assets/=
+- Cleans up its temp directory
+- Prints a summary of created files
+
+** Step 2: Review Summary Output
+
+Review the script's summary output and verify:
+- Filenames look correct (rename manually if needed)
+- Delete junk attachments (e.g., signature logos, tracking pixels)
+- Delete source EML from inbox after confirming results
+
+** Step 3: Report Results
+
+Report to Craig:
+- Summary of email content
+- What files were extracted and their final names
+- Where files were saved
+
+* Naming Convention
+
+Pattern: =YYYY-MM-DD-HHMM-Sender-TYPE-Description.ext=
+
+| Component | Source |
+|-------------+---------------------------------------------------------------------------|
+| YYYY-MM-DD | From the email's Date header (server time) |
+| HHMM | Hours and minutes from the Date header |
+| Sender | First name of the sender |
+| TYPE | =EMAIL= for the email body (.eml and .txt), =ATTACH= for attachments |
+| Description | Shortened subject line for EMAIL files; original filename for ATTACH files |
+
+** Example
+
+For an email from Jonathan Smith, subject "Re: Fw: 4319 Danneel Street", sent 2026-02-05 at 11:36, with a PDF attachment "Ltr Carrollton.pdf":
+
+#+begin_src
+2026-02-05-1136-Jonathan-EMAIL-Re-Fw-4319-Danneel-Street.eml
+2026-02-05-1136-Jonathan-EMAIL-Re-Fw-4319-Danneel-Street.txt
+2026-02-05-1136-Jonathan-ATTACH-Ltr-Carrollton.pdf
+#+end_src
+
+* Backwards-Compatible Mode
+
+Without =--output-dir=, the script behaves as before: prints metadata and body to stdout, extracts attachments alongside the EML file. This is useful for quick inspection without filing.
+
+#+begin_src bash
+python3 .ai/scripts/eml-view-and-extract-attachments.py inbox/message.eml
+#+end_src
+
+* Batch Processing
+
+When processing multiple emails, complete all steps for one email before starting the next. Do not parallelize across emails.
+
+* Principles
+
+- *Never modify =~/.mail/=* — always copy first, work on the copy
+- *EML is authoritative* — always keep it alongside extracted files
+- *Use email Date header for timestamps* — not extraction time
+- *Refer to find-email for maildir searches* — don't duplicate those instructions
+- *Script checks for collisions* — won't overwrite existing files in output dir
+- *One email at a time* — complete the full cycle before starting the next
+- *Source EML stays untouched* — the script copies, never moves the source; Claude deletes after verifying results
+
+* Tools Reference
+
+| Tool | Purpose |
+|-------------------------------------+---------------------------------|
+| eml-view-and-extract-attachments.py | Extract content and attachments |
+
+Script location: =.ai/scripts/eml-view-and-extract-attachments.py=
diff --git a/.ai/workflows/find-email.org b/.ai/workflows/find-email.org
new file mode 100644
index 0000000..0ef9615
--- /dev/null
+++ b/.ai/workflows/find-email.org
@@ -0,0 +1,122 @@
+#+TITLE: Find Email Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-02-01
+
+* Overview
+
+This workflow searches local maildir to find and identify emails matching specific criteria. Uses mu (maildir indexer) for fast searching.
+
+* Problem We're Solving
+
+Craig needs to find specific emails - shipping confirmations, receipts, correspondence with specific people, or messages about specific topics. Manually browsing mail folders is slow and error-prone. mu provides powerful search capabilities over the local maildir.
+
+* Exit Criteria
+
+Search is complete when:
+1. Matching emails are identified (or confirmed none exist)
+2. Relevant information is reported (subject, date, from, message path)
+3. Craig has what they need to proceed (info extracted, or path for further action)
+
+* When to Use This Workflow
+
+When Craig says:
+- "find email about [topic]"
+- "search for emails from [person]"
+- "do I have an email about [subject]?"
+- "look for [shipping/receipt/confirmation] email"
+- Before extract-email workflow (to locate the target email)
+
+* The Workflow
+** Step 0: Context Hygiene
+
+Before starting, write out the session context file and check with Craig whether we could compact the context. This might be a long process. If the context window collapses, we may forget important details. Writing out the session context prevents this data loss.
+
+** Step 1: Ensure Mail is Current (Optional)
+
+If searching for recent emails, run sync-email workflow first:
+
+#+begin_src bash
+mbsync -a && mu index
+#+end_src
+
+Skip if Craig confirms mail is already synced.
+
+** Step 2: Construct Search Query
+
+mu supports powerful search syntax:
+
+#+begin_src bash
+# By sender
+mu find from:jdslabs.com
+
+# By subject
+mu find subject:shipped
+
+# By date range
+mu find date:2w..now # last 2 weeks
+mu find date:2026-01-01.. # since Jan 1
+
+# Combined queries
+mu find from:fedex subject:tracking date:1w..now
+
+# In specific folder
+mu find maildir:/gmail/INBOX from:amazon
+
+# Full text search
+mu find "order confirmation"
+#+end_src
+
+** Step 3: Run Search
+
+#+begin_src bash
+mu find [query]
+#+end_src
+
+Default output shows: date, from, subject, path
+
+For more detail:
+#+begin_src bash
+mu find --fields="d f s l" [query] # date, from, subject, path
+mu find --sortfield=date --reverse [query] # newest first
+#+end_src
+
+** Step 4: Report Results
+
+Report to Craig:
+- Number of matches found
+- Key details (date, from, subject) for relevant matches
+- Message path if Craig needs to extract or read it
+
+If no matches:
+- Confirm the search was correct
+- Suggest alternative search terms
+- Consider if mail needs syncing first
+
+* Search Query Reference
+
+| Field | Example | Notes |
+|----------+------------------------------+--------------------------|
+| from: | from:amazon.com | Sender address/domain |
+| to: | to:c@cjennings.net | Recipient |
+| subject: | subject:"order shipped" | Subject line |
+| body: | body:tracking | Message body |
+| date: | date:1w..now | Relative or absolute |
+| flag: | flag:unread | unread, flagged, etc. |
+| maildir: | maildir:/gmail/INBOX | Specific folder |
+| mime: | mime:application/pdf | Has attachment type |
+
+Combine with AND (space), OR (or), NOT (not):
+#+begin_src bash
+mu find from:amazon subject:shipped not subject:delayed
+#+end_src
+
+* Principles
+
+- **Sync first if needed** - Searching stale mail misses recent messages
+- **Start broad, narrow down** - Better to find too many than miss the target
+- **Use date ranges** - Dramatically speeds up searches for recent mail
+- **Report paths** - Message paths enable extract-email workflow
+
+* Living Document
+
+Update this workflow as we discover useful search patterns.
diff --git a/.ai/workflows/first-session.org b/.ai/workflows/first-session.org
new file mode 100644
index 0000000..60118a2
--- /dev/null
+++ b/.ai/workflows/first-session.org
@@ -0,0 +1,87 @@
+#+TITLE: First Session Workflow
+#+AUTHOR: Craig Jennings & Claude
+
+Run this workflow on the first Claude Code session for a new
+project. It establishes the git/.ai policy, orients Claude to the
+project, and initializes =.ai/notes.org=.
+
+* When to Run
+
+When any of these are true:
+- =.ai/notes.org= contains the "If this is the first session" pointer
+- =.ai/sessions/= doesn't exist or is empty (no prior session records)
+- User says "this is a new project" or "let's set this project up"
+
+If unsure, ask.
+
+* The Workflow
+
+** Step 1: Determine git / =.ai= policy
+
+Ask:
+- Is this project in a git repository?
+- What are the remote repositories (if any)?
+- Is this a *code project* (Emacs package, library, software project)
+ or a *content/documentation project* (personal planning, business,
+ reference)?
+
+Based on the answer:
+
+*** Code project
+- Add =/.ai/= to =.gitignore= — session tooling is private, not part
+ of the codebase
+- Examples: org-msg, chime.el, wttrin, or any future Emacs
+ packages/libraries
+- =.ai/= contains session notes and Claude tooling; stays local-only
+- A project-level =docs/= (if ever created) is still tracked — real
+ user-facing docs go there, not in =.ai/=
+
+*** Content / documentation project
+- Commit =.ai/= normally — the project history IS the project
+- Examples: personal projects, business planning, documentation,
+ reference collections
+- =.ai/= holds session context and reference material that's part of
+ the project's evolution
+
+** Step 2: Understand the project
+
+Ask:
+- What is this project about?
+- What are the goals?
+- Any background, constraints, or people involved?
+- Anything that's already tried / ruled out?
+
+Take clarifying questions as they arise. Don't try to finish this
+step before moving on — understanding deepens across the first few
+sessions.
+
+** Step 3: Brainstorm how to help
+
+- Discuss approaches and strategies
+- Identify immediate next steps
+- Agree on a first concrete task (or that the first session is just
+ orientation)
+
+** Step 4: Document what was learned
+
+- Fill in the *Project-Specific Context* section of =.ai/notes.org=
+ with the project overview, goals, and any key facts from Step 2
+- Add project-specific references or files to =.ai/= as needed
+- If the project has a task file (=todo.org= at root), note its
+ location in notes.org
+
+** Step 5: Clean up
+
+- Remove the "If this is the first session" pointer from
+ =.ai/notes.org= (it's done its job)
+- The first session's record will be archived automatically via
+ wrap-it-up.org at session end — the session-context.org file
+ becomes =.ai/sessions/YYYY-MM-DD-HH-MM-description.org=. No
+ additional notes.org transcription needed.
+
+* Rationale
+
+First-session setup is a one-time event per project but it's
+procedural, so it belongs in workflows/ rather than embedded in the
+notes.org seed. The seed notes.org points here on first session;
+subsequent sessions should never touch this workflow.
diff --git a/.ai/workflows/journal-entry.org b/.ai/workflows/journal-entry.org
new file mode 100644
index 0000000..6fc5a73
--- /dev/null
+++ b/.ai/workflows/journal-entry.org
@@ -0,0 +1,218 @@
+#+TITLE: Journal Entry Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2025-11-07
+
+* Overview
+
+This workflow captures the day's work in Craig's personal journal. Journal entries serve as a searchable record for retrospectives, timelines, and trend analysis, while also providing context to Claude about relationships, priorities, mood, and goals that improve our collaboration.
+
+* Problem We're Solving
+
+Without regular journal entries, several problems emerge:
+
+** Limited Memory and Searchability
+- Craig's memory is limited, but what's recorded is always available
+- Finding when specific events occurred becomes difficult
+- Creating project timelines and retrospectives requires manual reconstruction
+- Identifying work patterns (weekday vs weekend, morning vs evening) is impossible
+
+** Missing Context for Collaboration
+- Claude lacks understanding of relationships (Julie is Craig's aunt, Laura is his sister)
+- Important contextual details that seem minor become critical unexpectedly
+- Craig's mood, frustrations, and satisfaction levels remain hidden
+- What Craig finds important vs unimportant isn't explicitly communicated
+- Claude can't identify where to help Craig focus attention to avoid mistakes
+
+** Lost Insights
+- Decisions made and reasoning behind them aren't captured
+- Big picture goals and upcoming plans remain undocumented
+- Patterns in what Craig is good at vs struggles with aren't tracked
+
+*Impact:* Without journal entries, Craig loses valuable personal records and Claude operates with incomplete context, reducing collaboration effectiveness.
+
+* Exit Criteria
+
+We know a journal entry is complete when:
+
+1. **Draft has been created** - Claude writes initial first-person draft based on today's session record (=.ai/session-context.org= if session is live, or today's file in =.ai/sessions/= if already wrapped up)
+2. **Revisions are complete** - Craig provides corrections and context until satisfied
+3. **Entry is added to journal file** - Text is added to the org-roam daily journal at ~/sync/org/roam/journal/YYYY-MM-DD.org
+4. **Craig approves** - Craig explicitly approves or indicates no more revisions needed
+
+*Measurable validation:*
+- Journal entry exists in the daily journal file
+- Craig has approved the final text
+- Entry captures big decisions, accomplishments, and unusual details
+- Tone feels personal, vulnerable, and story-like
+
+* When to Use This Session
+
+Trigger this workflow when:
+
+- Craig says "let's do a journal entry" or "create a journal entry"
+- At the end of a work session, particularly in the evening
+- Craig asks to wrap up the day
+- After completing significant work on a project
+
+This is typically done at the end of the day to capture that day's activities.
+
+* Approach: How We Work Together
+** Step 0: Context Hygiene
+
+Before starting, write out the session context file and check with Craig whether we could compact the context. If the context window collapses, we may forget important details. Writing out the session context prevents this data loss.
+
+** Step 1: Review the Day's Work
+
+Check today's session record for the day's activities:
+- If session is still live: read =.ai/session-context.org= (both Summary and Session Log)
+- If session already wrapped: read today's file in =.ai/sessions/= (named =YYYY-MM-DD-HH-MM-description.org=)
+
+Pull out:
+- Accomplishments achieved
+- Decisions made
+- Meetings or calls attended
+- Files created or organized
+- Actions planned for the future
+- Outstanding items
+
+** Step 2: Draft the Journal Entry
+
+Write a first-person journal entry as Craig. The entry should:
+- Be 2-3 paragraphs (unless it's an unusually eventful day)
+- Focus on big ideas and decisions
+- Include unusual or notable details
+- Read like a personal journal - it's a little story about how things went
+- Use a tone that's personal, genuine, and vulnerably open (never emotional)
+
+Structure suggestions:
+- Start with the big event or decision of the day
+- Explain what led to that decision or what work was accomplished
+- Include any context about people, mood, or upcoming plans
+- End with what's next or how you're feeling about progress
+
+** Step 3: Display and Request Revisions
+
+Display the draft to Craig and ask: "Does this capture the day? What would you like me to adjust?"
+
+This is where important context emerges:
+- Corrections about relationships and people
+- Clarification of goals and motivations
+- Craig's mood and feelings about events
+- Plans for the future
+- What's important vs not important
+
+** Step 4: Incorporate Feedback and Iterate
+
+Make the requested changes and display the revised text. Ask again for revisions. Repeat this process until Craig approves or indicates no more changes are needed.
+
+During revisions:
+- Ask questions if unsure about tone or word choice
+- Ask about people mentioned for the first time
+- If someone behaves strangely, ask Craig's thoughts to find the right tone
+- Record any new context in your notes for future reference
+
+** Step 5: Add Entry to Journal File
+
+Once approved:
+
+1. Find the org-roam daily journal file at ~/sync/org/roam/journal/YYYY-MM-DD.org
+2. If it doesn't exist, create it with this header:
+ ```
+ :PROPERTIES:
+ :ID: [generate UUID using uuidgen]
+ :END:
+ #+FILETAGS: Journal
+ #+TITLE: YYYY-MM-DD
+ ```
+3. Create a top-level org header with timestamp:
+ ```
+ * YYYY-MM-DD Day @ HH:MM:SS TZ ProjectName - What Kind of Day Has It Been?
+ ```
+ (Get timezone with: date +%z)
+4. Add the approved journal text below the header
+
+** Step 6: Wrap Up
+
+Update the session context file.
+
+After updating the session context file, ask Craig: "Are we done for the evening, or is there anything else that needs to be done?"
+
+Since journal entries typically happen at end of day, this provides a natural session close.
+
+* Principles to Follow
+
+** Personal and Vulnerable
+- Write in a genuinely open, vulnerable tone
+- Never emotional, but honest about challenges and feelings
+- Make it feel like Craig's personal journal, not a work report
+
+** Brief but Complete
+- Default to 2-3 paragraphs
+- Capture big ideas and unusual details
+- Don't document every minor task
+- Longer entries are fine for unusually eventful days
+
+** Story-Like Quality
+- Read like someone telling a story about their day
+- Have a narrative flow, not just a bullet list
+- Connect events and decisions with context
+
+** Clarifying Questions Welcome
+- Ask about tone, word choice, or what to include when unsure
+- Ask about people mentioned for the first time
+- Probe for Craig's thoughts when events seem unusual
+- Use questions to gather context that improves collaboration
+
+** Context Capture
+- Record new information about relationships, goals, and preferences
+- Note what Craig finds important vs unimportant
+- Track mood indicators and patterns
+- Save insights for future reference
+
+** Use Session Data
+- Start from today's session record (=.ai/session-context.org= if live, or today's =.ai/sessions/= file if wrapped)
+- Don't rely on memory - check the documented record
+- Include key decisions, accomplishments, and next steps
+
+* Living Document
+
+This is a living document. As we create journal entries and learn what works well, we update this file with:
+
+- Improvements to the drafting approach
+- Better examples of tone and style
+- Additional principles discovered
+- Refinements based on Craig's feedback
+
+Every journal entry is an opportunity to improve this workflow.
+
+* Example Journal Entry
+
+Here's an example of the tone, narrative flow, and level of detail to aim for:
+
+#+begin_quote
+Big day. We sold Gogo's condo.
+
+This morning I woke up to two counter offers - one from Cortney Arambula at $1,377,000 and another from Rolando Tong Jr. at $1,405,000. Deadline was 3 PM today.
+
+I had two phone calls with Craig Ratowsky. The first at 11:59 AM, we talked through both offers. Rolando's was clearly better - $28,000 more, already pre-approved, and the buyer is his sister. Craig walked me through the numbers and timeline.
+
+On the second call at 12:25 PM, I made the decision: accept Rolando's offer at $1,405,000. After all these months of work - dealing with mold, replacing the kitchen, new flooring, staging - we have a buyer.
+
+Escrow opens Monday (11/10/2025), 30-day close from there. By mid-December, this will be done.
+
+Net proceeds to the trust will be around $1,099,385 after the mortgage payoff, closing costs, and agent commissions.
+
+I spent the early evening getting all the files organized so I can figure out exactly how much Christine and I put in for the renovation and get reimbursed. This will also help when I report expenses to Mom and Laura about the estate.
+
+Now I need to plan the trip to Huntington Beach to handle Gogo's financial affairs - consolidate her accounts into the estate account, pay her bills, distribute her funds, and mail some items from the garage back home. Plus empty the garage for the seller before closing.
+
+Escrow Monday. Still need to:
+- Decide on compensating Craig and Justin for their extra work
+- Get the Tax ID number for the estate
+- Work on Gogo's final taxes with a CPA
+- File the Inventory & Appraisal with probate court
+
+It's been almost nine months since Gogo passed. Getting this condo sold feels like a huge milestone.
+#+end_quote
+
+Note the personal tone, narrative flow, big decision (accepting the offer), context about people (Gogo, Craig Ratowsky, Christine), mood (milestone feeling), and what's next.
diff --git a/.ai/workflows/page-me.org b/.ai/workflows/page-me.org
new file mode 100644
index 0000000..607ed51
--- /dev/null
+++ b/.ai/workflows/page-me.org
@@ -0,0 +1,173 @@
+#+TITLE: Page Me Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-01-31
+#+UPDATED: 2026-02-27
+
+* Overview
+
+This workflow enables Claude to set timers and alarms that reliably notify Craig, even if the terminal session ends or is accidentally closed. Notifications are distinctive (audible + visual with alarm icon) and persist until manually dismissed.
+
+Uses the =notify= command (alarm type) for consistent notifications across all AI workflows.
+
+* Trigger Phrase
+
+Craig says *"page me"* (or variations like "page me in 10 minutes", "page me at 3pm").
+
+The word "page" is the trigger for this workflow. It means: set a timed notification.
+
+Previously called "set-alarm" -- renamed to "page-me" for a distinctive, short trigger phrase that won't collide with common words like "remind" or "alert."
+
+* Problem We're Solving
+
+Notifications from AI sessions have several issues:
+
+1. *Too easy to miss* - Among many dunst notifications, AI alerts blend in
+2. *Not audible* - Dunst notifications are visual-only by default
+3. *Lost on terminal close* - If the terminal is accidentally closed, scheduled notifications never fire
+
+*Impact:* Missed notifications lead to lost time and reduced productivity. Tasks that depend on timely reminders get forgotten or delayed.
+
+* Exit Criteria
+
+The workflow is successful when:
+
+1. AI-set alarms are never missed
+2. Notifications are immediately noticeable, even when away from desk (audible)
+3. Notifications persist until manually dismissed (no auto-fade)
+4. Alarms fire regardless of whether the Claude session has ended or terminal was closed
+
+* When to Use This Workflow
+
+Use this workflow when:
+
+- Craig says "page me" about something at a specific time
+- A long-running task needs a check-in notification
+- Craig needs to leave the desk but wants to be alerted when to return
+- Any situation requiring a timed notification that must not be missed
+
+Examples:
+- "Page me at 5pm to wrap up"
+- "Page me in 30 minutes to check the build"
+- "Page me in 1 hour - time to take a break"
+
+* Approach: How We Work Together
+
+** Step 1: Craig Requests a Page
+
+Craig tells Claude when and why:
+- "Page me in 45 minutes - meeting starts"
+- "Page me at 3:30pm to call the dentist"
+
+** Step 2: Claude Sets the Page
+
+Claude schedules the alarm using the =at= daemon with =notify=:
+
+#+begin_src bash
+echo "notify alarm 'Page' 'Time to call the dentist' --persist" | at 3:30pm
+echo "notify alarm 'Page' 'Meeting starts' --persist" | at now + 45 minutes
+#+end_src
+
+The =at= daemon:
+1. Schedules the notification (survives terminal close)
+2. Confirms the alarm was set with the scheduled time
+
+** Step 3: Alarm Fires
+
+When the scheduled time arrives:
+1. Distinctive sound plays (alarm.ogg)
+2. Dunst notification appears with:
+ - Alarm icon
+ - The custom message provided
+ - Normal urgency (not critical - doesn't imply emergency)
+ - No timeout (persists until dismissed)
+
+** Step 4: Craig Responds
+
+Craig dismisses the notification and acts on it.
+
+* Implementation
+
+** Setting Alarms
+
+Use the =at= daemon to schedule a =notify alarm= command:
+
+#+begin_src bash
+# Schedule for specific time
+echo "notify alarm 'Page' 'Meeting starts' --persist" | at 3:30pm
+
+# Schedule for relative time
+echo "notify alarm 'Page' 'Check the build' --persist" | at now + 30 minutes
+
+# Schedule for tomorrow
+echo "notify alarm 'Page' 'Call the dentist' --persist" | at 3:30pm tomorrow
+#+end_src
+
+** Notification System
+
+Uses the =notify= command with the =alarm= type. The =notify= command provides 8 notification types with matching icons and sounds.
+
+#+begin_src bash
+# Immediate alarm notification (for testing)
+notify alarm "Page" "Your message here" --persist
+#+end_src
+
+The =--persist= flag keeps the notification on screen until manually dismissed. All page-me notifications should use =--persist= by default.
+
+** Managing Alarms
+
+#+begin_src bash
+# List pending alarms
+atq
+
+# Cancel an alarm by job number
+atrm JOB_NUMBER
+#+end_src
+
+The =at= command accepts various time formats:
+- =now + 30 minutes= - relative time
+- =now + 1 hour= - relative time
+- =3:30pm= - specific time today
+- =3:30pm tomorrow= - specific time tomorrow
+- =noon= - 12:00pm today
+- =midnight= - 12:00am tonight
+* Principles to Follow
+
+** Reliability
+The alarm must fire. Use the =at= daemon which is designed for exactly this purpose and survives terminal closure and session changes.
+
+** Efficiency
+Simple invocation - Claude runs one command. No complex setup required per alarm.
+
+** Fail Audibly
+If the alarm fails to schedule, report the error clearly. Don't fail silently.
+
+** Testable
+The =notify alarm= command can be called directly to verify notifications work without waiting for a timer.
+
+** Non-Alarming
+Use normal urgency, not critical. The notification should be noticeable but not imply something has gone horribly wrong.
+
+* Limitations (Current Version)
+
+- *Does not survive logout/reboot* - Alarms scheduled via =at= are lost on logout/reboot
+- *No alarm management UI* - Use =atq= to list and =atrm= to remove alarms manually
+
+Future versions may add:
+- Reboot persistence via systemd timers or alarm state file
+
+* Living Document
+
+Update this workflow as we learn what works:
+- Sound choices that are distinctive but not jarring
+- Icon that clearly indicates alarm origin
+- Any edge cases discovered in use
+
+** Sound Resources
+
+For future notification sounds:
+- Local collection: =~/documents/sounds/= (various notification tones)
+- https://notificationsounds.com - good selection of clean notification tones
+- https://mixkit.co/free-sound-effects/notification/ - royalty-free sounds
+
+See =notify= package for the unified notification system used across all AI workflows.
+
diff --git a/.ai/workflows/process-meeting-transcript.org b/.ai/workflows/process-meeting-transcript.org
new file mode 100644
index 0000000..07f8f3e
--- /dev/null
+++ b/.ai/workflows/process-meeting-transcript.org
@@ -0,0 +1,306 @@
+#+TITLE: Process Meeting Transcript Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-02-03
+
+* Overview
+
+This workflow defines the process for processing meeting recordings from start to finish: finding recordings, extracting audio, transcribing via AssemblyAI, identifying speakers, correcting errors, and archiving files.
+
+* When to Use This Workflow
+
+Trigger this workflow when:
+- Craig says "process the transcript" or "process the recording" or similar
+- New recording files (.mkv) appear in ~/sync/recordings/ after meetings
+- Craig wants to process meeting recordings into labeled transcripts
+
+* Prerequisites
+
+- Recording file(s) exist in ~/sync/recordings/ (*.mkv)
+- Calendar files available at ~/.emacs.d/data/*cal.org for meeting titles
+- AssemblyAI transcription script at ~/.emacs.d/scripts/assemblyai-transcribe
+- AssemblyAI API key stored in ~/.authinfo.gpg (machine api.assemblyai.com)
+- ffmpeg available for audio extraction
+
+* The Workflow
+
+** Step 1: Identify Engagement and Write Session Context
+
+Before starting transcript processing:
+
+1. *Identify which engagement this meeting belongs to:*
+ - DeepSat (default for current work)
+ - Vineti (historical)
+ - Salesforce (historical)
+ - If unclear, ask Craig
+
+2. *Set destination paths based on engagement:*
+ - Assets: ~{engagement}/assets/~ (e.g., ~deepsat/assets/~)
+ - Meetings: ~{engagement}/meetings/~ (e.g., ~deepsat/meetings/~)
+ - Knowledge: ~{engagement}/knowledge.org~ for reference
+
+3. Update .ai/session-context.org with current status:
+ - Note that we're about to process a meeting transcript
+ - Get meeting name by checking ~/.emacs.d/data/*cal.org (match date/time to transcript timestamp)
+ - If meeting not found in calendar, ask Craig for the meeting title
+
+** Step 2: Find Recording Files
+
+Find and match recording files with calendar events. *Run sub-steps 1 and 3 (recording list + calendar dump) as a single parallel batch* — they're independent. Sub-step 2 (parse timestamps) and sub-step 4 (matching) work from those two outputs in-memory, so they're sequential after the batch.
+
+1. **List recordings:** Find all recording files in ~/sync/recordings/ (video .mkv or audio-only .m4a)
+ #+begin_src bash
+ ls -la ~/sync/recordings/*.mkv ~/sync/recordings/*.m4a 2>/dev/null
+ #+end_src
+ Audio-only recordings (.m4a) are used when no screen content is expected. These skip Step 3 (audio extraction) since they're already in a transcribable format.
+
+2. **Extract timestamps:** Parse date/time from each filename (format: YYYY-MM-DD-HH-MM-SS.mkv or .m4a)
+
+3. **Match with calendar:** Check ~/.emacs.d/data/*cal.org for meetings at those times
+ #+begin_src bash
+ cat ~/.emacs.d/data/dcal.org | grep -A2 "YYYY-MM-DD"
+ #+end_src
+
+4. **Present selection table to Craig:**
+ | Filename | Meeting / Date-Time |
+ |-----------------------------+--------------------------------|
+ | 2026-02-03_10-00-00.mkv | DeepSat Standup (from calendar)|
+ | 2026-02-03_14-30-00.mkv | 2026-02-03 14:30 (no match) |
+
+5. **Craig selects files:** One, several, or all files to process
+
+6. **Queue for processing:** Selected files ordered oldest → newest for serial processing
+
+** Step 3: Extract Audio (video recordings only)
+
+*Skip this step for .m4a files* — they are already audio and can go directly to transcription.
+
+For .mkv video recordings, extract audio for transcription:
+
+#+begin_src bash
+ffmpeg -i ~/sync/recordings/FILENAME.mkv -vn -ac 1 -c:a aac -b:a 96k /tmp/FILENAME.m4a
+#+end_src
+
+Settings:
+- =-vn= : no video (audio only)
+- =-ac 1= : mono channel (sufficient for speech, smaller file)
+- =-c:a aac= : AAC codec
+- =-b:a 96k= : 96kbps bitrate (sufficient for speech transcription)
+
+Output: /tmp/FILENAME.m4a (temporary, deleted after transcription)
+
+** Step 4: Transcribe with AssemblyAI
+
+1. **Run transcription:**
+ #+begin_src bash
+ # For .mkv files (audio was extracted to /tmp/):
+ ~/.emacs.d/scripts/assemblyai-transcribe /tmp/FILENAME.m4a > ~/sync/recordings/FILENAME.txt
+ # For .m4a files (transcribe directly):
+ ~/.emacs.d/scripts/assemblyai-transcribe ~/sync/recordings/FILENAME.m4a > ~/sync/recordings/FILENAME.txt
+ #+end_src
+
+2. **Clean up:** Delete intermediate .m4a file after successful transcription (only for .mkv extractions — do NOT delete original .m4a recordings)
+ #+begin_src bash
+ rm /tmp/FILENAME.m4a
+ #+end_src
+
+3. **Output format:** The script produces speaker-diarized output:
+ #+begin_example
+ Speaker A: First speaker's text here.
+ Speaker B: Second speaker's response.
+ Speaker A: First speaker continues.
+ #+end_example
+
+4. Continue to speaker identification workflow below.
+
+** Step 5: Locate Files
+
+Confirm the transcript and recording files are ready:
+
+1. **Verify transcript exists:**
+ #+begin_src bash
+ ls -la ~/sync/recordings/FILENAME.txt
+ #+end_src
+
+2. **Verify recording exists:**
+ #+begin_src bash
+ ls -la ~/sync/recordings/FILENAME.mkv
+ #+end_src
+
+3. **Get meeting title:** If not already known from Step 2, check calendar
+ - Calendar location: ~/.emacs.d/data/*cal.org
+ - Match the meeting time to the transcript timestamp
+
+** Step 6: Read and Analyze Transcript
+
+1. Read the full transcript file
+
+2. Identify speakers by analyzing context clues:
+ - Names mentioned in conversation ("Thanks, Ryan")
+ - Role references ("as the developer", "on the IT side")
+ - Project-specific knowledge (who works on what)
+ - Previous meeting context (known attendees)
+ - Speaking order patterns
+
+3. Build a speaker identification table:
+ | Speaker | Person | Evidence |
+ |---------|--------|----------|
+ | A | Name | Clues... |
+
+** Step 7: Confirm Speaker Identifications
+
+Present the speaker identification table to Craig for confirmation:
+- List each speaker label and proposed name
+- Include the evidence/reasoning
+- Ask about any uncertain identifications
+- Note any new people to add to notes.org contacts
+
+** Step 8: Create Labeled Transcript
+
+1. Replace all speaker labels with actual names
+
+2. Correct transcription errors:
+ - Common mishearings (names, technical terms, company names)
+ - Known substitutions from this project:
+ - "Vanetti" → "Vineti"
+ - "Fresh" → "Vrezh"
+ - "Clean4" / "clone" → "CLIN 4"
+ - "Vascan" → "Vazgan"
+ - "Hike" / "Ike" → "Hayk"
+ - "High Tech" → "HyeTech"
+ - "Java software" → "JAMA software"
+ - "JSON" (person) → "Jason"
+ - "their S" / "ress" → "Nerses"
+ - Technical terms specific to DeepSat (GovCloud, AFRL, SOUTHCOM, etc.)
+
+3. Save to engagement assets folder:
+ - Location: ~{engagement}/assets/~ (e.g., ~deepsat/assets/~)
+ - Filename: YYYY-MM-DD-meeting-name.txt
+ - Example: deepsat/assets/2026-02-03-standup-ipm-grooming.txt
+
+** Step 9: Copy Recording to Meetings Folder
+
+1. Ensure engagement meetings folder exists and patterns are in .gitignore (~*/meetings/*.mkv~ and ~*/meetings/*.m4a~)
+
+2. Copy the recording file with descriptive name:
+ #+begin_src bash
+ # Video recordings:
+ cp ~/sync/recordings/YYYY-MM-DD-HH-MM-SS.mkv {engagement}/meetings/YYYY-MM-DD_HH-MM-meeting-name.mkv
+ # Audio-only recordings:
+ cp ~/sync/recordings/YYYY-MM-DD-HH-MM-SS.m4a {engagement}/meetings/YYYY-MM-DD_HH-MM-meeting-name.m4a
+ #+end_src
+ Example: ~deepsat/meetings/2026-02-03_11-02-standup-ipm-grooming.mkv~
+
+3. Verify the copy succeeded
+
+** Step 10: Update Session Context with Meeting Summary
+
+Add a meeting summary section to .ai/session-context.org including:
+
+1. **Attendees** - List all participants
+
+2. **Key Decisions** - Important choices made
+
+3. **Action Items** - Tasks assigned, especially for Craig
+
+4. **New Information** - Things learned that should be noted
+
+5. **New Contacts** - People to add to notes.org
+
+** Step 11: Write Session Context File
+
+Update .ai/session-context.org with:
+- Files created this session (transcript, recording)
+- Summary of what was processed
+- Next steps (file to assets, update notes.org, etc.)
+
+*** Context Management (for multiple files)
+
+When processing multiple recordings in a queue:
+
+1. **After completing each file's workflow**, update .ai/session-context.org with:
+ - Files processed so far
+ - Current position in queue
+ - Summary of meeting just processed
+
+2. **Ask Craig if compact is needed** before starting next file:
+ - Transcript processing uses significant context
+ - Compacting preserves session context for recovery
+
+3. **If autocompact occurs**, reread session-context.org to:
+ - Resume at correct position in queue
+ - Avoid reprocessing already-completed files
+
+** Step 12: Clean Up Source Files
+
+After successful completion of all previous steps, delete the source files from ~/sync/recordings/:
+
+1. **Delete the original recording:**
+ #+begin_src bash
+ rm ~/sync/recordings/FILENAME.mkv
+ #+end_src
+
+2. **Delete the raw transcript** (if generated):
+ #+begin_src bash
+ rm ~/sync/recordings/FILENAME.txt
+ #+end_src
+
+This step happens last to ensure all files are safely copied/processed before deletion. If anything goes wrong earlier in the workflow, the source files remain intact for retry.
+
+* Output Files
+
+| File | Location | Purpose |
+|--------------------+-------------------------------------------------------+------------------------------------|
+| Labeled transcript | {engagement}/assets/YYYY-MM-DD-meeting-name.txt | Corrected transcript for reference |
+| Meeting recording | {engagement}/meetings/YYYY-MM-DD_HH-MM-meeting-name.mkv | Video for review (gitignored) |
+| Session context | .ai/session-context.org | Crash recovery, meeting summary |
+| Knowledge base | {engagement}/knowledge.org | Team, infrastructure, corrections |
+
+* Common Transcription Errors
+
+Keep this list updated as new patterns emerge:
+
+| Heard As | Correct | Context |
+|---------------+---------------+------------------------------------------------|
+| Vanetti | Vineti | Company where Craig, Nerses, Eric, Ryan worked |
+| Fresh | Vrezh | Developer name |
+| Clean4, clone | CLIN 4 | Contract milestone |
+| Vascan | Vazgan | MagicalLabs AI team member |
+| Hike, Ike | Hayk | CTO name |
+| High Tech | HyeTech | Armenian tech community org |
+| Java software | JAMA software | Requirements traceability tool |
+| JSON (person) | Jason | DevSecOps or advisor |
+| their S, ress | Nerses | CEO name |
+| sir Keith | Sarkis | BD/investor relations |
+| Fastgas | MagicalLabs | Armenian AI contractor |
+| Sitelix | Cytellix | CMMC security/compliance partner |
+
+* Tips
+
+1. **Read the whole transcript first** - Context from later in the meeting often helps identify speakers from earlier
+
+2. **Use the calendar** - Meeting names help set expectations for who attended
+
+3. **Check engagement knowledge.org** - Team roster and transcription corrections specific to this engagement
+
+4. **Ask about unknowns** - If a new person appears, ask Craig for context
+
+5. **Note new learnings** - Update engagement knowledge.org with new contacts, corrections, or context after processing
+
+* Validation Checklist
+
+- [ ] Engagement identified and destination paths set
+- [ ] Session context written before starting
+- [ ] Recording files listed and matched with calendar
+- [ ] Craig selected files to process
+- [ ] Audio extracted to .m4a (mono, 96k AAC)
+- [ ] AssemblyAI transcription completed
+- [ ] Intermediate .m4a file deleted
+- [ ] Transcript file verified
+- [ ] All speakers identified
+- [ ] Speaker identifications confirmed with Craig
+- [ ] Transcript corrected and saved to {engagement}/assets/
+- [ ] Recording copied to {engagement}/meetings/ with proper name
+- [ ] Session context updated with meeting summary
+- [ ] New contacts/info flagged for {engagement}/knowledge.org update
+- [ ] (If multiple files) Queue position tracked in session context
+- [ ] Source files deleted from ~/sync/recordings/
diff --git a/.ai/workflows/read-calendar-events.org b/.ai/workflows/read-calendar-events.org
new file mode 100644
index 0000000..be66bf4
--- /dev/null
+++ b/.ai/workflows/read-calendar-events.org
@@ -0,0 +1,216 @@
+#+TITLE: Read Calendar Events Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-02-01
+
+* Overview
+
+Workflow for viewing and querying calendar events. Uses the Google Calendar MCP server (preferred) or gcalcli (fallback, personal account only).
+
+* Triggers
+
+- "what's on my calendar"
+- "show me appointments"
+- "summarize my schedule"
+- "what do I have today"
+- "calendar for this week"
+- "any meetings tomorrow"
+
+* Prerequisites
+
+- Google Calendar MCP server configured and authenticated (=@cocal/google-calendar-mcp=)
+- Two accounts available: =personal= (Craig Google) and =work= (Craig Deepsat)
+- Fallback: gcalcli installed (personal account only)
+
+* CRITICAL: Cross-Calendar Visibility
+
+The MCP server has access to both personal and work Google calendars. Use =list-events= with the appropriate =account_id= ("personal" or "work") to see events from each.
+
+For a complete picture, also check the Proton calendar (not accessible via MCP or gcalcli):
+
+#+begin_src bash
+grep "2026-02-18" ~/.emacs.d/data/pcal.org # Proton calendar
+#+end_src
+
+*ALWAYS check Proton calendar* alongside MCP results when showing a full schedule or checking availability.
+
+* Workflow Steps
+
+** 1. Parse Time Range
+
+Interpret the user's request to determine date range:
+
+| Request | Interpretation |
+|--------------------+-------------------------------|
+| "today" | Today only |
+| "tomorrow" | Tomorrow only |
+| "this week" | Next 7 days |
+| "next week" | 7-14 days from now |
+| "this month" | Rest of current month |
+| "April 2026" | That entire month |
+| "next Tuesday" | That specific day |
+| "the 15th" | The 15th of current month |
+
+*No fixed default* - interpret from context. If unclear, ask.
+
+** 2. Determine Calendar Scope
+
+Options:
+- All calendars (default — query both MCP accounts + Proton org file)
+- Personal only: =account_id: "personal"=
+- Work only: =account_id: "work"=
+
+** 3. Query Calendar
+
+*** MCP (preferred)
+Use =list-events= MCP tool with:
+- =account_id=: "personal" or "work" (query both for full picture)
+- =time_min=, =time_max=: ISO 8601 datetime range
+- =calendar_id=: specific calendar (optional, defaults to all)
+
+Also use =get-freebusy= to quickly check availability across calendars.
+
+*** gcalcli (fallback, personal only)
+#+begin_src bash
+gcalcli agenda "start_date" "end_date"
+gcalcli calw # weekly view
+gcalcli calm # monthly view
+#+end_src
+
+** 4. Format Results
+
+Present events in a readable format:
+
+#+begin_example
+=== Tuesday, February 4, 2026 ===
+
+9:00 AM - 10:00 AM Team Standup
+ Location: Conference Room A
+
+2:00 PM - 3:00 PM Dentist Appointment
+ Location: Downtown Dental
+
+=== Wednesday, February 5, 2026 ===
+
+(No events)
+
+=== Thursday, February 6, 2026 ===
+
+10:00 AM - 11:30 AM Project Review
+ Location: Zoom
+#+end_example
+
+** 5. Summarize
+
+Provide a brief summary:
+- Total number of events
+- Busy days vs free days
+- Any all-day events
+- Conflicts (if any)
+
+* gcalcli Command Reference
+
+** Agenda View
+
+#+begin_src bash
+# Default agenda (next few days)
+gcalcli agenda
+
+# Today only
+gcalcli agenda "today" "today 11:59pm"
+
+# This week
+gcalcli agenda "today" "+7 days"
+
+# Specific date range
+gcalcli agenda "2026-03-01" "2026-03-31"
+
+# Specific calendar
+gcalcli --calendar "Work" agenda "today" "+7 days"
+#+end_src
+
+** Calendar Views
+
+#+begin_src bash
+# Weekly calendar (visual)
+gcalcli calw
+
+# Monthly calendar (visual)
+gcalcli calm
+
+# Multiple weeks
+gcalcli calw 2 # Next 2 weeks
+#+end_src
+
+** Search
+
+#+begin_src bash
+# Search by title
+gcalcli search "meeting"
+
+# Search specific calendar
+gcalcli --calendar "Work" search "standup"
+#+end_src
+
+* Output Formats
+
+gcalcli supports different output formats:
+
+| Option | Description |
+|------------------+--------------------------------|
+| (default) | Colored terminal output |
+| --nocolor | Plain text |
+| --tsv | Tab-separated values |
+
+* Time Range Examples
+
+| User Says | gcalcli Command |
+|------------------------+----------------------------------------------|
+| "today" | agenda "today" "today 11:59pm" |
+| "tomorrow" | agenda "tomorrow" "tomorrow 11:59pm" |
+| "this week" | agenda "today" "+7 days" |
+| "next week" | agenda "+7 days" "+14 days" |
+| "February" | agenda "2026-02-01" "2026-02-28" |
+| "next 3 days" | agenda "today" "+3 days" |
+| "rest of the month" | agenda "today" "2026-02-28" |
+
+* Calendars
+
+| Calendar | Access | Account | Notes |
+|---------------------------+--------+----------+--------------------------------|
+| Craig Google | owner | personal | Default personal calendar |
+| Christine | owner | personal | Christine's calendar |
+| Craig Deepsat | owner | work | DeepSat work calendar |
+| Todoist | owner | personal | Todoist integration |
+| Craig Jennings (TripIt) | reader | personal | View only |
+| Holidays in United States | reader | personal | View only |
+| Craig Proton | reader | personal | View only (no API access) |
+
+* Handling No Events
+
+If the date range has no events:
+- Confirm the range was correct
+- Mention the calendar is free
+- Offer to check a different range
+
+Example: "No events found for tomorrow (Feb 3). Your calendar is free that day."
+
+* Error Handling
+
+** No Events Found
+Not an error - calendar may simply be free.
+
+** MCP Authentication Error
+Use =manage-accounts= MCP tool with =action: "add"= to re-authenticate.
+
+** gcalcli Authentication Error
+Run =gcalcli init= to re-authenticate.
+
+** Invalid Date Range
+MCP: Use ISO 8601 format: =YYYY-MM-DDTHH:MM:SS±HH:MM=
+gcalcli: Use explicit dates: =YYYY-MM-DD=
+
+* Related
+
+- [[file:add-calendar-event.org][Add Calendar Event]] - create events
+- [[file:edit-calendar-event.org][Edit Calendar Event]] - modify events
+- [[file:delete-calendar-event.org][Delete Calendar Event]] - remove events
diff --git a/.ai/workflows/retrospective.org b/.ai/workflows/retrospective.org
new file mode 100644
index 0000000..3cf0494
--- /dev/null
+++ b/.ai/workflows/retrospective.org
@@ -0,0 +1,94 @@
+#+TITLE: Retrospective Workflow
+#+DESCRIPTION: How to run a retrospective after major problem-solving sessions
+
+* When to Run a Retrospective
+
+Run after:
+- Major debugging/troubleshooting sessions
+- Complex multi-step implementations
+- Any session where significant friction occurred
+- Sessions lasting more than an hour with trial-and-error
+
+* The Process
+
+** 0. Context Hygiene
+
+Before starting, write out the session context file and check with Craig whether we could compact the context. If the context window collapses, we may forget important details. Writing out the session context prevents this data loss.
+
+** 1. Trigger the Retrospective
+
+Either party can say: "Let's do a retrospective" or "Retrospective time"
+
+** 2. Answer These Questions (Both Parties)
+
+*** What went well?
+Identify patterns worth reinforcing. Be specific.
+
+*** What didn't go well?
+Identify friction points, mistakes, wasted time. No blame, just facts.
+
+*** What behavioral changes should we make?
+Focus on *how we work*, not technical facts.
+- Good: "Confirm before rebooting"
+- Not behavioral: "AMD needs firmware 20260110"
+
+*** What would we do differently next time?
+Specific scenarios and better approaches.
+
+*** Any new principles to add?
+Distill lessons into short, actionable principles for retrospective/PRINCIPLES.org.
+
+** 3. Copy and Update retrospectives/PRINCIPLES.org
+
+Copy the template retrospectives/PRINCIPLES.org.
+
+Using the copied template, add new behavioral principles learned. Keep them:
+- Short and actionable
+- Focused on behavior, not facts
+- Easy to remember and apply
+
+** 4. Create Retrospective Record
+
+Save to =.ai/retrospectives/YYYY-MM-DD-topic.org= with:
+- Summary of what happened
+- Answers to the questions above
+- Link to detailed session doc if exists
+
+** 5. Commit Changes
+
+Commit PRINCIPLES.org updates and retrospective record.
+
+* PRINCIPLES.org Structure
+
+#+BEGIN_SRC org
+,* How We Work Together
+,** Principle Name
+- Bullet points explaining the principle
+- When it applies
+- Why it matters
+
+,* Checklists
+,** Checklist Name
+- [ ] Step 1
+- [ ] Step 2
+#+END_SRC
+
+* Integration with Session Startup
+
+Add to project's protocols.org or session startup:
+- Check if PRINCIPLES.org was updated since last session
+- Review any new principles before starting work
+
+* Example Principles (Starters)
+
+** Sync Before Action
+- Confirm before destructive or irreversible actions
+- State what you're about to do and wait for go-ahead
+
+** Verify Assumptions
+- When something "should work" but doesn't, question the assumption
+- Test one variable at a time
+
+** Clean Up After Yourself
+- Reset temporary changes before finishing
+- Verify system is in expected state
diff --git a/.ai/workflows/send-email.org b/.ai/workflows/send-email.org
new file mode 100644
index 0000000..cfd7adf
--- /dev/null
+++ b/.ai/workflows/send-email.org
@@ -0,0 +1,198 @@
+#+TITLE: Email Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-01-26
+
+* Overview
+
+This workflow sends emails with optional attachments via msmtp using the cmail account (c@cjennings.net via Proton Bridge).
+
+* When to Use This Workflow
+
+When Craig says:
+- "email workflow" or "send an email"
+- "email [person] about [topic]"
+- "send [file] to [person]"
+
+* Required Information
+
+Before sending, gather and confirm:
+
+1. **To:** (required) - recipient email address(es)
+2. **CC:** (optional) - carbon copy recipients
+3. **BCC:** (optional) - blind carbon copy recipients
+4. **Subject:** (required) - email subject line
+5. **Body:** (required) - email body text
+6. **Attachments:** (optional) - file path(s) to attach
+
+* The Workflow
+
+** Step 1: Gather Missing Information
+
+If any required fields are missing, prompt Craig:
+
+#+begin_example
+To send this email, I need:
+- To: [who should receive this?]
+- Subject: [what's the subject line?]
+- Body: [what should the email say?]
+- Attachments: [any files to attach?]
+- CC/BCC: [anyone to copy?]
+#+end_example
+
+** Step 2: Validate Email Addresses
+
+Look up all recipient names/emails in the contacts file:
+
+#+begin_src bash
+grep -i "[name or email]" ~/sync/org/contacts.org
+#+end_src
+
+**Note:** If contacts.org is empty, check for sync-conflict files:
+#+begin_src bash
+ls ~/sync/org/contacts*.org
+#+end_src
+
+For each recipient:
+1. Search contacts by name or email
+2. Confirm the email address matches
+3. If name not found, ask Craig to confirm the email is correct
+4. If multiple emails for a contact, ask which one to use
+
+** Step 3: Confirm Before Sending
+
+Display the complete email for review:
+
+#+begin_example
+Ready to send:
+
+From: c@cjennings.net
+To: [validated email(s)]
+CC: [if any]
+BCC: [if any]
+Subject: [subject]
+
+[body text]
+
+Attachments: [list files if any]
+
+Send this email? [Y/n]
+#+end_example
+
+** Step 4: Send the Email
+
+Use Python to construct MIME message and pipe to msmtp:
+
+#+begin_src python
+python3 << 'EOF' | msmtp -a cmail [recipient]
+import sys
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+from email.mime.application import MIMEApplication
+from email.utils import formatdate
+import os
+
+msg = MIMEMultipart()
+msg['From'] = 'c@cjennings.net'
+msg['To'] = '[to_address]'
+# msg['Cc'] = '[cc_address]' # if applicable
+# msg['Bcc'] = '[bcc_address]' # if applicable
+msg['Subject'] = '[subject]'
+msg['Date'] = formatdate(localtime=True)
+
+body = """[body text]"""
+msg.attach(MIMEText(body, 'plain'))
+
+# For each attachment:
+# pdf_path = '/path/to/file.pdf'
+# with open(pdf_path, 'rb') as f:
+# attachment = MIMEApplication(f.read(), _subtype='pdf')
+# attachment.add_header('Content-Disposition', 'attachment', filename='filename.pdf')
+# msg.attach(attachment)
+
+print(msg.as_string())
+EOF
+#+end_src
+
+**Important:** When there are CC or BCC recipients, pass ALL recipients to msmtp:
+#+begin_src bash
+python3 << 'EOF' | msmtp -a cmail to@example.com cc@example.com bcc@example.com
+#+end_src
+
+** Step 5: Verify Delivery
+
+Check the msmtp log for confirmation:
+
+#+begin_src bash
+tail -3 ~/.msmtp.cmail.log
+#+end_src
+
+Look for: ~smtpstatus=250~ and ~exitcode=EX_OK~
+
+** Step 6: Sync to Sent Folder (Optional)
+
+If Craig wants the email in his Sent folder:
+
+#+begin_src bash
+mbsync cmail
+#+end_src
+
+* msmtp Configuration
+
+The cmail account should be configured in ~/.msmtprc:
+
+#+begin_example
+account cmail
+tls_certcheck off
+auth on
+host 127.0.0.1
+port 1025
+protocol smtp
+from c@cjennings.net
+user c@cjennings.net
+passwordeval "cat ~/.config/.cmailpass"
+tls on
+tls_starttls on
+logfile ~/.msmtp.cmail.log
+#+end_example
+
+**Note:** ~tls_certcheck off~ is used because Proton Bridge uses self-signed certificates on localhost.
+
+* Attachment Handling
+
+** Supported Types
+
+Common MIME subtypes:
+- PDF: ~_subtype='pdf'~
+- Images: ~_subtype='png'~, ~_subtype='jpeg'~
+- Text: ~_subtype='plain'~
+- Generic: ~_subtype='octet-stream'~
+
+** Multiple Attachments
+
+Add multiple attachment blocks before ~print(msg.as_string())~
+
+* Troubleshooting
+
+** Password File Missing
+Ensure ~/.config/.cmailpass exists with the Proton Bridge SMTP password.
+
+** TLS Certificate Errors
+Use ~tls_certcheck off~ in msmtprc for Proton Bridge (localhost only).
+
+** Proton Bridge Not Running
+Start Proton Bridge before sending. Check if port 1025 is listening:
+#+begin_src bash
+ss -tlnp | grep 1025
+#+end_src
+
+* Example Usage
+
+Craig: "email workflow - send the November 3rd SOV to Christine"
+
+Claude:
+1. Searches contacts for "Christine" -> finds cciarmello@gmail.com
+2. Asks for subject and body if not provided
+3. Locates the SOV file in assets/
+4. Shows confirmation
+5. Sends via msmtp
+6. Verifies delivery in log
diff --git a/.ai/workflows/startup.org b/.ai/workflows/startup.org
new file mode 100644
index 0000000..19045d3
--- /dev/null
+++ b/.ai/workflows/startup.org
@@ -0,0 +1,178 @@
+#+TITLE: Startup Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-04-25
+
+* Overview
+
+This workflow runs automatically at the beginning of EVERY session. It gives Claude project context, syncs templates, discovers available workflows, and determines session priorities. Do NOT ask Craig if he wants to run it — just execute it.
+
+The workflow is structured into four phases. *Phase A.0* is a sequential pre-flight; *Phase A and Phase B should each run as a single batch of parallel tool calls* — sending one message with multiple Bash / Read calls in it, not sequential round-trips. Phase C is interactive and runs sequentially.
+
+* The Workflow
+
+** Phase A.0 — Pre-flight: refresh claude-templates and project repo (sequential, runs first)
+
+Two refreshes happen before Phase A. Both are sequential pre-steps, not part of Phase A's parallel batch. Run them as two separate Bash calls in order.
+
+*** Refresh claude-templates
+
+Phase A's rsync commands copy from =~/projects/claude-templates/= into the project's =.ai/= directory. If that source repo is behind its own =origin/main=, the rsync silently reverts committed template updates in the project, dirtying the working tree. Pull claude-templates first so the rsync runs against current content.
+
+#+begin_src bash
+ct="$HOME/projects/claude-templates"
+if [ -d "$ct/.git" ]; then
+ if (cd "$ct" && git diff --quiet --ignore-submodules HEAD -- 2>/dev/null); then
+ (cd "$ct" && git pull --ff-only origin main 2>&1) | tail -3
+ else
+ echo "claude-templates: dirty working tree — using as-is, skipping pull"
+ fi
+else
+ echo "claude-templates: not a git checkout — skipping"
+fi
+#+end_src
+
+Behavior:
+- *Clean working tree* → fast-forward pull. =git pull --ff-only= refuses any merge or rebase, so the operation is either a no-op (already current) or a clean advance.
+- *Dirty working tree* → skip the pull. Don't auto-stash and don't auto-merge — those would either lose work or invite conflicts at the worst possible moment (session start).
+- *Non-fast-forward history* → =--ff-only= aborts with an error. Surface that to the user; the rsync still proceeds against the working tree as-is.
+
+*** Refresh project repo (cwd)
+
+Pull down whatever's been pushed to the project's remotes since the last session — could be commits Craig made on another machine, teammate pushes, or any branch that advanced upstream. Without this, the session starts from a stale local view and any new branch work happens on top of an out-of-date base.
+
+#+begin_src bash
+if [ -d .git ]; then
+ git fetch --all --prune 2>&1 | tail -5
+
+ current=$(git symbolic-ref --short HEAD 2>/dev/null)
+ dirty=0
+ if ! git diff --quiet --ignore-submodules HEAD -- 2>/dev/null \
+ || [ -n "$(git status --porcelain --untracked-files=no)" ]; then
+ dirty=1
+ fi
+
+ git for-each-ref --format='%(refname:short)' refs/heads/ | while read branch; do
+ upstream=$(git rev-parse --abbrev-ref "${branch}@{upstream}" 2>/dev/null) || continue
+ counts=$(git rev-list --left-right --count "${upstream}...${branch}" 2>/dev/null) || continue
+ behind=$(echo "$counts" | cut -f1)
+ ahead=$(echo "$counts" | cut -f2)
+
+ if [ "$behind" -gt 0 ] && [ "$ahead" -eq 0 ]; then
+ if [ "$branch" = "$current" ]; then
+ if [ "$dirty" -eq 0 ]; then
+ git merge --ff-only "$upstream" >/dev/null 2>&1 \
+ && echo " $branch: fast-forwarded $behind commits"
+ else
+ echo " $branch: behind $behind — dirty tree, fetched only"
+ fi
+ else
+ git fetch . "${upstream}:${branch}" >/dev/null 2>&1 \
+ && echo " $branch: fast-forwarded $behind commits (non-checkout)"
+ fi
+ elif [ "$ahead" -gt 0 ] && [ "$behind" -gt 0 ]; then
+ echo " $branch: diverged ($ahead ahead, $behind behind) — leaving alone"
+ fi
+ done
+else
+ echo "project repo: not a git checkout — skipping"
+fi
+#+end_src
+
+Behavior, per branch:
+- *Behind only, current branch, clean tree* → =git merge --ff-only= advances HEAD.
+- *Behind only, current branch, dirty tree* → fetched but not advanced. Surface so Craig can ff manually after dealing with the dirty state.
+- *Behind only, non-checkout branch* → =git fetch . upstream:branch= advances the ref without touching the working tree.
+- *Diverged* (ahead and behind) → leave alone. Surface for Craig to resolve. Don't auto-rebase or auto-merge.
+- *Ahead only* or *up to date* → silent no-op.
+
+Phase A's rsyncs depend on the claude-templates refresh completing first. The project-repo refresh has no such dependency, but lives here for symmetry with the wrap-up's "push all local branches" step.
+
+** Phase A — Initial fan-out (one parallel batch)
+
+These calls have no dependencies on each other. Issue them all together in one message:
+
+1. =date "+%A %Y-%m-%d %H:%M %Z"= — accurate timestamp.
+2. Check whether =.ai/session-context.org= exists (e.g. =[ -e .ai/session-context.org ] && echo present || echo absent=).
+3. =rsync -a ~/projects/claude-templates/.ai/protocols.org .ai/protocols.org=.
+4. =rsync -a --delete ~/projects/claude-templates/.ai/workflows/ .ai/workflows/=.
+5. =rsync -a --delete ~/projects/claude-templates/.ai/scripts/ .ai/scripts/=.
+6. =\ls -t .ai/sessions/ 2>/dev/null | head -5= — list 5 most recent session files. The backslash bypasses any =ls= alias in the user's profile. Without it, bare =ls -t= silently returns no output under =exa= (a common =ls= replacement) — which makes a sessions directory full of files look empty, and the agent then skips Phase B step 2.
+7. =\ls -la inbox/ 2>/dev/null= — inventory the inbox. Same reason for the backslash escape, applied uniformly across the Phase A =ls= calls.
+8. =cross-agent-status 2>/dev/null || true= — snapshot of pending cross-agent messages across local projects. This is layer A of the cold-start design from =cross-agent-comms.org=: pending messages from other agents (delivered while no session was active here) get surfaced on session start. The =|| true= keeps Phase A from failing if =cross-agent-status= isn't installed yet — older projects without the script still boot cleanly. If HALT is active, =cross-agent-status= prints a banner; surface that prominently in Phase C.
+9. Read =.ai/notes.org= — Project-Specific Context, Active Reminders, Pending Decisions sections (skip About This File).
+10. Read =.ai/project-workflows/startup-extras.org= if it exists.
+
+Notes on the rsync commands:
+- Trailing slashes on both source and destination matter — they tell rsync to sync /contents/ rather than nest a directory inside.
+- =--delete= on the directory syncs lets retired template files actually disappear from each project on next startup.
+- protocols.org is a single file, no =--delete= needed.
+
+Rationale: Every call in Phase A is read-only or writes to a distinct path. Running them sequentially wastes round-trips; running them in parallel gives Claude the complete starting picture in one round-trip.
+
+** Phase B — Dependent fan-out (one parallel batch)
+
+These calls depend on Phase A outputs, but are independent of each other. Issue them as a single parallel batch once Phase A returns:
+
+1. *Read =.ai/session-context.org= if Phase A reported it exists.* The file is the crash-recovery anchor — if it's there, the previous session was interrupted and the context lives only in this file.
+2. *Read each of the 5 most recent session files* from Phase A's =\ls -t .ai/sessions/= output. Read just the =* Summary= section of each — not the full file. The Summary gives Active Goal / Decisions / Data Collected / Findings / Files Modified / Next Steps. That's enough to pick up where things left off. Drill into a specific =* Session Log= later only if you need the /why/ or sequence on something. *If Phase A's listing came back empty, sanity-check with =\ls -la .ai/sessions/= before treating empty as definitive — sessions/ should normally be populated, and an empty result usually means the listing got swallowed somewhere, not that the directory is genuinely empty.*
+3. *Read each new inbox file* from Phase A's =\ls -la inbox/= output. For =.eml= files, defer to Phase C — those need the extract script (below) rather than a raw Read.
+4. *Process pending cross-agent messages.* For each project with a pending count >0 in Phase A's =cross-agent-status= output (typically the current project; cross-project pending is surfaced too but only acted on if the user asks), run =cross-agent-recv <message-file>= on the file path =cross-agent-status= named. The script returns a structured decision (=process= / =dedup= / =query= / =reject=) per the protocol. For =process=, read the message body to determine the action. For =query=, prepare a clarifying reply. For =reject=, surface to user with the reason. For =dedup=, no action — silent retry already handled. Surface all decisions in Phase C alongside other findings.
+
+Rationale: Reads are independent and benign. Batching them means the whole session-history view + inbox view lands in one round-trip instead of one per file.
+
+** Phase C — Synthesis + interactive
+
+This phase touches the user and runs sequentially:
+
+1. *Surface findings from Phase A and B:*
+ - If =session-context.org= existed, summarize what was in flight at the crash point and ask whether to resume.
+ - Surface Active Reminders from notes.org immediately.
+ - Mention Pending Decisions from notes.org.
+ - Briefly note significant template updates noticed during sync (new workflows, protocol changes).
+ - *Surface pending cross-agent messages.* If =cross-agent-status= reported any pending messages, list them with their =cross-agent-recv= decision (process / query / reject) per file. For =process= messages in this project's inbox, propose handling now or after the current task. For pending in other projects, mention the count so the user knows to switch projects when ready. If HALT was active, surface that prominently — cross-agent activity is paused until =cross-agent-resume= clears it.
+2. *Process inbox if non-empty.* Mandatory — don't ask, just do it. For each file: determine action, recommend filing, get approval, move. For =.eml= files use the extract script (not raw Read):
+ #+begin_src bash
+ # View mode — print metadata and body, extract attachments alongside EML
+ python3 .ai/scripts/eml-view-and-extract-attachments.py inbox/message.eml
+
+ # Pipeline mode — extract, auto-rename, refile to output dir, clean up
+ python3 .ai/scripts/eml-view-and-extract-attachments.py inbox/message.eml --output-dir assets/target-dir/
+ #+end_src
+ The script handles metadata extraction, HTML-to-text conversion, attachment extraction, and auto-renaming to =YYYY-MM-DD-HHMM-Sender-TYPE-Description.ext=. See [[file:../scripts/eml-view-and-extract-attachments-readme.org][EML script readme]].
+3. *Execute project-specific startup extras* (the contents of =.ai/project-workflows/startup-extras.org= read in Phase A). If the file didn't exist, skip.
+4. *Ask about priorities.* "What would you like to work on, or is there something urgent you need?"
+ - If urgent: proceed immediately.
+ - If not: surface the top 3 priority A or B tasks in todo.org plus recent work as context.
+
+Rationale: User-facing work and decisions can't be parallelized — they have to happen one at a time so the user can react.
+
+* Workflow discovery (on demand, not at startup)
+
+Two directories hold workflows:
+- =.ai/workflows/= — template workflows (synced from claude-templates, never edit in project).
+- =.ai/project-workflows/= — project-specific workflows (never touched by sync).
+
+When the user says "let's run/do the [X] workflow" (or otherwise references a workflow by topic):
+
+1. *Read =.ai/workflows/INDEX.org=.* It maps trigger phrases to workflow filenames so you don't have to read each workflow's "When to Use" section to route. Project-workflows aren't in the index — handle those via =ls= in step 2.
+2. *List both directories:* =ls -1 .ai/workflows/ .ai/project-workflows/ 2>/dev/null=.
+3. *Drift check.* Compare the =.org= files in =.ai/workflows/= against the entries in INDEX.org (excluding INDEX.org itself). If an index entry points at a deleted file, or a file in the directory has no index entry, surface the mismatch to the user and offer to update INDEX.org. Don't silently route around it.
+4. *Match the request* against the index trigger phrases first, then against project-workflow filenames if no index hit.
+5. *Read and execute* the matching file.
+6. *No match* → offer to create via =create-workflow=; new workflows go to =.ai/project-workflows/= and project-specific ones don't go in INDEX.org.
+7. *Project extension.* As one of the very last steps in the matched workflow's flow, check if =.ai/project-workflows/= contains a file with the same name as the template that just ran. If yes, read and execute it as *additional* steps appended to the workflow — not a replacement. The project file contains add-on steps that pick up where the template's main flow ends. Surface the extension once per session ("Project has additional steps for send-email.org — running them now"). Only applies when the matched workflow came from =.ai/workflows/=; project-only workflows have no template to extend. This mirrors the startup-extras.org pattern: projects extend template behavior without forking the template repo.
+
+The index is the catalog; the directory is the truth. Drift between them is a bug — catching it on demand keeps the index honest without paying the read cost on every session.
+
+* Common Mistakes
+
+1. *Running Phase A sequentially.* Send all Phase A calls in one message — sequential rsync + ls + read costs round-trips for nothing.
+2. *Reading the entire notes.org file* — only Project-Specific Context, Active Reminders, Pending Decisions.
+3. *Skipping template sync* — projects fall behind on rule changes.
+4. *Skipping Phase A.0* — rsync runs against a stale claude-templates checkout and silently reverts committed template updates in the project's =.ai/=, dirtying the working tree at session start.
+5. *Auto-stashing or auto-merging in Phase A.0* — don't. If claude-templates has uncommitted edits or a non-fast-forward history, leave it alone and let the rsync run against the working tree as-is.
+6. *Not checking for session-context.org* — lose context from crashed sessions.
+7. *Forgetting to surface Active Reminders* — Craig misses critical items.
+8. *Asking if Craig wants inbox processed* — it's mandatory, not optional.
+9. *Announcing "session start complete"* — just begin working on the chosen task.
+10. *Reading full session files instead of just the Summary section* — wastes context for past noise that lives in the Session Log.
diff --git a/.ai/workflows/status-check.org b/.ai/workflows/status-check.org
new file mode 100644
index 0000000..efff16d
--- /dev/null
+++ b/.ai/workflows/status-check.org
@@ -0,0 +1,178 @@
+#+TITLE: Status Check Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-02-02
+
+* Overview
+
+This workflow defines how Claude monitors and reports on long-running jobs (10+ minutes). It provides regular status updates, ETA estimates, and clear completion/failure signals with notifications.
+
+Uses the =notify= command for completion/failure notifications.
+
+* Problem We're Solving
+
+Long-running jobs create uncertainty:
+
+1. *Silent failures* - Jobs fail without notification, wasting time
+2. *Missed completions* - Job finishes but user doesn't notice for hours
+3. *No visibility* - User doesn't know if it's safe to context-switch
+4. *Unknown ETAs* - No sense of when to check back
+
+*Impact:* Delayed follow-up, wasted time, uncertainty about when to return attention to the task.
+
+* Exit Criteria
+
+The workflow is successful when:
+
+1. Claude proactively monitors long-running tasks (10+ minutes)
+2. Status updates arrive every 5 minutes with progress and ETA
+3. Completion/failure is clearly announced with notification
+4. Failures trigger investigation or confirmation before action
+
+* When to Use This Workflow
+
+Use automatically when:
+- Network transfers (rsync, scp, file sync)
+- Test suites expected to run long
+- Build processes
+- Any job estimated at 10+ minutes
+
+Use when Craig requests:
+- "Keep me posted on this"
+- "Provide status checks on this job"
+- "Let me know when it's done"
+- "Monitor this for me"
+
+* Approach: How We Work Together
+
+** Step 1: Initial Status
+
+When a long-running job starts, report:
+
+#+begin_example
+HH:MM - description - ETA
+19:10 - Starting file transfer of ~/videos to wolf - ETA ~30 minutes
+#+end_example
+
+Format: One line, under 120 characters.
+
+** Step 2: Progress Updates (Every 5 Minutes)
+
+Report progress with updated ETA:
+
+#+begin_example
+HH:MM - job description - update - ETA
+19:15 - File transfer to wolf - now transferring files starting with "h" - ETA ~25 minutes
+#+end_example
+
+If ETA changes significantly, explain why:
+
+#+begin_example
+19:20 - File transfer to wolf - network speed dramatically reduced - ETA ~40 minutes
+19:25 - File transfer to wolf - network speed recovered - ETA ~10 minutes
+#+end_example
+
+** Step 3: Completion
+
+On success:
+
+#+begin_example
+HH:MM - job description SUCCESS! - elapsed time
+19:35 - File transfer to wolf SUCCESS! - elapsed: ~25 minutes
+#+end_example
+
+Then:
+1. Play success sound and show persistent notification
+2. Report any relevant details (files transferred, tests passed, etc.)
+
+#+begin_src bash
+notify success "Job Complete" "File transfer to wolf finished" --persist
+#+end_src
+
+** Step 4: Failure
+
+On failure:
+
+#+begin_example
+HH:MM - job description FAILURE! - elapsed time
+Reason: Network connectivity dropped. Should I investigate, restart, or something else?
+#+end_example
+
+Then:
+1. Play failure sound and show persistent notification
+2. Investigate the reason OR ask for confirmation before diagnosing
+3. Unless fix is trivial and obvious, ask before fixing or rerunning
+
+#+begin_src bash
+notify fail "Job Failed" "File transfer to wolf - network error" --persist
+#+end_src
+
+* Status Format Reference
+
+| Situation | Format |
+|-----------+----------------------------------------------------------|
+| Initial | =HH:MM - description - ETA= |
+| Progress | =HH:MM - job description - update - ETA= |
+| Success | =HH:MM - job description SUCCESS! - elapsed time= |
+| Failure | =HH:MM - job description FAILURE! - elapsed time= + reason |
+
+All status lines should be under 120 characters.
+
+* Principles to Follow
+
+** Reliability
+Updates every 5 minutes, no exceptions. Status checks are never considered an interruption.
+
+** Transparency
+Honest progress reporting. If ETA changes, explain why. Don't silently adjust estimates.
+
+** ETA Honesty
+- Always try to estimate, even if uncertain
+- If truly unknown, say "ETA unknown"
+- When ETA changes significantly, explain the reason
+- A wrong estimate with explanation is better than no estimate
+
+** Fail Loudly
+Never let failures go unnoticed. Always announce failures with sound and persistent notification.
+
+** Ask Before Acting
+On failure, investigate or ask - don't automatically retry or fix unless the solution is trivial and obvious.
+
+* Implementation
+
+** Monitoring with Sleep (Blocking)
+
+To ensure 5-minute status updates happen reliably, use blocking sleep loops.
+Do NOT use =at= for reminders - it only notifies the user, not Claude.
+
+#+begin_src bash
+# Check status, sleep 5 min, repeat until job completes
+sleep 300 && date "+%H:%M" && tail -15 /path/to/output.log
+#+end_src
+
+This blocks the conversation but guarantees regular updates. The user has
+explicitly approved this approach - status checks are never an interruption.
+
+** Background Jobs
+
+For jobs run in background via Bash tool:
+1. Start job with =run_in_background: true=
+2. Note the output file path
+3. Use blocking sleep loop to check output every 5 minutes
+4. Continue until job completes or fails
+
+* Notification Reference
+
+#+begin_src bash
+# Success
+notify success "Job Complete" "description" --persist
+
+# Failure
+notify fail "Job Failed" "description" --persist
+#+end_src
+
+* Living Document
+
+Update as patterns emerge:
+- Which jobs benefit most from monitoring
+- ETA estimation techniques that work well
+- Common failure modes and responses
diff --git a/.ai/workflows/summarize-emails.org b/.ai/workflows/summarize-emails.org
new file mode 100644
index 0000000..6ac5e6f
--- /dev/null
+++ b/.ai/workflows/summarize-emails.org
@@ -0,0 +1,243 @@
+#+TITLE: Summarize Emails Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-02-14
+
+* Overview
+
+This workflow filters out marketing noise and surfaces only emails that matter — messages from real people, businesses Craig works with, or anything that needs his attention. It chains together existing tools: sync-email, mu queries, the extract script, and Claude's judgment to produce a curated summary.
+
+* Problem We're Solving
+
+Craig's inbox contains a mix of important correspondence and marketing noise. Manually scanning through emails to find what matters wastes time and risks missing something important buried under promotional messages. This workflow automates the filtering and presents a concise summary of only the emails that deserve attention.
+
+* Exit Criteria
+
+Summary is complete when:
+1. All qualifying emails in the requested scope have been reviewed
+2. Summary presented to Craig, grouped by account
+3. Temp directory cleaned up
+4. Session context file updated
+
+* When to Use This Workflow
+
+When Craig says:
+- "summarize my emails", "what emails do I have", "anything important in my inbox", "email summary"
+- "any unread emails", "check my unread"
+- "any starred emails", "show flagged emails"
+- "emails from [person]", "what has [person] sent me"
+- "emails also sent to Christine"
+
+* The Workflow
+
+** Step 1: Context Hygiene
+
+Before starting, write out the session context file and ask Craig if he wants to compact first. This workflow is token-heavy (reading multiple full emails). If the context window compresses mid-workflow, we may lose important details. Writing out session context prevents this data loss.
+
+** Step 2: Parse Scope
+
+Determine the mu query from Craig's request. Supported scope types:
+
+| Scope Type | Example Request | mu Query |
+|--------------------+--------------------------------+---------------------------------------|
+| Date range | "last week", "since Monday" | =date:1w..now=, =date:2026-02-10..now= |
+| Unread only | "unread emails" | =flag:unread= |
+| Unread + date | "unread emails this week" | =flag:unread date:1w..now= |
+| Starred/flagged | "starred emails" | =flag:flagged= (with optional date) |
+| From a sender | "emails from Dan" | =from:dan= (with optional =--maxnum=) |
+| Sent to someone | "emails also sent to Christine"| =to:christine OR cc:christine= |
+
+Scopes can be combined. For example, "unread emails from Dan this week" becomes =flag:unread from:dan date:1w..now=.
+
+If no scope is provided or it's ambiguous, *ask Craig* before querying.
+
+** Step 3: Offer to Sync
+
+Ask Craig if he wants to sync first (=mbsync -a && mu index=). Don't auto-sync. If Craig confirms, run the [[file:sync-email.org][sync-email workflow]].
+
+** Step 4: Query mu
+
+Append =NOT flag:list= to the query to exclude emails with List-* headers (catches most mailing list / marketing / bulk mail).
+
+#+begin_src bash
+mu find --sortfield=date --reverse --fields="d f t s l" [query] NOT flag:list
+#+end_src
+
+Output fields: date, from, to, subject, path. Sorted by date, newest first.
+
+** Step 5: Copy Qualifying Emails to Temp Directory
+
+Create an isolated temp directory for the summary work:
+
+#+begin_src bash
+mkdir -p ./tmp/email-summary-YYYY-MM-DD/
+#+end_src
+
+Copy the EML files from their maildir paths into this directory.
+
+*CRITICAL: Copy FROM =~/.mail/=, never modify =~/.mail/=.*
+
+#+begin_src bash
+cp ~/.mail/gmail/INBOX/cur/message.eml ./tmp/email-summary-YYYY-MM-DD/
+#+end_src
+
+** Steps 6 & 7: Header Inspection + Address Verification (one parallel batch)
+
+For each copied email, run BOTH the second-pass header inspection (Step 6 criteria below) AND the addressed-to-Craig check (Step 7 criteria below) as a single parallel batch — one Read per email per check, all issued together in one message. The two checks are independent per-email and don't depend on each other's outcome.
+
+After the batch returns, discard any email that fails either check, then proceed to Step 8 with the survivors.
+
+*** Step 6 criteria — Second-Pass Header Inspection
+
+Check headers for additional marketing signals that mu's =flag:list= might miss. Discard emails that match any of:
+
+**** Bulk Sender Tools
+=X-Mailer= or =X-Mailtool= containing: Mailchimp, ExactTarget, Salesforce, SendGrid, Constant Contact, Campaign Monitor, HubSpot, Marketo, Brevo, Klaviyo
+
+**** Bulk Precedence
+=Precedence: bulk= or =Precedence: list=
+
+**** Bulk Sender Patterns
+=X-PM-Message-Id= patterns typical of bulk senders
+
+**** Marketing From Addresses
+From address matching: =noreply@=, =no-reply@=, =newsletter@=, =marketing@=, =promotions@=
+
+*** Step 7 criteria — Verify Addressed to Craig
+
+Check To/CC headers contain one of Craig's addresses:
+- =craigmartinjennings@gmail.com=
+- =c@cjennings.net=
+
+Discard BCC-only marketing blasts where Craig isn't in To/CC.
+
+** Step 8: Run Extract Script on Survivors
+
+Use =eml-view-and-extract-attachments.py= in stdout mode (no =--output-dir=) to read each email's content:
+
+#+begin_src bash
+python3 .ai/scripts/eml-view-and-extract-attachments.py ./tmp/email-summary-YYYY-MM-DD/message.eml
+#+end_src
+
+This prints headers and body text to stdout without creating any files in the project.
+
+** Step 9: Triage and Summarize
+
+For each email, apply judgment:
+- *Clearly needs Craig's attention* → summarize it (who, what, any action needed)
+- *Unsure whether important* → summarize it with a note about why it might matter
+- *Clearly unimportant* (automated notifications, receipts for known purchases, etc.) → mention it briefly but don't summarize in detail
+
+** Step 10: Present Summary
+
+Group by account (gmail / cmail). For each email show:
+- From, Subject, Date
+- Brief summary of content and any action needed
+- Flag anything time-sensitive
+
+Example output format:
+
+#+begin_example
+** Gmail
+
+1. From: Dan Smith | Subject: Project update | Date: Feb 14
+ Dan is asking about the timeline for the next milestone. Needs a reply.
+
+2. From: Dr. Lee's Office | Subject: Appointment confirmation | Date: Feb 13
+ Appointment confirmed for Feb 20 at 2pm. No action needed.
+
+** cmail
+
+1. From: Christine | Subject: Weekend plans | Date: Feb 14
+ Asking about Saturday dinner. Needs a reply.
+
+** Skipped (not important)
+- Order confirmation from Amazon (Feb 13)
+- GitHub notification: CI passed (Feb 14)
+#+end_example
+
+** Step 11: Clean Up
+
+Remove the temp directory:
+
+#+begin_src bash
+rm -rf ./tmp/email-summary-YYYY-MM-DD/
+#+end_src
+
+If =./tmp/= is now empty, remove it too.
+
+** Step 12: Post-Summary Actions
+
+After presenting the summary, ask Craig if he wants to:
+
+*** Star emails
+
+Star specific emails by passing their maildir paths:
+
+#+begin_src bash
+python3 .ai/scripts/maildir-flag-manager.py star --reindex /path/to/message1 /path/to/message2
+#+end_src
+
+To also mark starred emails as read in one step:
+
+#+begin_src bash
+python3 .ai/scripts/maildir-flag-manager.py star --mark-read --reindex /path/to/message1
+#+end_src
+
+*** Mark reviewed emails as read
+
+Mark all unread INBOX emails as read across both accounts:
+
+#+begin_src bash
+python3 .ai/scripts/maildir-flag-manager.py mark-read --reindex
+#+end_src
+
+Or mark specific emails as read:
+
+#+begin_src bash
+python3 .ai/scripts/maildir-flag-manager.py mark-read --reindex /path/to/message1 /path/to/message2
+#+end_src
+
+Use =--dry-run= to preview what would change without modifying anything.
+
+The script uses atomic =os.rename()= directly on maildir files — the same mechanism mu4e uses. Flag changes are persisted to the filesystem so mbsync picks them up on the next sync.
+
+*** Delete emails (future)
+=mu= supports =mu remove= to delete messages from the filesystem and database. Not yet integrated into this workflow — explore when ready.
+
+** Step 13: Context Hygiene (Completion)
+
+Write out session-context.org again after the summary is presented, capturing what was reviewed and any action items identified.
+
+* Principles
+
+- *=maildir-flag-manager.py= for flag changes* — use the script for mark-read and star operations; it uses atomic =os.rename()= on maildir files (same mechanism as mu4e) and mbsync syncs changes on next run
+- *Ask before syncing* — don't auto-sync; Craig may have already synced or may not want to wait
+- *Ask before querying* — if scope is ambiguous, clarify rather than guess
+- *Filter aggressively, surface generously* — when in doubt about whether an email is marketing, filter it out; when in doubt about whether it's important, include it in the summary
+- *One pass through the extract script* — don't re-read emails; read once and summarize
+- *Stdout mode only* — use the extract script without =--output-dir= to avoid creating files in the project
+- *Clean up always* — remove the temp directory even if errors occur partway through
+
+* Tools Reference
+
+| Tool | Purpose |
+|-------------------------------------+--------------------------------------|
+| mbsync / mu index | Sync and index mail |
+| mu find | Query maildir for matching emails |
+| eml-view-and-extract-attachments.py | Read email content (stdout mode) |
+| maildir-flag-manager.py | Mark read, star (batch flag changes) |
+
+* Files Referenced
+
+| File | Purpose |
+|------------------------------------------------+-------------------------|
+| [[file:sync-email.org][.ai/workflows/sync-email.org]] | Sync step |
+| [[file:find-email.org][.ai/workflows/find-email.org]] | mu query patterns |
+| .ai/scripts/eml-view-and-extract-attachments.py | Extract script |
+| .ai/scripts/maildir-flag-manager.py | Flag management script |
+| =~/.mail/gmail/= | Gmail maildir (READ ONLY) |
+| =~/.mail/cmail/= | cmail maildir (READ ONLY) |
+
+* Living Document
+
+Update this workflow as we discover new marketing patterns to filter, useful query combinations, or improvements to the summary format.
diff --git a/.ai/workflows/sync-email.org b/.ai/workflows/sync-email.org
new file mode 100644
index 0000000..52a7caf
--- /dev/null
+++ b/.ai/workflows/sync-email.org
@@ -0,0 +1,108 @@
+#+TITLE: Sync Email Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-02-01
+
+* Overview
+
+This workflow syncs local maildir with remote email servers (Gmail and cmail/Proton) and updates the mu index for local searching.
+
+* Problem We're Solving
+
+Email lives on remote servers. To search or read emails locally, the local maildir needs to be updated from the servers. Without syncing, local tools (mu4e, mu find) only see stale data.
+
+* Exit Criteria
+
+Sync is complete when:
+1. mbsync finishes successfully (exit code 0)
+2. mu index completes successfully
+3. Sync summary is reported (new messages, any errors)
+
+* When to Use This Workflow
+
+When Craig says:
+- "sync email" or "sync mail"
+- "pull new mail"
+- "check for new email"
+- Before any workflow that needs to search or read local email
+
+* The Workflow
+
+** Step 1: Sync All Accounts
+
+Run mbsync to pull mail from all configured accounts:
+
+#+begin_src bash
+mbsync -a
+#+end_src
+
+This syncs both gmail and cmail accounts as configured in ~/.mbsyncrc.
+
+** Step 2: Index Mail
+
+Update the mu database to make new mail searchable:
+
+#+begin_src bash
+mu index
+#+end_src
+
+mu index is incremental by default - it only indexes new/changed messages.
+
+** Step 3: Report Results
+
+Report to Craig:
+- Number of new messages pulled (if visible in mbsync output)
+- Any errors encountered
+- Confirmation that sync and index completed
+
+** Handling Errors
+
+If errors occur, diagnose at that step. Common issues:
+
+*** UIDVALIDITY Errors
+
+UIDVALIDITY errors occur when UIDs change on the server (Proton Bridge resets) or when mu4e moves files without renaming them.
+
+*Prevention (mu4e users):* Add to Emacs config:
+#+begin_src elisp
+(setq mu4e-change-filenames-when-moving t)
+#+end_src
+
+*If errors occur:*
+1. First, try running mbsync again - [[https://isync.sourceforge.io/mbsync.html][official docs]] say it "will recover just fine if the change is unfounded"
+2. If errors persist, reset sync state (only if mail is safe on server):
+#+begin_src bash
+find ~/.mail/cmail -name ".uidvalidity" -delete
+find ~/.mail/cmail -name ".mbsyncstate" -delete
+mbsync cmail
+#+end_src
+
+*References:*
+- [[https://isync.sourceforge.io/mbsync.html][mbsync official documentation]]
+- [[https://pragmaticemacs.wordpress.com/2016/03/22/fixing-duplicate-uid-errors-when-using-mbsync-and-mu4e/][Fixing duplicate UID errors with mbsync and mu4e]]
+- [[https://www.julioloayzam.com/guides/recovering-from-a-mbsync-uidvalidity-change/][Recovering from mbsync UIDVALIDITY change]]
+
+*** Connection Errors
+- Gmail: Check network, may need app password refresh
+- cmail: Ensure Proton Bridge is running (check port 1143)
+
+#+begin_src bash
+ss -tlnp | grep 1143
+#+end_src
+
+* Mail Configuration Reference
+
+| Account | Local Path | IMAP Server |
+|---------+---------------+--------------------|
+| gmail | ~/.mail/gmail | imap.gmail.com |
+| cmail | ~/.mail/cmail | 127.0.0.1:1143 |
+
+* Principles
+
+- **Sync all accounts by default** - Unless Craig specifies a single account
+- **No pre-checks** - Don't verify connectivity before running; diagnose if errors occur
+- **Trust the tools** - mbsync and mu are robust; don't add unnecessary validation
+- **Never modify ~/.mail/ directly** - Read-only operations only; mbsync manages the maildir
+
+* Living Document
+
+Update this workflow as we discover new patterns or issues with email syncing.
diff --git a/.ai/workflows/task-review.org b/.ai/workflows/task-review.org
new file mode 100644
index 0000000..f11db2a
--- /dev/null
+++ b/.ai/workflows/task-review.org
@@ -0,0 +1,216 @@
+#+TITLE: Task Review Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-04-25
+
+* Overview
+
+Unified workflow for reviewing open tasks. Two modes share the same data-gathering and reconciliation logic, then branch on output:
+
+- *List mode* — exhaustive, priority-grouped display of every open task. Use when Craig wants to see his full plate.
+- *Next mode* — single-task recommendation via prioritization cascade. Use when Craig wants to pick what to do next without scanning the whole list.
+
+This workflow supersedes the previous standalone =open-tasks.org= (list mode) and =whats-next.org= (next mode), which duplicated the data-gathering and reconciliation logic.
+
+* When to Use This Workflow
+
+User says one of:
+
+- *List mode triggers:* "list open tasks", "show me all tasks", "what's on my plate", "task review"
+- *Next mode triggers:* "what's next", "what should I work on next", "what should I work on", "I need a recommendation"
+- *Ambiguous:* "what should I do today", "show me my tasks" — ask which mode
+
+* Phase A: Data Gathering (one parallel batch — both modes)
+
+Issue all source reads as a single batch of parallel tool calls. They're independent:
+
+1. Read =notes.org= — sections *Active Reminders* and *Pending Decisions* only.
+2. Read each of the 2-3 most recent files in =.ai/sessions/= — only the *Next Steps* subsection under =* Summary= (those capture recent "next time, do X" items that may still be open).
+3. Read =todo.org= — entries under the open-work header (=* $Project Open Work=, e.g. =* Homelab Open Work=). Skip the resolved header (=* $Project Resolved=).
+4. Run =date= for an accurate timestamp (used to evaluate deadlines + flag suspected completions).
+
+The phases below work entirely from this in-memory snapshot. The original split-workflow versions issued these reads sequentially in different files; consolidating saves round-trips and prevents the two implementations drifting apart.
+
+* Phase B: Reconcile (both modes)
+
+Compare the open-task signal across sources. For each task in =notes.org= Active Reminders or in a recent session's Next Steps that does NOT have a corresponding =todo.org= entry:
+
+1. Create a new =** TODO= entry under the =* $Project Open Work= header.
+2. Assign a priority based on context: =[#A]= if time-sensitive or blocking, =[#B]= if important, =[#C]= if low urgency.
+3. Include:
+ - =:CREATED:= property with today's date.
+ - Brief description of what needs to be done.
+ - Why it matters (context from the reminder or session note).
+ - Recommended approach or next steps.
+4. If a deadline exists, add a =DEADLINE:= line.
+
+*Do NOT remove the item from =notes.org= Active Reminders.* Reminders serve a different purpose (surfaced at session start). The =todo.org= entry is for tracking and prioritization.
+
+*Judgment call:* Not every reminder needs a =todo.org= entry. Skip:
+- Pure informational notes (e.g. "rsyncshot running with 600s timeout").
+- Waiting-for items with no action Craig can take (e.g. "package arriving Feb 25").
+- Items already completed (handle in Phase C list mode).
+
+* Phase C: Mode-Specific Output
+
+** List Mode
+
+*** Step 1: Review for Suspected Completions
+
+Quickly scan all open tasks and check if any appear already done, based on:
+- Recent session history mentioning completion.
+- Context clues (e.g. "arriving Feb 7" and today's date is Feb 12).
+- Work completed in previous sessions that wasn't marked done.
+
+Build a list of *suspected completions* — do NOT mark them done yet. These get confirmed with Craig in Step 3.
+
+*** Step 2: Display All Open Tasks
+
+Present grouped by priority. Format rules:
+
+- *Group by priority:* A (High), B (Medium), C (Low/Someday).
+- *Default priority:* Tasks without an explicit priority are treated as C.
+- *No table structure* — use a flat bulleted list within each group.
+- *Include deadlines:* If a task has a =DEADLINE:=, show it inline as =DEADLINE: <date>=.
+- *Include scheduled dates:* If a task has a =SCHEDULED:=, show it inline.
+- *Keep descriptions concise* — task name + one-line summary, not full details.
+- *Note source* if task came from reminders only (not yet in =todo.org=) vs =todo.org=.
+
+Example:
+
+#+begin_example
+**Priority A (High)**
+
+- Complete Sara Essex email setup — add Google Workspace MX records, verify delivery
+- Set up Comet KVMs — remote console for TrueNAS and ratio
+- Complete UPS/TrueNAS integration — USB cable, configure shutdown threshold. DEADLINE: <2026-01-21>
+
+**Priority B (Medium)**
+
+- Design Zettelkasten architecture — resume at Question 4 (Staleness)
+- Compare Ubiquiti UTR vs open source mesh router
+
+**Priority C (Low / Someday)**
+
+- Explore Whisper-to-Claude-Code voice integration
+- Get Keychron Q6 Pro carrying case. SCHEDULED: <2026-02-07>
+#+end_example
+
+*** Step 3: Confirm Suspected Completions
+
+After displaying the list, present suspected completions:
+
+#+begin_example
+These tasks may already be completed — can you confirm?
+- "OBSBOT Tiny 3 webcam arriving" — it's past the expected delivery date
+- "Sweetwater order arriving" — expected Feb 7, now Feb 12
+#+end_example
+
+For each task Craig confirms as done:
+1. Add =CLOSED: [YYYY-MM-DD Day]= timestamp (use the =date= output from Phase A).
+2. Change status from =TODO= to =DONE=.
+3. Add a brief completion note (when/how it was resolved).
+4. Move the entry from =* $Project Open Work= to =* $Project Resolved= in =todo.org=.
+5. If the task also exists in Active Reminders in =notes.org=, remove it from there.
+
+For tasks Craig says are NOT done, leave them as-is.
+
+** Next Mode
+
+Apply the prioritization cascade in order. Stop at the first matching step:
+
+*** 1. In-Progress Tasks
+- Look for tasks marked =DOING= or partially complete.
+- *If found:* Recommend that task (always finish what's started).
+- *If user declines:* Continue to next step.
+
+*** 2. Active Reminders
+- Review notes.org Active Reminders (already in the Phase A snapshot).
+- *If found:* Recommend reminder task.
+- *If user declines:* Add to =todo.org= per Phase B (if not already there), then continue.
+
+*** 3. Deadline-Driven Tasks
+- Scan =todo.org= for tasks with explicit deadlines.
+- *If found:* Recommend the task with the closest deadline.
+- *If none:* Continue to next step.
+
+*** 4. V2MOM Method Order (if applicable)
+If =todo.org= is structured with V2MOM methods:
+- Method 1 priority A tasks first.
+- Then Method 2 priority A, Method 3 priority A, etc.
+- Then Method 1 priority B, Method 2 priority B, etc.
+- Continue pattern through priorities C and D.
+
+*** 5. Simple Priority Order
+If =todo.org= is a flat list:
+- Evaluate all priority A tasks, pick most important.
+- If no priority A, evaluate priority B tasks.
+- Continue through priorities C and D.
+
+*** 6. All Tasks Complete
+If no tasks remain: report "All done — no open tasks in =todo.org=."
+
+*** Handling Multiple Tasks at Same Level
+
+When multiple tasks share priority/method position, pick one based on:
+
+1. *Blocks other work* — dependencies matter.
+2. *Recently discussed* — mentioned in recent conversation.
+3. *Most foundational* — enables other tasks.
+4. *If truly uncertain* — show 2-3 options and let Craig choose.
+
+*** Output Format
+
+Keep the recommendation concise but informative:
+
+#+begin_example
+Next: Fix org-noter reliability (Method 1, Priority A, 8/18 complete)
+Reason: Blocks daily reading/annotation workflow
+#+end_example
+
+Include:
+- Task name / description.
+- One-line reasoning (which cascade step matched and why).
+- Progress indicator (for V2MOM-structured todos).
+
+* Resolving a Task — Format Reference
+
+When moving a task to Resolved (list mode Step 3), it should look like this:
+
+#+begin_example
+** DONE [#A] Set up Comet KVMs
+CLOSED: [2026-02-12 Thu]
+:PROPERTIES:
+:CREATED: [2026-01-19 Mon]
+:END:
+
+Comet KVMs set up for TrueNAS and ratio. Remote BIOS/console access working.
+
+*Resolution:* Completed during Feb 12 session. Both KVMs connected and tested.
+#+end_example
+
+Key elements:
+- =DONE= replaces =TODO=.
+- =CLOSED:= line with completion date.
+- Original =:PROPERTIES:= block preserved.
+- Brief resolution note explaining when/how.
+
+* Common Mistakes
+
+1. *Running Phase A sequentially* — issue all reads in one parallel batch.
+2. *Marking tasks done without confirmation* — always ask Craig first (list mode Step 3).
+3. *Removing reminders from =notes.org= when adding to =todo.org=* — they serve different purposes.
+4. *Creating =todo.org= entries for pure informational reminders* — use judgment in Phase B.
+5. *Using a table for the task list* — Craig prefers flat bulleted lists in list mode.
+6. *Skipping the cascade order in next mode* — the cascade exists to override subjective choice with objective criteria; respect it.
+7. *Recommending more than one task in next mode* — be decisive. Only show 2-3 if truly uncertain after applying the same-level tie-breakers.
+8. *Re-querying =todo.org= during Phase C* — the snapshot from Phase A is canonical; don't re-read.
+
+* Living Document
+
+Update this workflow as task management patterns evolve. If new task sources are added (external issue trackers, shared task lists), add them to Phase A. If the cascade ordering needs to change for a project, document the variant here.
+
+* Replaces
+
+This file replaces:
+- =open-tasks.org= (list mode logic now lives in Phase C → List Mode)
+- =whats-next.org= (next mode logic now lives in Phase C → Next Mode)
diff --git a/.ai/workflows/wrap-it-up.org b/.ai/workflows/wrap-it-up.org
new file mode 100644
index 0000000..37d2522
--- /dev/null
+++ b/.ai/workflows/wrap-it-up.org
@@ -0,0 +1,235 @@
+#+TITLE: Session Wrap-Up Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-04-20
+
+* Overview
+
+This workflow defines the process for ending a Claude Code session cleanly. It finalizes the session record, commits + pushes all work, and provides a warm handoff.
+
+Triggered by Craig saying "wrap it up," "that's a wrap," "let's call it a wrap," or similar.
+
+* The Session Record
+
+Throughout the session, =.ai/session-context.org= has been maintained with:
+- =* Summary= — structured distillation (empty or draft during session)
+- =* Session Log= — chronological narrative of what happened, written as you go
+
+At wrap-up, this file becomes the permanent session record by being renamed to =.ai/sessions/YYYY-MM-DD-HH-MM-description.org=. No transcription elsewhere. The file IS the record.
+
+* Exit Criteria
+
+The wrap-up is complete when:
+
+1. *Summary is written.* The =* Summary= section of =.ai/session-context.org= is populated by reading the =* Session Log= — Active Goal, Decisions, Data Collected / Findings, Files Modified, Next Steps.
+2. *File is archived.* =.ai/session-context.org= has been renamed to =.ai/sessions/YYYY-MM-DD-HH-MM-description.org=. The old path no longer exists.
+3. *todo.org is clean.* Cleanup script ran. Any auto-fixes are staged for the wrap-up commit. Orphan planning lines surfaced for manual fix if there are any.
+4. *Git state is clean.* All changes committed + pushed to all remotes. Working tree clean.
+5. *Valediction delivered.* Brief, warm closing with key accomplishments and reminders.
+
+The absence of =.ai/session-context.org= is the signal that the last session wrapped up cleanly. Its presence at session start means the previous session was interrupted.
+
+* The Workflow
+
+** Step 1: Finalize the Summary
+
+Read through the =* Session Log= in =.ai/session-context.org=. Populate (or refine) the =* Summary= section:
+
+- *Active Goal* — one or two sentences describing the session's focus
+- *Decisions* — key choices made, with enough context to recall the /why/
+- *Data Collected / Findings* — anything concrete (measurements, root causes, paths, discoveries)
+- *Files Modified* — what was changed, with one-line rationale per significant file
+- *Next Steps* — what should happen in the next session
+
+Don't repeat everything from the Log in the Summary. The Summary is distillation — pull out what's load-bearing. The Log stays in the file and is available if a future reader wants detail.
+
+** Step 2: Pick a description + rename
+
+Read the Summary's Active Goal and the prominent entries in the Session Log. Pick a 4-6 word description that would make sense as a git-commit-message-series summary for the whole session.
+
+Good descriptions are concrete nouns/verbs:
+- =docs-ai-migration-and-ai-launcher=
+- =mybitch-usb-disconnect-diagnosis=
+- =ratio-system-health-check=
+- =orchestration-dashboard-bug-triage=
+
+Avoid vague ones:
+- =session-work= (useless)
+- =various-improvements= (useless)
+- =updates= (useless)
+
+Get current time and rename:
+
+#+begin_src bash
+mkdir -p .ai/sessions
+now=$(date +%Y-%m-%d-%H-%M)
+mv .ai/session-context.org .ai/sessions/${now}-DESCRIPTION.org
+#+end_src
+
+Replace =DESCRIPTION= with your picked slug.
+
+** Step 3: todo.org hygiene pass
+
+If the project has a =todo.org= at its root, run the cleanup script before committing. It catches a recurring pattern: org sometimes leaves noise lines like =- State "X" from "X" [date]= when a state-change log lands outside a =:LOGBOOK:= drawer and the state didn't actually change. These lines carry no information and they break org's planning-line parser by wedging between the heading and =DEADLINE:=/=SCHEDULED:=, which kicks the entry out of agenda views.
+
+#+begin_src bash
+[ -f todo.org ] && emacs --batch -q -l .ai/scripts/todo-cleanup.el todo.org
+#+end_src
+
+The script is fast (under half a second on a 4000-line file) and idempotent — if there's nothing to fix, it reports zero changes and exits clean.
+
+What it does:
+
+1. *Auto-deletes* bogus state-log lines (matched on identical from/to states). Any deletions show up in the wrap-up commit's diff, so they get reviewed before push.
+2. *Reports* "orphan planning lines" — entries whose body has =DEADLINE:= or =SCHEDULED:= but =org-entry-get= can't read it (some other malformation kept it out of canonical position). The script doesn't auto-rewrite these because the right fix depends on whether real state-log history needs preserving — surface them and fix manually if they matter for the agenda.
+
+Run the report-only variant first if you want to see what would change without writing:
+
+#+begin_src bash
+emacs --batch -q -l .ai/scripts/todo-cleanup.el --check todo.org
+#+end_src
+
+** Step 4: Git commit + push
+
+*** Review changes
+
+#+begin_src bash
+git status
+git diff --stat
+#+end_src
+
+Decide the scope of the wrap-up commit. Usually everything that changed during the session goes into one commit. If anything is intentionally not part of this session's work (pre-existing WIP, unrelated files), leave it out.
+
+*** Stage
+
+Add the renamed session file and all other session changes:
+
+#+begin_src bash
+git add .ai/sessions/ [other modified paths]
+#+end_src
+
+Do NOT blindly =git add .= — review what's being staged so unrelated dirty state isn't dragged in.
+
+*** Commit
+
+Commit message rules (also see protocols.org "Git Commit Requirements"):
+
+- Subject line: concise, describes what /shipped/. Use conventional prefixes (=docs:=, =refactor:=, =fix:=, =feat:=, =chore:=) — NEVER =session:=.
+- Body: 1-3 terse sentences describing what was accomplished.
+- NO Claude Code attribution. NO =Co-Authored-By=. NO references to =notes.org=, =session-context.org=, =.ai/sessions/=, "session wrap-up", or session timestamps.
+
+*Wrap-up commits skip the inline-approval gate.* The =commits.md= rule that requires writing the message to =/tmp/commit-<slug>.md=, printing inline, and waiting for an approve / request-changes / open-in-editor response does *not* apply to wrap-up commits. The wrap-up flow is meant to be quick — Craig has already authorized the wrap by triggering the workflow ("wrap it up"), and stopping again to approve a commit message disrupts the cadence.
+
+Still apply the prose-quality passes silently before committing — humanizer + jargon-rewrite + semicolon-swap + contractions + sentence-split — so the message reads cleanly. Just don't print and ask. Commit directly with the cleaned message.
+
+If a wrap-up commit needs Craig's eyes for a content reason (sensitive change, unusual scope, something he flagged earlier), surface it explicitly. Otherwise commit and move on.
+
+Example:
+#+begin_example
+docs: restructure docs/ to .ai/ and unify aix+hey into ai launcher
+
+Hidden .ai/ now holds Claude tooling; project-level docs/ reserved
+for user-facing docs. Single 'ai' launcher (fzf multi + smart tmux
++ git-aware fetch/pull) replaces the aix script and hey alias.
+#+end_example
+
+Use heredoc for multi-line:
+#+begin_src bash
+git commit -m "$(cat <<'EOF'
+subject line here
+
+body sentences here.
+EOF
+)"
+#+end_src
+
+*** Push to all remotes
+
+#+begin_src bash
+git remote -v
+#+end_src
+
+Push the current branch to every remote (preserves the mirror behavior — rulesets and a few other repos have github.com + cjennings.net mirrors that should both stay current):
+
+#+begin_src bash
+current=$(git symbolic-ref --short HEAD)
+for r in $(git remote); do git push "$r" "$current"; done
+#+end_src
+
+Then push every other local branch with a tracking upstream to its tracking remote. This catches feature branches that advanced during the session but aren't the one being wrapped up — without it, work-in-progress branches stay local-only and are at risk if the machine dies before the next wrap-up.
+
+#+begin_src bash
+git for-each-ref --format='%(refname:short) %(upstream:remotename)' refs/heads/ | \
+while read branch remote; do
+ [ "$branch" = "$current" ] && continue
+ if [ -z "$remote" ]; then
+ echo " $branch: no tracking upstream — skipped (push manually with 'git push -u')"
+ else
+ git push "$remote" "$branch"
+ fi
+done
+#+end_src
+
+Behavior:
+- *Tracked branches* → pushed to their upstream remote.
+- *Untracked branches* (no upstream set) → surfaced, not pushed. Craig sets the upstream manually with =git push -u <remote> <branch>= when he's ready. Auto-creating an upstream would commit to a remote choice the workflow can't make safely.
+- *Diverged or rejected pushes* → surface and stop. Don't force-push from this workflow; resolve manually.
+
+*** Verify clean
+
+#+begin_src bash
+git status
+#+end_src
+
+Should show "working tree clean" and "branch is up to date" with each remote. If not, resolve before proceeding.
+
+** Step 5: Valediction
+
+Brief, warm closing. 3-4 sentences max.
+
+Include:
+- What was accomplished (specific, not generic)
+- What's ready for next session
+- Any critical reminders or deadlines
+
+Tone: warm but professional. No emoji unless Craig has explicitly requested. Acknowledge effort when session was long or difficult.
+
+Example:
+#+begin_example
+That's a wrap. Today we restructured the entire claude-templates
+ecosystem: docs/ → .ai/ across all 23 projects, unified aix + hey
+into a single 'ai' launcher with git-aware fetch/pull, and cleaned
+up 4 code projects on velox. Both machines fully in sync.
+
+Two things to pick up next: the chime README WIP (your inline notes
+from earlier) and archsetup's layout-navigate tests. Both are
+ratio-local uncommitted state.
+
+Good session. Talk tomorrow.
+#+end_example
+
+* Common Mistakes to Avoid
+
+1. *Skipping Step 1 (Summary)* — the file becomes the record; an empty Summary makes it hard to scan at catch-up
+2. *Vague description in filename* — =2026-04-20-updates.org= is useless next to =2026-04-20-13-45-docs-ai-migration.org=
+3. *=git add .= without review* — drags in unrelated dirty state
+4. *=session:= prefix in commit message* — explicitly forbidden; use real change categories
+5. *Claude-tooling references in commit message* — describes tooling, not what shipped
+6. *Forgetting to push to all remotes* — check =git remote -v=, push to each
+7. *Leaving =.ai/session-context.org= in place* — its presence means "interrupted session", confuses next startup
+8. *Long preachy valediction* — brief beats thorough
+
+* Validation Checklist
+
+Before considering wrap-up complete:
+
+- [ ] =.ai/session-context.org= =* Summary= section populated
+- [ ] File renamed to =.ai/sessions/YYYY-MM-DD-HH-MM-description.org=
+- [ ] =.ai/session-context.org= no longer exists
+- [ ] =todo-cleanup.el= ran (if =todo.org= exists at project root)
+- [ ] Any orphan-planning-line warnings reviewed (fix or accept)
+- [ ] =git status= clean after commit + push
+- [ ] Current branch pushed to ALL remotes (verified with =git remote -v=)
+- [ ] All other local branches with a tracking upstream pushed to their remote
+- [ ] Any untracked-upstream branches surfaced for manual =git push -u=
+- [ ] Commit message follows format (no =session:=, no Claude attribution)
+- [ ] Valediction delivered (brief, specific, warm)