Cascade
Cascade runs background tasks and keeps a durable record of each run. I started the project and maintain it.
A task starts in the TypeScript SDK, passes through the API and Redis queue, runs in a worker, and appears in the dashboard with its output and logs. It can be retried, scheduled, cancelled, or replayed.
Why I built it
The project started with three questions: What happens if a worker dies? What if the same request arrives twice? What if a deployment changes while a task is still running? I built Cascade to answer them in one system.
What had to be true
- A run could not disappear when a worker stopped.
- Repeating a request could not create duplicate work.
- One organization could never read another one’s data.
- The dashboard had to show stored system state, not temporary browser state.
- Tests had to use the real SDK, API, queue, and worker path.
The important choices
PostgreSQL is the source of truth for runs and deployments. Redis handles short-lived coordination such as queues, rate limits, and concurrency. Large payloads can move to object storage. The dashboard reads the same durable records used by the workers.
Run lifecycle
- TypeScript SDKSends a task trigger.
- API + PostgreSQLRecords a pending run before execution.
- RedisQueues the run ID for a worker.
- WorkerClaims the run and writes logs, heartbeats, and output.
- DashboardReloads the stored state for the user.
What changed my approach
An early browser test created successful records directly in the database. The screen passed even though that setup skipped the real product. I replaced it with a test that starts from the SDK and continues through the API, queue, worker, and dashboard.
The replacement is slower. It catches failures the old test could not see.