Event-Driven Architecture
How Cremind reacts to external changes in sub-second time using a relay WebSocket, a markdown event log, and a filesystem watcher — instead of polling.
Most assistants only act when you type. Cremind can also act when the world changes. A new email arrives, a calendar event moves, a ticket is updated — and the agent runs on its own, immediately, with that change as if you'd just described it. The whole pipeline is designed for sub-second latency, and it does this without polling, cron jobs, or heartbeat loops.
This page traces an event from the outside world all the way to a response on your screen. The pieces live in the app/events package: EventManager, FileWatcherManager, and runner.py.
The problem with polling
The naive way to "watch" an external service is to ask it over and over: poll an inbox every 30 seconds, run a cron job every minute, ping a heartbeat. Polling is a trade-off you always lose — poll often and you waste requests and hit rate limits; poll rarely and your assistant is stale. None of it is truly event-driven; it's just guessing on a timer.
Cremind takes a different path: it waits to be told.
The pipeline
External service
│ (change happens)
▼
Cremind Connect relay ──── content-free "resync" nudge ────► Skill listener
│ (holds a long-lived WebSocket)
│ pulls the delta with its own local token
▼
events/<event_type>/<new>.md (markdown file written)
│
filesystem watchdog detects new file
▼
fan-out to every subscribed conversation
│
runner.py runs the agent
(event as a synthetic user message)
│
SSE stream ──► your clients1. A long-lived WebSocket, not a poll loop
A skill's listener holds a long-lived WebSocket connection to the Cremind Connect relay. There's no polling — the connection sits open and idle until something happens.
2. A content-free "resync" nudge
When the external service changes, the relay sends the listener a small, content-free "resync" nudge. It deliberately carries no data — just "something changed, go look." This keeps the relay simple and keeps your data flowing through your own credentials rather than the relay.
3. The listener pulls the delta
On receiving the nudge, the listener pulls the actual change — the delta — directly from the source, using its own local token. It then writes the result as a markdown file into the skill's events/<event_type>/ folder.
4. A filesystem watcher fans it out
A filesystem watchdog notices the new markdown file the instant it lands. It fans the event out to every conversation subscribed to that event type, so multiple conversations can react to the same change.
5. The agent runs immediately
runner.py runs the agent immediately, injecting the event as a synthetic user message — as though you had just pasted the change in yourself. The agent reasons over it and acts using its normal tool plane. The output streams to your clients over SSE, so you watch the response unfold in real time.
Why content-free nudges?
The relay never needs to see your data. It only says "resync"; the listener does the fetching with its own token. That keeps the sensitive content on your machine and the relay's job dead simple — which is also what makes the round trip fast.
Why markdown files on disk?
Writing each event to a file in events/<event_type>/ is a small choice with big benefits:
- Durability — the event is on disk before the agent runs, so it survives a crash or restart.
- Auditability — you can read exactly what the agent reacted to, after the fact.
- Decoupling — the listener (which fetches) and the runner (which reasons) don't have to be in lockstep; the file is the hand-off point, and the watcher bridges them.
Polling vs. event-driven
| Polling / cron / heartbeat | Cremind events | |
|---|---|---|
| Trigger | A timer fires | An actual change happens |
| Freshness | Stale between polls | Sub-second |
| Cost | Repeated requests, even when nothing changed | One nudge per real change |
| Data path | Often pulls everything each cycle | Pulls only the delta, with your token |
What you need
The relay side is provided by Cremind Connect, the companion sub-product with its own site. It summarizes to: Connect is the hosted relay that turns external webhooks and change notifications into the resync nudges your listeners wait on. See connect.cremind.io for what Connect covers and how to set it up.