workflow
Durable, type-safe queue workers with @falcondev-oss/workflow on Redis.
Skill by falcondev-oss
Chalk Labs
Need help deploying workflow?
Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/falcondev-oss-workflow
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: productivity
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/workflow
What's inside
- Skill name
workflow- Description
- Durable, type-safe queue workers with @falcondev-oss/workflow on Redis. Use when working with WorkflowNamespace / createWorkflow / defineWorkflow, handle() and returns<T>(), step.do / step.wait / step.waitUntil, workflow.run / runIn / runAt / work(), job.wait(), upsertSchedule cron schedules, groupId ordering and numeric priority, or the createRedis connection setup.
- License
- MIT
- Instructions
- 165 lines in SKILL.md · view on GitHub
Files (2)
- SKILL.md
- options.md · reference/
SKILL.md (first lines)
# @falcondev-oss/workflow
A long-running async process — send an email, sleep three days, send a follow-up — dies with the process that runs it. This library makes it survivable: the workflow body is an ordinary async function, but every `step` it takes is persisted to Redis, so a crash, deploy, or retry **replays** the function from the top and skips straight past the steps that already finished.
Three layers, each minted by the one above:
| Layer | Owns |
|---|---|
| `WorkflowNamespace` | The redis connection, the pub/sub connection, a cross-workflow concurrency cap |
| `WorkflowDefinition` | One queue + one job type: input schema and options. Enqueues, schedules, inspects |
| `Workflow` | A definition plus its `run` handler, so it can also `work()` |
| `WorkflowJob` | One enqueued run: `id`, `groupId`, `wait()` |
Everything is exported from the package root: `WorkflowNamespace`, `createRedis`, `expBackoff`, `TimeoutError`, `ResultExpiredError`, `JobAlreadyExistsError`, `NonRecoverableError`. Always mint workflows through the namespace (`createWorkflow` / `defineWorkflow`), never `new Workflow()`.
## Setting up
```ts
import { createRedis, WorkflowNamespace } from '@falcondev-oss/workflow'
const namespace = new WorkflowNamespace({
id: 'app',
redis: await createRedis({ url: process.env.REDIS_URL }),
logger: console, // opt-in; no logging by default
})
```
The namespace owns all configuration: `redis`, `logger` (`info`/`success`/`error`/`debug`/`warn`, all optional), and the option bags its workflows inherit. Without `redis` it falls back to a process-wide connection to `localhost:6379`. `createRedis` applies the connection options the library needs (`maxRetriesPerRequest: null`, offline queue off, a capped retry strategy), so build connections with it rather than `new IORedis()`.
The namespace registers its own exit hook, so `SIGTERM` drains workers and disconnects. Pass `autoClose: false` and call `namespace.close()` yourself in tests.
## Defining a workflow
```ts
const onboarding = namespace.createWorkflow({
id: 'onboarding',
schema: z.object({ userId: z.string(), name: z.string() }),
getGroupId: (input) => input.userId,
async run({ input, step }) {
await step.do('welcome', () => sendWelcome(input.userId))
await step.wait('settle', 3 * 24 * 60 * 60 * 1000)
const engaged = await step.do('check engagement', () => isEngaged(input.userId))
return { engaged }
},
})
```
- **`id`** scopes every redis key for this workflow. Changing it orphans in-flight jobs.
- **`schema`** is any [Standard Schema](https://standardschema.dev) (zod, arktype, …), and validates twice: at enqueue and again in the worker. A failure throws `Invalid workflow input`. Omit it and `input` is typed `undefined`.
- **`getGroupId`** derives the group from validated input; an explicit `groupId` on the run call wins over it.
- **`run`** receives `{ input, step, span }` and its resolved value becomes the job output. Payload, step results, and output all round-trip through superjson, so `Date`, `Map`, `Set`, and `BigInt` survive intact.
- `queueOptions` / `workerOptions` / `jobOptions` set per-workflow defaults; the same keys on the namespace are shallow-merged underneath them. See [`reference/options.md`](reference/options.md).
## Definitions and handlers
`createWorkflow` puts the handler in the workflow's module, so every module that enqueues it also loads everything the handler imports. When enqueueing code should stay cheap (client modules, import cycles, slow test startup), split it:
```ts
// jobs/onboarding.ts: imported wherever jobs are enqueued
export const onboarding = namespace
.defineWorkflow({ id: 'onboarding', schema: z.object({ userId: z.string() }) })
.returns<{ engaged: boolean }>()
// handlers/onboarding.ts: imported only by the worker process
export default onboarding.handle(async ({ input, step }) => ({ engaged: await check(input.userId) }))
// worker.ts
import onboarding from './handlers/onboarding'
await onboarding.work()
```
- `defineWorkflow` takes every `createWorkflow` option except `run`. The definition has every workflow method except `work()`.
- `returns<T>()` declares the output type, so `job.wait()` is typed for enqueueing code. Without it the definition's output is `unknown`.
- `handle(run)` returns a full `Workflow`. Its handler must return the declared type; without `returns<T>()` the output is inferred from the handler.
- `handle()` and `returns()` are type errors on a workflow that already has a handler. The library never `import()`s handlers, so the worker entry must import every handler module it serves.
```ts
await onboarding.work() // start a worker in this process
const job = await onboarding.run({ userId: 'u1', name: 'John' })
const { engaged } = await job.wait() // typed as the run() return
```
`run(input, opts?)` enqueues now, `runIn(input, delayMs, opts?)` after a delay, `runAt(input, date, opts?)` at a timestamp. All three resolve to a `WorkflowJob` as soon as it is enqueued — they do not wait for it to execute. `opts` accepts `groupId`, `priority`, `maxAttempts`, and `jobId`.
`job.wait(timeoutMs?)` blocks on a pub/sub notification and returns the deserialized output. It works from a pure producer with no worker in the process. It rejects with:
| Error | When |
|---|---|
| the workflow's own `Error` | `run` threw and exhausted its attempts — the original message is preserved |
| `TimeoutError` | `timeoutMs` elapsed first (no default; waits forever without it) |
| `ResultExpiredError` | the job finished but its result record aged out of `resultTtl` (default 300 s) |
Passing an explicit `jobId` that is already in use rejects with `JobAlreadyExistsError` — the deduplication handle for "enqueue this at most once".
## Steps and replay
`step.do(name, run)` is the unit of durability. It memoizes **on completion**: once it returns, its result is written to the job's step hash, and any later attempt returns the cached value without re-running the body. So a retry replays completed steps instantly and re-executes only the step that failed.
```ts
const user = await step.do('load user', () => db.user.find(input.userId))
await step.do('charge', async ({ step, span }) => {
span.setAttribute('user.plan', user.plan)
await step.do('authorize', () => gateway.authorize(user)) // nested → 'charge|authorize'
await step.do('capture', () => gateway.capture(user))
})
```
The callback receives `{ step, span }`: a nested `step` whose names are prefixed with the parent's (`parent|child`), and the OTel span for the step. Step names are the memoization key — **keep them stable and unique within a job**, and treat renaming a step as invalidating its cached result.
`step.wait(name, durationMs)` is a durable sleep: it persists `startedAt` on first entry, so a replay sleeps only the *remaining* time rather than the full duration again. `step.waitUntil(name, date)` is the same thing against an absolute time.
Three properties worth holding onto:
- **No step-level retry.** `step.do` takes no options bag; a throw fails the whole job, and retries are job-level (`maxAttempts`).
- **Cancellation is cooperative.** A worker that loses its claim (lock stolen, heartbeat lost) aborts the job's signal: `step.wait` rejects immediately, and `step.do` refuses to *start* new work — but a step already running is not interrupted. Long CPU-bound steps hold their slot until they return.
- **`Promise.all` over steps does not cancel siblings.** When one parallel step throws, the others keep running to completion; the job failure waits for them (and logs a warning). Sequence steps that must not outlive a failure.From falcondev-oss/skills, MIT.
More from falcondev-oss/skills
- 0.598score
conventional-commits
falcondev-oss
Format commit messages, branch names, PR titles, and issue titles to the Conventional Commits 1.0.0 spec.
SkillAFreshness: A★ 2Claude Code +9 - 0.597score
babysit-pr
falcondev-oss
Monitor a pull request through review and CI.
SkillAFreshness: A★ 2Claude Code +9 - 0.597score
file-pr
falcondev-oss
File a PR from the current branch.
SkillAFreshness: A★ 2Claude Code +9 - 0.593score
bug-review
falcondev-oss
Cold review of a diff for merge-blocking bugs, each with a way to demonstrate the failure.
SkillAFreshness: A★ 2Claude Code +9 - 0.592score
review-local-changes
falcondev-oss
Review the local changes cold and report the findings to the user.
SkillAFreshness: A★ 2Claude Code +9 - 0.592score
implement-pr
falcondev-oss
Implement a change and deliver a green, review-ready pull request with explicit scope and evidence.
SkillAFreshness: A★ 2Claude Code +9 - 0.584score
quick-iteration
falcondev-oss
Enter a quick iteration session.
SkillAFreshness: A★ 2Claude Code +9 - 0.557score
falcondev-form
falcondev-oss
Building or editing forms with @falcondev-oss/form — the type-safe, reactive, schema-driven form library (packages form-core, form-react, form-vue) built on…
SkillBFreshness: B★ 2Claude Code +9 - 0.554score
shareflare
falcondev-oss
Share any local file through a public URL.
SkillBFreshness: B★ 2Claude Code +9 - 0.507score
trpc-typed-form-data
falcondev-oss
Type-safe file uploads through tRPC with @falcondev-oss/trpc-typed-form-data.
SkillCFreshness: C★ 2Claude Code +9
Signals
- Stars
- 2
- Forks
- 0
- Open issues
- 0
- Last commit (this folder)
- Oct 13, 2026 (17 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/falcondev-oss/skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from falcondev-oss
- 0.598score
conventional-commits
falcondev-oss
Format commit messages, branch names, PR titles, and issue titles to the Conventional Commits 1.0.0 spec.
SkillAFreshness: A★ 2Claude Code +9 - 0.597score
babysit-pr
falcondev-oss
Monitor a pull request through review and CI.
SkillAFreshness: A★ 2Claude Code +9 - 0.597score
file-pr
falcondev-oss
File a PR from the current branch.
SkillAFreshness: A★ 2Claude Code +9 - 0.593score
bug-review
falcondev-oss
Cold review of a diff for merge-blocking bugs, each with a way to demonstrate the failure.
SkillAFreshness: A★ 2Claude Code +9 - 0.592score
review-local-changes
falcondev-oss
Review the local changes cold and report the findings to the user.
SkillAFreshness: A★ 2Claude Code +9 - 0.592score
implement-pr
falcondev-oss
Implement a change and deliver a green, review-ready pull request with explicit scope and evidence.
SkillAFreshness: A★ 2Claude Code +9
Done for you
Rather not set it up yourself?
Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.
See how we build AI agents →Read the case studies
Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.
- launches shipped
- 50+
- launches shipped
- media placements
- 500+
- media placements
- founders on every account
- 2
- founders on every account
Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block
Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.
Get workflow running in your business
Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.
Cite this page
workflow skill. Agent Mart. https://agentmart.dev/skills/falcondev-oss-workflow (last updated 2026-10-24)