Skip to content

Temporal

Huddle uses Temporal (self-hosted) for all durable, multi-step, cross-service work — document parsing, bond automation, and transactional email. Anything that must survive a restart, retry cleanly, or outlive a browser session runs as a Temporal workflow.

  • The API is the sole Temporal client. It defines no workflows — it starts them (on user actions or internal events) and watches their status (a single internal poller streams changes out over SSE).
  • RAG and Notification are Temporal workers. Each polls its own task queue and executes the workflows registered on it.
graph LR
    API[API — client] -->|start / watch status| RAGQ[rag queue]
    API -->|start| NOTQ[notification queue]
    RAGQ --> RAG[RAG worker]
    NOTQ --> NOT[Notification worker]

The Temporal namespace is the deployment environment (development / staging / production), so each environment’s workflows are isolated.

Queue Worker Workflows
rag-tasks RAG ProcessBondRequest (parent), ParseDocument, GenerateField, FindBond, FillBond, DeleteDocument
notification Notification SendInvite, SendOTP, SendBondRequest

The API derives each workflow’s ID as {taskQueue}/{workflow}/sha256(args). Starting a workflow with identical args terminates and replaces the running one — a natural dedupe for idempotent triggers.

Temporal has no push channel, so workflow.Client runs one internal poller (a Watcher, 1s tick) and the API exposes SSE …/watch endpoints that stream a bare status (running / completed / failed) whenever it changes. The app consumes those streams server-side and re-exposes them as tRPC subscriptions. This is how the UI reflects parse / generate / fill / find progress live — without per-second polling from the browser. See API › Watching workflow status.

Open the Temporal web UI in a browser and sign in with your Huddle Google Workspace account (Google SSO — access is restricted to the org). From there you can inspect workflows, task queues, and — via the Workers tab — live worker health: per-worker task-slot usage, poll activity, and cache hit rate.

Self-hosted on Railway from the temporal repo (its own Railway project, deploys from main). Single-container temporalio/server — all four roles in one process — backed by Postgres, with schema setup and per-environment namespace creation handled at boot. One namespace per environment (production / staging / development); services target theirs via TEMPORAL_NAMESPACE. Deployment, SSO, and configuration specifics live in the repo.