Skip to content

Dynamic habit aliases (system architecture)

The capture/coordinator brain prompt no longer hardcodes habit phrases. Instead, the Habit Definitions DB owns the canonical list, and both bots read it at runtime.

Why

Hardcoding "washed face → Wash Face" in the brain prompt meant every new habit Justin added required a code edit. On 2026-04-30 he flagged this: "think about how these systems work together." Coordinator should write the schema, capture should read it.

Pieces

Piece Location
Notion property Aliases (rich_text) on Habit Definitions DB 3510950b-d7a9-8112-a196-cb92f2e5d7df
Tool that writes aliases tool_create_habit_definition in forge/scripts/forge_telegram_inbox_brain.py (param aliases: list[str] required; Sonnet generates 4-7 phrases per habit)
Runtime loader core.load_active_habits_block() in forge/scripts/forge_telegram_inbox_brain.py, 15-min process cache
Prompt injection _build_xml_prompt in forge/scripts/forge_telegram_brain.py injects the block into <reference_data> for both personas
Backfill cron forge-habits-alias-backfill.timer (daily 03:30 CT, before 04:00 instantiate). Fills aliases on any active def lacking them. Idempotent.
One-shot script forge/scripts/forge_habits_alias_backfill.py (also runnable manually)

Format injected into prompt

Justin's active habits (use the EXACT name in log_habit; match Aliases against his message):
- 💧 Hydrate (Health, Daily, goal 15 cups). Aliases: drank water, had water, cups of water, hydrated, ...
- 🏋️ Gym (Health, 3 days/week, goal 1 session). Aliases: hit the gym, did my workout, lifted, ...

Add-a-habit flow (end-to-end)

  1. Justin tells @forge_lifeos_coordinator_bot: "add a habit, stretch every morning 5 minutes"
  2. Coordinator brain calls tool_create_habit_definition(name='Morning Stretch', icon='🧘', category='Health', color='green', cadence_target='Daily', goal_qty=5, goal_unit='minutes', aliases=['stretched', 'did my stretch', 'morning stretching', ...]).
  3. Tool creates the def with full schema + aliases + icon, AND creates today's log row.
  4. Capture brain's 15-min cache refreshes within 15 minutes (or instantly on next bot restart). If aliases were omitted, the 03:30 cron fills them overnight.
  5. Justin texts @forge_inbox_capture_bot: "stretched this morning". Brain matches against the alias list, calls log_habit(habit_name='Morning Stretch').

Failure modes + safeguards

  • Cache stale. load_active_habits_block() 15-min TTL means a brand-new habit might not be recognized for up to 15 min. Bot restart or force=True invalidates immediately.
  • Notion query fails. Loader returns last-good cache, else a stub. Bot falls back to fuzzy title contains matching on log_habit.
  • Sonnet writes empty aliases. Backfill cron catches it the next 03:30 CT. Tool description requires aliases param; brain prompt won't omit it.
  • Conflicting aliases between morning/nightly variants (e.g. "brushed teeth" matches both Brush Teeth defs). Brain prompt rule disambiguates by time of day.

Extending the pattern

Same shape applies to anything with a fixed taxonomy: food categories, time-block categories, knowledge tags. Each gets a Notion-side canonical list with aliases, a runtime loader, and a prompt-injection slot. Coordinator writes once, capture reads every session.

[Claude Code]