Files
zeekay 50914e547c brain: ship the gbrain-equivalent meta-pack as bot extensions
Four new extensions implement the Hanzo Brain on top of @hanzo/bot
without renaming anything or carving a separate repo. Solo devs install
nothing extra — single binary, single config, SQLite by default.

  brain/             meta-pack: wires the three below into one register()
  memory-postgres/   dual-mode store: Postgres+pgvector OR SQLite+FTS5,
                     selected by HANZO_BRAIN_URL / DATABASE_URL. Schema
                     mirrors gbrain's: pages, edges, facts, FTS index.
  graph-links/       zero-LLM typed-link extractor — runs on every page
                     write. Emits attended/works_at/invested_in/founded/
                     advises edges from regex + role inference. Strips
                     code fences. Self-wiring knowledge graph at
                     >10K pages/sec.
  recipes-brain/     YAML recipe loader + scheduler. Flagship recipe:
                     recipes/email.yaml — daily Gmail pull → classify →
                     draft → enqueue for swipe-review in the Hanzo app
                     (bot/apps/{ios,android,macos}). Every swipe is a
                     training signal for tone.

Tests: 16/16 pass (12 graph-links unit + 4 memory-postgres SQLite).
End-to-end smoke (markdown ingest → edge extraction → fact upsert →
hybrid search → recipe load) runs clean against bun:sqlite.

Deploy path: bot CI builds @hanzo/bot image w/ new extensions →
workflow_dispatch on .github/workflows/k8s-deploy-bot.yml rolls it to
app.hanzo.bot.
2026-05-10 17:03:50 -07:00

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