External triggers.
Five trigger paths matter. Desktop chat, mobile chat, and routines are real today. Slack-style inbound messages and public webhooks are product work, not shipped features.
1. The user sends a message from the desktop
Standard chat. The desktop UI calls
POST /v1/agents/<path>/sessions. The engine
spawns a CLI subprocess, the response streams back. Chapter 4
covers it. Shipped.
2. The user sends a message from mobile
Mobile is a PWA at tunnel.gethouston.ai. The desktop
engine dials an outbound WebSocket to the Houston Relay
(houston-relay/, Cloudflare Worker plus Durable
Object). The relay proxies mobile HTTP and WS over that link.
Same engine route as desktop chat. Shipped.
3. A Slack-style inbound message arrives
This is not shipped today. Houston can let agents use Slack through Composio tools, and the UI has generic channel concepts, but there is no dedicated engine Slack crate, route, or two-way sync handler in the current repo.
Future shape: an inbound channel event maps to one agent session,
writes a trigger_runs row, starts a turn through the same
durable turn path as chat, and posts the answer back through the
channel adapter.
4. A cron fires
Each agent has routines. 0 7 * * * runs at 7am every
day. Today the active scheduler lives inside
engine/houston-engine-core/src/routines/scheduler.rs.
It sleeps until the fire time and calls run_routine
through a RoutineDispatcher.
Current routine run history exists in the routines data model, but
there is no global SQL trigger_runs ledger tied to durable
turn replay. Chapter 6 adds that ledger and makes missed-fire policy
explicit.
5. A webhook arrives
Stripe sends a payment event. GitHub sends a PR notification. Each one routed to a specific agent.
Honest status: engine/houston-events has typed input
scaffolding, including Webhook, plus an unbounded queue.
That crate is not the central path for current chat or routines. There
is no public HTTP route, webhook token table, signature verification,
handler, durable trigger row, or session dispatch integration.
The route: POST /v1/hooks/<route_token> in a new
engine/houston-engine-server/src/routes/hooks.rs. The
handler must verify the hook token and provider signature, insert
trigger_runs, then start a durable turn.
Each webhook gets its own route token, separate from the engine bearer. Stripe's token only fires the Stripe hook. A compromised Stripe token lets the attacker fake payment events but doesn't let them read your chat history or trigger other agents.
Bounded queue (M1 prerequisite)
The old event queue is unbounded
(mpsc::unbounded_channel at
engine/houston-events/src/queue.rs:36). A webhook
flood must never be allowed to balloon memory. M1 either wires a
bounded queue into the real trigger path or implements bounded intake
at the route layer, with explicit accept/reject audit rows. Without
that change, opening
POST /v1/hooks/... to the world is a DoS waiting to
happen.
Wake-on-event for sleeping engines (M4)
Once the engine learns to sleep in Cloud (Chapter 10), the relay
in front will catch inbound triggers, wake the engine via the
platform API (Fly Machines, systemd socket activation, etc), wait
for /v1/health to respond, then proxy the request
through.
Human chat, mobile chat, Slack-style inbound, webhook, and scheduled
routines should converge on the same durable acceptance path. Routines
may be woken by a central scheduler instead of a relay request, but
they still need the same trigger_runs and turn ledger.
Webhook input scaffolding: engine/houston-events/src/types.rs. Old queue, unbounded today: engine/houston-events/src/queue.rs:36. Active routines scheduler: engine/houston-engine-core/src/routines/scheduler.rs. Routine runner: engine/houston-engine-core/src/routines/runner.rs. Relay: houston-relay/. Missing pieces for webhooks: route, token model, signature verification, bounded intake, trigger_runs, and durable session dispatch.