Skip to content

feat: event-driven architecture + external triggers + Flow visualization#128

Merged
luokerenx4 merged 3 commits intomasterfrom
dev
Apr 17, 2026
Merged

feat: event-driven architecture + external triggers + Flow visualization#128
luokerenx4 merged 3 commits intomasterfrom
dev

Conversation

@luokerenx4
Copy link
Copy Markdown
Contributor

Summary

Three commits that progressively open up Alice's event-driven lifecycle, culminating in a visual representation.

  • fix: cron engine save() race that caused intermittent ENOENT when the periodic onTick save() collided with a UI-triggered save()
  • feat: webhook ingest at POST /api/events/ingest + TriggerListener that routes external events through the AI pipeline, plus a causedBy field on EventLogEntry for causal tracing
  • feat: constrained emitter — listeners declare emits tuples, Registry wraps handle() with a typed ListenerContext whose emit() is restricted (compile + runtime) to the declared set and auto-populates causedBy. GET /api/topology exposes the resulting graph. New Flow tab on /automation renders it with @xyflow/react and pulses nodes in real time via SSE.

Test plan

  • npx tsc --noEmit — backend + UI clean
  • pnpm test — 1043/1043 pass (+14 new registry tests, +schema coverage for trigger.done / trigger.error)
  • Manual: curl -X POST /api/events/ingest -d '{...}' → 201, downstream trigger.done with causedBy
  • Manual: /automation → Flow tab renders full topology, nodes pulse when events flow
  • Manual: curl /api/topology → expected shape (11 event types, 5 listeners with emits)

🤖 Generated with Claude Code

Ame and others added 3 commits April 17, 2026 13:22
The tmp filename used process.pid alone, which is constant within a
process. Concurrent save() calls (e.g. onTick and a UI-triggered
add/update) collided on the same tmp file — one rename succeeded,
the next found no file and threw ENOENT.

Add a per-call randomUUID suffix so each save owns its own tmp path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce a POST /api/events/ingest endpoint that lets external
systems push events into the EventLog. A new TriggerListener
subscribes to 'trigger' events and routes them through the AgentCenter
like CronListener does for cron.fire — same prompt-then-notify pipeline.

Also adds a 'causedBy' field on EventLogEntry so emitted child events
(cron.done, heartbeat.done, trigger.done, etc.) link back to the
event that triggered them. Enables future causal tracing and graph
visualization of the event flow.

With this, Alice is no longer a closed loop — any external producer
(webhook, API client, monitoring system) can pump an event and see
Alice react.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Make `emits` declarations load-bearing: each Listener declares the event
types it can emit, and the Registry wraps its handle() with a typed
ListenerContext whose emit() is constrained to those types (compile-time
via generics, runtime via a Set check). The emitter auto-populates
causedBy with the parent event's seq — no more manual passing.

Expose the resulting topology at GET /api/topology so the frontend can
render Alice's async lifecycle as a graph. Add an "Flow" tab to the
Automation page using @xyflow/react: event-type nodes on the left,
listener nodes in the middle, emitted-type nodes on the right. Nodes
pulse when events flow through via SSE.

With this, users can see at a glance what Alice listens to, how she
reacts, and what each reaction produces downstream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@luokerenx4 luokerenx4 merged commit dd4eb39 into master Apr 17, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant