Skip to content

Force Flow

Tommy at the Force Flow switchboard — routing every alert in the haus through one brain

Every notification in the haus used to go through three different systems. Home Assistant had its own push notifications. The Living Force had sanctum_notify. The Council Router had its escalation chain. Three notification stacks, four channels, zero coordination — which is how you end up with Yoda’s cloned voice shouting about Ring motion through the bathroom speaker at 7 AM while your phone buzzes twice about the same squirrel.

Force Flow is the fix. One service. One brain. Everything flows through it.

ALL NOTIFICATION SOURCES
┌──────────┬───┴───┬──────────┐
HA Living Force Council Agents
│ │ Router │
└──────────┴───┬───┴──────────┘
┌──────▼──────┐
│ Force Flow │
│ port 4077 │
└──────┬──────┘
┌────────────┼────────────┐
│ │ │
┌─────▼─────┐ ┌───▼───┐ ┌─────▼─────┐
│ iPhone │ │ Sonos │ │ Dashboard │
│ (push) │ │ (TTS) │ │ (banner) │
└───────────┘ └───────┘ └───────────┘

Every notification hits the /notify endpoint with a source, severity, title, and message. Force Flow decides where it goes based on two things: how bad is it, and what time is it.

The thing that makes this tractable is context discipline. Force Flow is the delivery brain, not the memory layer. When a council session needs message history, notes, calendar state, or local files to decide whether something is noise or a real escalation, that context comes from Jocasta MCP. Jocasta reads the archive; Force Flow decides whether the archive warrants waking a human up.

Severity08:00–22:0022:00–08:00 (Quiet)
infoDashboard onlyLog only
warniPhone (silent) + DashboardLog only
erroriPhone (sound) + DashboardiPhone (silent) + Dashboard
criticaliPhone + Sonos + Signal + DashboardiPhone + Sonos (reduced) + Signal + Dashboard

Ring doorbells love to fire four motion events in thirty seconds. Without dedup, that is four push notifications, four Sonos announcements, and one family member who is now very awake and very angry.

Force Flow hashes each notification by source + title + message. If the same hash appears within a 120-second window, duplicates are suppressed. The first one goes through. The rest get logged with deduplicated: true and go nowhere.

Non-critical notifications are capped at 10 per hour. When a service is flapping — restarting every 60 seconds, firing an alert each time — you get the first 10 and then silence until the hour rolls over.

Critical bypasses the rate limit. If something genuinely urgent fires 15 times, you hear about it 15 times. That is a feature, not a bug.

Terminal window
# Send a notification
curl -X POST http://127.0.0.1:4077/notify \
-H "Content-Type: application/json" \
-d '{"source": "windu", "severity": "error", "title": "Motion Alert", "message": "Ring detected motion at front door"}'
# Response
{"status": "sent", "channels": ["iphone", "dashboard"], "deduplicated": false, "rate_limited": false, "quiet_hours": false}
# Health check
curl http://127.0.0.1:4077/health
{"status": "ok", "quiet_hours": false, "hourly_count": 3, "max_per_hour": 10}
# Query history
curl "http://127.0.0.1:4077/history?limit=10&severity=error"
FieldTypeRequiredDescription
sourcestringyesWho is sending: ha, windu, sanctum, living-force, council
severitystringyesinfo, warn, error, critical
titlestringyesShort title for the notification
messagestringyesFull message body
force_channelslistnoOverride automatic routing (e.g. ["iphone", "sonos"])

HA automations call Force Flow via rest_command.force_flow instead of notify.notify or notify.mobile_app. This means HA never decides where a notification goes — it describes what happened and how bad it is, and Force Flow handles the rest.

# In an automation action:
- service: rest_command.force_flow
data:
source: windu
severity: error
title: "Ring Motion While Away"
message: "Motion detected at Front Door while armed away."

The sanctum_notify bash function in lib/notify.sh routes through Force Flow with a fallback to macOS Notification Center if Force Flow is down.

4077/notify
sanctum_notify "Service Down" "Gateway is unreachable" "error"
# → Falls back to osascript if Force Flow is down

Council agents sending alerts to Bert route through Force Flow instead of direct Signal messages. The escalation config in escalation.json defines severity thresholds; Force Flow handles the actual delivery.

Jocasta does not deliver notifications herself. She gives the council the missing record before a notification is sent: recent Messages context, calendar conflicts, Apple Notes fragments, prior files, and local system telemetry. That keeps Force Flow from becoming a bloated reasoning service. The split is deliberate:

  • Jocasta answers “what do we know?”
  • Force Flow answers “does Bert need to hear about this now?”

For example, a council workflow can pull the last relevant thread from Jocasta, synthesize the risk, then hand Force Flow a single high-signal warn or critical event instead of three half-informed pings.

Every notification — sent, deduplicated, or rate-limited — is logged to SQLite at ~/.sanctum/force-flow/notifications.db. This means you can answer “what woke me up last Tuesday” with a query instead of a guess.

Terminal window
# Last 5 critical alerts
curl "http://127.0.0.1:4077/history?limit=5&severity=critical"
# Direct SQLite query
sqlite3 ~/.sanctum/force-flow/notifications.db \
"SELECT timestamp, source, title FROM notifications WHERE severity='critical' ORDER BY id DESC LIMIT 10"
PropertyValue
Port4077
LaunchAgentcom.sanctum.force-flow
Binary~/.sanctum/force-flow/.venv/bin/python3.12
Script~/.sanctum/force-flow/force_flow.py
Log~/.openclaw/logs/force-flow.log
Database~/.sanctum/force-flow/notifications.db
Tests~/.sanctum/force-flow/test_force_flow.py (42 tests)
KeepAliveYes (restarts on crash)

Before Force Flow, the notification stack looked like this:

ComponentWhat it didProblem
notify.notify (HA)Push to all devicesNo dedup, no quiet hours, double-notified
notify.mobile_app (HA)Push to iPhoneSeparate from notify.notify, often both fired
script.security_announcement (HA)Sonos TTS via bridgeHad its own quiet hours logic, no coordination
sanctum_notify (bash)macOS + dashboard + SignalCompletely separate stack, no dedup
escalation.json (Council)Severity → channel mappingConfig only, no enforcement

Five systems, zero awareness of each other. Force Flow is one system that knows everything.

If Force Flow is the switchboard, Jocasta MCP is the archive room behind it: less glamorous, more dangerous, and usually the reason the right alert reaches the right person with fewer false alarms.

As of March 30, 2026, the migration is complete. Every notification source in the haus routes through Force Flow:

SourceIntegrationFallback
HA automations (11)rest_command.force_flowNone (HA native)
Living Force (swap alerts)curl :4077/notifyosascript
sanctum_notify() (lib/notify.sh)curl :4077/notifyosascript
Proxy watchdogcurl :4077/notifyDirect Signal
ChaosForge (weekly chaos)curl :4077/notifyLog warning

There are no remaining direct notification paths. If it alerts, it flows through 4077.