Atlas recap

2026-10-09

How Fimo events start routine runs

Events say what happened. Triggers decide what should run. The engine does the work. Infrastructure carries and stores it.

Scope Architecture + planned triggers Base bdf39fc Depth Normal Evidence Source inspected Current + planned

Current + planned

Events, infrastructure and routine execution

Follow a site publish from its first event to an agent run, then see what happens when the load grows.

Outcome

A site publish can notify Studio, prepare webhooks and, with the planned smart triggers, start a routine. These are separate reactions to the same fact.

Fimo already has the event dispatcher, QStash delivery and agent engine. Smart triggers add a decision between the event and the routine run. More API replicas can share the work; the database keeps each conversation's turns in order.

Responsibilities

Four parts, four different jobs

These are responsibilities, not four separately deployed services. Today the dispatcher, job handlers and engine are wired into the API. The realtime events server is a separate Cloudflare service.

Events

What happened?A named fact with an ID, project scope and data. Example: project.publication.succeeded.

Triggers · planned

Should a routine run?Find matching routines, apply filters, group changes and enforce limits.

Engine

Do the workAdmit a turn, call models and tools, persist messages and settle the result.

Infrastructure

Carry and store workAPI replicas run code. QStash delivers jobs. Postgres stores state. Redis carries live streams.

Listening

Handlers register once when each API process starts

A subscriber declares the event name it cares about, its own unique name, and whether it runs locally or through QStash. Publishing an event looks up that registry. There is no polling loop per event, and QStash does not broadcast it to every API replica.

flowchart TB
  A[Site published] --> B[Event dispatcher]
  B -->|sync| C[Local handler]
  B -->|async| D[QStash message]
  C --> E[Studio notification]
  D --> F[Shared API endpoint]
  F --> G[Named handler]
Existing flow. QStash calls /jobs/deliver-application-event with the event and the name of one subscriber.
  • One message per async subscriberThree async listeners mean three messages, each with its own retry. No named QStash queue per event.
  • Local is awaitedThe publisher waits for local handlers to finish and for QStash to accept async handoffs. It does not wait for async handlers to finish.
  • A replica can receive the deliveryIt uses the same registry to run only the addressed handler. Adding replicas does not multiply the logical subscribers.

From fact to action

A trigger queues a routine; the engine runs its next turn

Example: after a site is published, run the SEO routine. The new trigger subscriber matches routines in that project. The wider proposal groups matching items for 60 seconds and asks Jev a yes/no question only when a rule needs judgment.

flowchart TB
  A[Site publish event] --> B[Match routine]
  B --> C[Group matching items]
  C --> D[Optional Jev check]
  D --> E[Limits + routine job]
  E --> F[Stored turn queue]
  F --> G[Engine runs agent]
  classDef planned fill:#332b20,stroke:#efb765,color:#f8e2ba,stroke-dasharray:5 4;
  class B,C,D,E planned;
Dashed boxes are planned trigger work. Event delivery, routine-fire jobs, stored turn queues and the engine already exist.

The first slice is simply after each publish. Content filters, grouping and Jev follow. The existing routine-fire payload needs an event-trigger extension.

QStash deliveryTransport
Question
Which HTTP job must be delivered?
Unit
A subscriber delivery or routine-fire job.
Conversation queueStored in Postgres
Question
Which message gets the next turn?
Unit
Pending work in one routine's conversation.

Scaling

Spread independent work across replicas; keep each chat ordered

The deployment workflow targets Kubernetes through ArgoCD. The source permits different API replicas to handle different deliveries and conversations. It does not show the live replica count or autoscaling policy.

Incoming work

QStashDelivers subscriber and routine jobs to the shared API address

API replicas

Replica AHandlers + engine turns
Replica BHandlers + engine turns
Replica NSame code and subscriber registry

Shared services

PostgresPending messages, run state and execution ownership
RedisLive stream replay and cancellation
Models + sandboxesProvider calls and project execution
  • Across conversationsIndependent turns can run concurrently. More replicas add compute, up to database, model-provider and sandbox limits.
  • Inside one conversationA database claim and unique index allow one live root turn. Adding replicas cannot make the same conversation run two root turns at once.
  • While a turn runsThe worker renews a database lease. Postgres decides ownership; Redis transports live output. Another replica can serve the viewer's stream.
  • Realtime notificationsCloudflare Durable Object rooms serve Studio connections separately from backend jobs and agent execution.

Capacity and failure

More replicas help only when the shared limits allow it

This is a scaling path, not a measured capacity claim. A burst multiplies by the number of async subscribers. For example, 100 events with 3 async subscribers create 300 initial delivery messages, plus any retries and downstream jobs.

API

More simultaneous jobs and turns. Add replicas when CPU or memory is the bottleneck. Handlers and engine turns currently share the API deployment; separating worker capacity would be future infrastructure work.

Limits

Protect the shared services. Monitor database connections, model limits, sandbox capacity and queue age. The current event adapter sets deduplication, but no explicit flow-control or per-project delivery limit.

Bursts

Reduce unnecessary agent work. Planned code filters, batching and daily routine limits can reduce runs. They do not remove the cost of delivering the original events.

Failure

Retry delivery, avoid repeated effects. Failed handlers return 500. Each handler must tolerate duplicates. The bus has no event-order guarantee or transactional outbox, so a committed change can still lose its event before handoff.

Evidence

The drawing separates current code from the trigger proposal

Current behavior is pinned to develop at bdf39fc. Smart-trigger behavior comes from FIMO-2714, with the first slice in FIMO-2804.

Source behind the drawing5 pinned files · optional detail

Infrastructure sources: deployment workflow · Cloudflare rooms · Redis stream replay · queue recovery

VerifiedSource inspection

Listener registration, QStash fan-out, routine enqueue, database turn ownership and deployment wiring.

PlannedSmart triggers

Matching, grouping, Jev and event-trigger payloads are proposed, not claimed as deployed.

UnknownProduction capacity

No load test, live replica inventory, autoscaling settings or provider quotas checked.

Source peek

Open at head
Path copied