mirror of
https://github.com/hanzoai/mcp.git
synced 2026-10-15 12:30:12 +00:00
builtin_email_recipe_yaml include_str!'d ../../../../bot/extensions/recipes-brain/recipes/email.yaml — a path that climbs out of this repo into a sibling checkout of hanzoai/bot. That resolves only on a workstation with both repos side by side, so every CI target failed to compile the lib: couldn't read `src/brain/../../../../bot/.../email.yaml` Copy the file to rust/recipes/email.yaml (byte-identical, sha256 f4577ec92d696a426d8ec230af55747e81a505c79f9d02266f9e2e0538ed38ae) and include it from there. This is also what the module doc already claimed: recipes live at <crate_dir>/recipes/*.yaml. Verified by building all five targets from a copy containing only this crate's tracked files, with no sibling repo present.
93 lines
2.9 KiB
YAML
93 lines
2.9 KiB
YAML
# Hanzo Brain — Email Recipe
|
|
#
|
|
# Daily Gmail pull → classify → draft response → enqueue for swipe-review
|
|
# in the Hanzo app (~/work/hanzo/bot/apps/{ios,android,macos}).
|
|
#
|
|
# Run:
|
|
# hanzo-bot brain run-recipe email
|
|
# hanzo-bot brain drafts list
|
|
# hanzo-bot brain drafts approve <id> # sends via gmail
|
|
# hanzo-bot brain drafts reject <id> # logs as decline-fact for training
|
|
#
|
|
# Trains your reply tone over time. Every swipe is a signal.
|
|
|
|
recipe: email
|
|
version: 1
|
|
backend: gmail
|
|
|
|
auth:
|
|
provider: hanzo.id # OIDC via hanzo IAM — single sign-on
|
|
scopes:
|
|
- https://www.googleapis.com/auth/gmail.readonly
|
|
- https://www.googleapis.com/auth/gmail.compose
|
|
|
|
cron: "*/30 * * * *" # every 30 min — adjust per volume
|
|
|
|
ingest:
|
|
source: inbox
|
|
since: 24h
|
|
max: 100
|
|
filter:
|
|
has_thread: any
|
|
skip_labels: [SPAM, TRASH, PROMOTIONS]
|
|
|
|
classify:
|
|
model: zen-2-haiku # cheap classifier, fast
|
|
schema:
|
|
needs_reply: bool
|
|
priority: one_of [P0, P1, P2, P3]
|
|
intent: one_of [question, fyi, scheduling, ask, update, spam]
|
|
entities: [person, company, deal]
|
|
sentiment: one_of [positive, neutral, negative]
|
|
action_items: list
|
|
|
|
draft:
|
|
when: needs_reply == true
|
|
model: zen-2 # main model — bigger context, better tone
|
|
context:
|
|
- brain.recall(from_email) # who is this person (facts)
|
|
- brain.search(thread_id, limit=10) # past replies in thread (RAG)
|
|
- brain.facts({entity:from_email, since:30d, limit:30})
|
|
- brain.style(sender:me, recent:200) # 200-shot style examples from outbox
|
|
prompt: |
|
|
You are drafting a reply for me. Match my tone exactly — terse,
|
|
direct, no exclamation marks, no emoji. Answer the question in the
|
|
fewest words that are still useful. Sign off only if I usually do.
|
|
Never invent commitments. If you're unsure, suggest "let me check
|
|
and get back to you" and add an action_item.
|
|
output_schema:
|
|
subject: string
|
|
body: string
|
|
action_items: list
|
|
confidence: number # 0..1, used for triage ranking
|
|
|
|
enqueue:
|
|
queue: drafts.email
|
|
ttl: 7d
|
|
rank_by: [priority, confidence]
|
|
fact_on_enqueue:
|
|
subject: from_email
|
|
predicate: thread_active
|
|
object: true
|
|
source: thread_id
|
|
|
|
notify:
|
|
channel: hanzo-app
|
|
badge: count_unswiped
|
|
push: drafts.email.new
|
|
|
|
# After the user swipes in the app, the result feeds back to the brain:
|
|
on_swipe_send:
|
|
- send via gmail
|
|
- upsertFact: { subject: from_email, predicate: replied, object: thread_id, ts: now }
|
|
- learn: append (received_msg, sent_draft) to style examples
|
|
|
|
on_swipe_reject:
|
|
- delete draft
|
|
- upsertFact: { subject: from_email, predicate: declined_reply, object: thread_id, ts: now }
|
|
- learn: down-weight similar drafts in future ranking
|
|
|
|
on_swipe_edit:
|
|
- open editor with the draft pre-filled
|
|
- on save: send + learn (received_msg, edited_draft) as positive example
|