rowboat/apps/skills/draft-emails/SKILL.md
tusharmagar 9a308cb7a9 feat(skills): single-source skill system with markdown SKILL.md + include directive
Skills move out of packages/core/src/application/assistant/skills/*/skill.ts
(TS string constants) into apps/skills/<id>/SKILL.md (Agent Skills spec format
— YAML frontmatter + markdown body). One directory, one loader, one place to
look at every skill the agent can load.

Key change vs the old dev system: a `{{include:<skill-id>}}` directive lets one
skill transclude another. This removes the parallel TS constant for the
knowledge-note style guide — it now lives at apps/skills/knowledge-note-style/
(hidden from catalog) and is pulled into doc-collab + the live-note and
background-task agents via the resolver instead of via a TS import.

Infrastructure:
- packages/core/src/skills/ — types, skill-md-parser, FS-backed official repo,
  SkillResolver with recursive {{include:<id>}} expansion + cycle detection
- packages/shared/src/skill.ts — SkillFrontmatter, SkillCatalogEntry,
  ResolvedSkill schemas
- DI: officialSkillsRepo + skillResolver registered; registerSkillsDir helper
  wires the path before any consumer resolves
- IPC: skills:list / skills:get (read-only) for the Settings UI
- Main: resolveSkillsDir picks Resources/skills (packaged) or repo apps/skills
  (dev). forge.config.cjs ships apps/skills/ as extraResource.

Consumer refactor:
- buildCopilotInstructions: catalog markdown built from resolver.getCatalog()
- builtin-tools: loadSkill uses resolver, new listSkills tool
- background-tasks/agent + live-note/agent: now async builders that load
  the knowledge-note-style skill content via resolver
- runtime.loadAgent: awaits the now-async builders
- Deleted: assistant/skills/ directory, knowledge-note-style.ts

UI:
- New SkillsSettings component (read-only list + detail view) wired into
  Settings dialog as the "Skills" tab.
2026-05-13 12:31:06 +05:30

7.7 KiB

name description metadata
draft-emails Process incoming emails and create draft responses using calendar and knowledge base for context.
title
Draft Emails

Email Draft Skill

You are helping the user draft email responses. Use their calendar and knowledge base for context.

CRITICAL: Always Look Up Context First

BEFORE drafting any email, you MUST look up the person/organization in the knowledge base.

PATH REQUIREMENT: Always use knowledge/ as the path (not empty, not the workspace root, not an absolute path).

  • WRONG: path: "" or path: "."
  • CORRECT: path: "knowledge/"

When the user says "draft an email to Monica" or mentions ANY person, organization, project, or topic:

  1. STOP - Do not draft anything yet
  2. SEARCH - Look them up in the knowledge base (path MUST be knowledge/):
    workspace-grep({ pattern: "Monica", path: "knowledge/" })
    
  3. READ - Read their note to understand who they are:
    workspace-readFile("knowledge/People/Monica Smith.md")
    
  4. UNDERSTAND - Extract their role, organization, relationship history, past interactions, open items
  5. THEN DRAFT - Only now draft the email, using this context

DO NOT skip this step. DO NOT provide generic templates. If you don't look up the context first, you will give a useless generic response.

Key Principles

Ask, don't guess:

  • If the user's intent is unclear, ASK them what the email should be about
  • If a person has multiple contexts (e.g., different projects, topics), ASK which one they want to discuss
  • WRONG: "Here are three variants for different contexts - pick one"
  • CORRECT: "I see Akhilesh is involved in Rowboat, banking/ODI, and APR. Which topic would you like to discuss in this email?"

Be decisive, not generic:

  • Once you know the context, draft ONE email - no multiple versions or options
  • Do NOT provide generic templates - every draft should be personalized based on knowledge base context
  • Infer the right tone, content, and approach from the context you gather
  • Do NOT hedge with "here are a few options" or "you could say X or Y" - either ask for clarification OR make a decision and draft ONE email

State Management

All state is stored in pre-built/email-draft/:

  • state.json - Tracks processing state:
    {
      "lastProcessedTimestamp": "2025-01-10T00:00:00Z",
      "drafted": ["email_id_1", "email_id_2"],
      "ignored": ["spam_id_1", "spam_id_2"]
    }
    
  • drafts/ - Contains draft email files

Initialization

On first run, check if state exists. If not, create it:

  1. Check if pre-built/email-draft/state.json exists
  2. If not, create pre-built/email-draft/ and pre-built/email-draft/drafts/
  3. Initialize state.json with empty arrays and a timestamp of "1970-01-01T00:00:00Z"

Processing Flow

Step 1: Load State

Read pre-built/email-draft/state.json to get:

  • lastProcessedTimestamp - Only process emails newer than this
  • drafted - List of email IDs already drafted (skip these)
  • ignored - List of email IDs marked as ignored (skip these)

Step 2: Scan for New Emails

List emails in gmail_sync/ folder.

For each email file:

  1. Extract the email ID from filename (e.g., 19048cf9c0317981.md -> 19048cf9c0317981)
  2. Skip if ID is in drafted or ignored lists
  3. Read the email content

Step 3: Parse Email

Each email file contains:

# Subject Line

**Thread ID:** <id>
**Message Count:** <count>

---

### From: Name <email@example.com>
**Date:** <date string>

<email body>

Extract:

  • Thread ID (this is the email ID)
  • From (sender name and email)
  • Date
  • Subject (from the # heading)
  • Body content
  • Message count (to understand if it's a thread)

Step 4: Classify Email

Determine the email type and action:

IGNORE these (add to ignored list):

  • Newsletters (unsubscribe links, "View in browser", bulk sender indicators)
  • Marketing emails (promotional language, no-reply senders)
  • Automated notifications (GitHub, Jira, Slack, shipping updates)
  • Spam or cold outreach that's clearly irrelevant
  • Emails where you (the user) are the sender and it's outbound with no reply

DRAFT response for:

  • Meeting requests or scheduling emails
  • Personal emails from known contacts
  • Business inquiries that seem legitimate
  • Follow-ups on existing conversations
  • Emails requesting information or action

Step 5: Gather Context

Before drafting, gather relevant context. Always check the knowledge base first for any person, organization, project, or topic mentioned in the email.

Knowledge Base Context (REQUIRED):

First, search for the sender and any mentioned entities (path MUST be knowledge/):

# Search for the sender by name or email
workspace-grep({ pattern: "sender_name_or_email", path: "knowledge/" })

# List all people to find potential matches
workspace-readdir("knowledge/People")

Then read the relevant notes:

# Read the sender's note
workspace-readFile("knowledge/People/Sender Name.md")

# Read their organization's note
workspace-readFile("knowledge/Organizations/Company Name.md")

Extract from these notes:

  • Their role, title, and organization
  • History of past interactions and meetings
  • Commitments made (by them or to them)
  • Open items and pending actions
  • Relationship context and rapport

Use this context to provide informed, personalized responses that demonstrate you remember past interactions.

Calendar Context (for scheduling emails):

  • Read calendar events from calendar_sync/ folder
  • Look for events in the relevant time period
  • Check for conflicts, availability

Step 6: Create Draft

For emails that need a response, create a draft file in pre-built/email-draft/drafts/:

Filename: {email_id}_draft.md

Content format:

# Draft Response

**Original Email ID:** {email_id}
**Original Subject:** {subject}
**From:** {sender}
**Date Processed:** {current_date}

---

## Context Used

- Calendar: {relevant calendar info or "N/A"}
- Memory: {relevant notes or "N/A"}

---

## Draft Response

Subject: Re: {original_subject}

{draft email body}

---

## Notes

{any notes about why this response was crafted this way}

Drafting Guidelines:

  • Draft ONE email - do not offer multiple versions or options unless explicitly asked
  • Be concise and professional
  • For scheduling: propose specific times based on calendar availability
  • For inquiries: answer directly or indicate what info is needed
  • Reference any relevant context from memory naturally - show you remember past interactions
  • Match the tone of the incoming email
  • If it's a thread with multiple messages, read the full context
  • Do NOT use generic templates or placeholder language - personalize based on knowledge base
  • If you're unsure about the user's intent, ask a clarifying question first

Step 7: Update State

After processing each email:

  1. Add the email ID to either drafted or ignored list
  2. Update lastProcessedTimestamp to the current time
  3. Write updated state to pre-built/email-draft/state.json

Output

After processing all new emails, provide a summary:

## Processing Summary

**Emails Scanned:** X
**Drafts Created:** Y
**Ignored:** Z

### Drafts Created:
- {email_id}: {subject} - {brief reason}

### Ignored:
- {email_id}: {subject} - {reason for ignoring}

Error Handling

  • If an email file is malformed, log it and continue
  • If calendar/notes folders don't exist, proceed without that context
  • Always save state after each email to avoid reprocessing on failure

Important Notes

  • Never actually send emails - only create drafts
  • The user will review and send drafts manually
  • Be conservative with ignore - when in doubt, create a draft
  • For ambiguous emails, create a draft with a note explaining the ambiguity