From 72c11ea07c896d187513940b7159e73f1eedbc0a Mon Sep 17 00:00:00 2001 From: Omri Ariav Date: Fri, 6 Feb 2026 12:58:24 +0200 Subject: [PATCH 1/6] feat(morning): lean triage output + label caching (#38, #39) Lean triage output (#38): - Grouped JSON format: auto_handled/needs_input/batch_stats - NOISE items reduced to thread_id + reason only - Compact matches array (non-null only) - ~80% token reduction in triage agent output Cache label list (#39): - Fetch labels once at session start, write to scratchpad - Label-resolver accepts cached labels_file parameter - Saves ~4k tokens + 2-3s per label operation Co-Authored-By: Claude Opus 4.6 --- skills/morning/SKILL.md | 29 ++++++++++------ skills/morning/prompts/label-resolver.md | 10 ++++-- skills/morning/prompts/triage-agent.md | 42 +++++++++++++++--------- 3 files changed, 52 insertions(+), 29 deletions(-) diff --git a/skills/morning/SKILL.md b/skills/morning/SKILL.md index 605c198..bd44c0c 100644 --- a/skills/morning/SKILL.md +++ b/skills/morning/SKILL.md @@ -55,6 +55,16 @@ The config contains: - `inbox_query` — Gmail search query (default: `"is:unread in:inbox"`) - `daily_log_doc_id` — Google Doc ID for the daily log (empty = skip logging) +### Cache label list + +Fetch the Gmail label list once at session start to avoid re-fetching on every label operation: + +```bash +gws gmail labels > "$SCRATCHPAD_DIR/morning/labels.json" +``` + +This cached list is passed to the label-resolver sub-agent when the user picks "Label & archive" during triage. Saves ~4k tokens + 2-3s per label operation vs fetching each time. + ## Step 2: Launch Background Data Gathering Launch a **background agent** to run prefetch + pre-filter while the main agent shows the user immediate context. @@ -143,18 +153,16 @@ Starting triage. The background agent classifies all remaining emails using the rules from `skills/morning/prompts/triage-agent.md`. This happens as part of the background agent launched in Step 2 — the main agent does NOT classify. -For each email, the background agent determines: -- **Classification:** ACT_NOW / REVIEW / NOISE -- **Priority score:** 1-5 (highest signal, not additive) -- **Summary:** 1-2 lines -- **Matches:** OKR, task, or calendar match if any -- **Recommended action** +The background agent returns a grouped JSON result (lean format — see `triage-agent.md`): +- **`auto_handled`:** NOISE items — thread IDs + reason only (no subject/sender/matches) +- **`needs_input`:** ACT_NOW and REVIEW items with priority, summary, sender, subject, and non-null matches +- **`batch_stats`:** Total counts per category -The background agent writes results to `classified.json` and archives all NOISE items via `gws gmail archive-thread --quiet`. +The main agent uses `auto_handled` thread IDs for bulk archive via `scripts/bulk-gmail.sh archive-thread`, and `needs_input` for guided triage. ## Step 4: Collect Results -When the background agent completes, the main agent reads `classified.json` and presents the auto-action summary. +When the background agent completes, the main agent bulk-archives NOISE items and presents the auto-action summary. ### Classification Categories @@ -290,7 +298,7 @@ Use AskUserQuestion with **4 options**. Pick the best 4 from the pool based on c - **Reply** — Compose a reply: fetch email via `gws gmail read `, draft a response using conversation context (OKR/task matches, deep-dive insights if available), present draft for user approval, then `gws gmail reply --body ""`. Thread ID, subject, and headers are auto-populated. - **Reply All** — Same as Reply but to all recipients: `gws gmail reply --body "" --all` - **Forward** — Forward to a colleague: ask who, fetch email content (`read` for single message, `thread` for conversations), compose brief forwarding context (why forwarding + 1-2 key points, include OKR/task match if relevant), then `gws gmail send --to "" --subject "Fwd: " --body "\n\n---\nOriginal message:\n"` -- **Label & archive** — Spawn label-resolver sub-agent (`skills/morning/prompts/label-resolver.md`) with `action=archive` +- **Label & archive** — Spawn label-resolver sub-agent (`skills/morning/prompts/label-resolver.md`) with `action=archive` and `labels_file="$SCRATCHPAD_DIR/morning/labels.json"` (cached from Step 1) - **Add task & archive** — Ask for title, run `gws tasks create`, then archive - **Open in browser** — Run `open "https://mail.google.com/mail/u/0/#inbox/"` @@ -615,5 +623,6 @@ Common `gws` commands used during triage: ### Label Operations - Gmail labels are resolved by **display name** (case-insensitive), not by internal ID. -- For label operations during triage, use the **label-resolver sub-agent** (`skills/morning/prompts/label-resolver.md`) to avoid loading the full label list (4000+ labels) into the main context. +- **Labels are cached at session start** (Step 1) in `$SCRATCHPAD_DIR/morning/labels.json`. Pass `labels_file` to the label-resolver sub-agent to avoid re-fetching on every label operation (~4k tokens + 2-3s saved per operation). +- For label operations during triage, use the **label-resolver sub-agent** (`skills/morning/prompts/label-resolver.md`) — keeps the full label list (4000+ labels) out of the main context. - Common label patterns: `gws gmail label --add "STARRED"`, `gws gmail label --remove "UNREAD"` diff --git a/skills/morning/prompts/label-resolver.md b/skills/morning/prompts/label-resolver.md index 93476de..b0062bf 100644 --- a/skills/morning/prompts/label-resolver.md +++ b/skills/morning/prompts/label-resolver.md @@ -17,15 +17,19 @@ You are a Gmail label resolver agent. Your job: find the best matching label for - thread_id: (required if action is "archive") - desired_label: