Surfaces
Inngest workflows
8 functions, ~25 invocations/day. Event-driven where possible, cron only where necessary.
Registered at https://soma-ai.cc/api/inngest. Handler served by inngest/next.
The catalog
| Function | Trigger | What it does |
|---|---|---|
morningBriefCron | cron 0 8 * * * · 1/day | Fan-out brief.generate per user |
generateBrief | event brief.generate | Assemble context → Opus synthesis → deliver |
weeklyReview | event review.weekly | Opus-driven deep synthesis over the week's events + facts |
gmailIngest | event gmail.ingest | Pull recent Gmail messages → upsert sources, person entities, email_received events |
gcalSyncCron | cron 0 * * * * · 24/day | Fan-out gcal.sync per Google-connected user |
gcalSync | event gcal.sync | 21-day horizon, upsert events (dedup on calendar id) |
healthImport | event health.import | Parse Apple Health XML → workouts/sleep/metrics events |
factExtract | event conversation.turn | Extract facts from turn → upsert with cosine dedup (τ=0.85) → update confidence via moving average |
Concurrency & retries
- Per-user limit = 1 on ingestion workflows (
gmailIngest,gcalSync,healthImport) — prevents double-ingestion races when a user triggers multiple syncs in quick succession. - Retries 1–3 per function, depending on the failure mode (transient network = retry; bad data = fail fast).
- Langfuse trace on every function. Tags:
['workflow', '<name>'].
Cron budget
:::tip
Cron cost cut by 87% during migration. The original setup had two */15 * * * * crons for Gmail and GCal — 192 polling invocations per day, mostly no-op on idle accounts. Gmail is now push-only via Pub/Sub webhook; GCal pulls hourly since calendars move at human cadence.
:::
Before:
gmailIngestCron· 96/daygcalSyncCron· 96/daymorningBriefCron· 1/day- Total: 193/day
After:
gmailIngest· triggered by/api/webhooks/gmail(Pub/Sub)gcalSyncCron· 24/day (hourly)morningBriefCron· 1/day- Total: 25/day, ~87% reduction
Trigger topology
flowchart TB
subgraph Time
morn["cron 0 8 * * *"]
hr["cron 0 * * * *"]
end
subgraph Push
gps["/api/webhooks/gmail<br/>(Google Pub/Sub)"]
chat["/api/chat onFinish"]
tg["/api/telegram/webhook"]
hi["/api/health/import"]
end
morn --> mbc[morningBriefCron]
hr --> gcc[gcalSyncCron]
mbc --> genBrief[generateBrief]
gcc --> gcalSync[gcalSync]
gps --> gmailIngest[gmailIngest]
chat --> fe[factExtract]
tg --> fe
hi --> health[healthImport]Adding a function
- Write the function in
packages/agent/src/inngest/functions/<name>.ts. - Export it and add to
inngestFunctionsinpackages/agent/src/inngest/index.ts. - Deploy. Next invocation of
PUT /api/inngestauto-registers with Inngest Cloud. - Wrap the handler body in
startTrace(...)→flushLangfuse()for observability.
See the Claude skill soma-inngest-workflows for detailed patterns.