{"id":"6a1dcf63-e592-4106-b066-dc7a268687e6","shortId":"3zE5LC","kind":"skill","title":"saq","tagline":"Use when editing SAQ task queues, saq imports, background jobs, async workers, enqueueing jobs, CronJob schedules, queue configuration, worker lifecycle, or async-native task processing.","description":"# SAQ (Simple Async Queue) Skill\n\nSAQ is a lightweight async task queue built on asyncio. Supports Redis and Postgres backends. Designed for simplicity with async-native patterns — no separate broker process, no class-based tasks, just plain async functions.\n\n## Code Style Rules\n\n- Use PEP 604 for unions: `T | None` (not `Optional[T]`)\n- **Never** use `from __future__ import annotations`\n- Use Google-style docstrings\n- All task functions must be `async def`\n- First argument of every task function is always the context dict (`ctx`)\n\n## Quick Reference\n\n### Queue Creation\n\n```python\nfrom saq import Queue\n\n# Redis backend\nqueue = Queue.from_url(\"redis://localhost\")\n\n# Postgres backend\nqueue = Queue.from_url(\"postgresql+asyncpg://user:pass@localhost/db\")\n```\n\n### Task Definition\n\n```python\nasync def send_email(ctx: dict, *, recipient: str, subject: str, body: str) -> None:\n    \"\"\"Send an email as a background task.\n\n    Args:\n        ctx: SAQ context dict (contains queue, job, and custom startup keys).\n        recipient: Email recipient address.\n        subject: Email subject line.\n        body: Email body content.\n    \"\"\"\n    mailer = ctx[\"mailer\"]  # injected via startup hook\n    await mailer.send(recipient, subject, body)\n```\n\n### Enqueueing Jobs\n\n```python\n# Fire and forget\nawait queue.enqueue(\"send_email\", recipient=\"user@example.com\", subject=\"Hello\", body=\"World\")\n\n# Enqueue and wait for result\nresult = await queue.apply(\"send_email\", recipient=\"user@example.com\", subject=\"Hello\", body=\"World\")\n\n# With job options\nawait queue.enqueue(\n    \"send_email\",\n    recipient=\"user@example.com\",\n    subject=\"Hello\",\n    body=\"World\",\n    timeout=30,\n    retries=3,\n    ttl=3600,\n    key=\"email-user@example.com\",  # deduplication key\n)\n```\n\n### CronJob Scheduling\n\n```python\nfrom saq import CronJob\n\n# Run at the top of every hour\nhourly_report = CronJob(\n    function=generate_report,\n    cron=\"0 * * * *\",\n    timeout=300,\n)\n\n# Run every 15 minutes\nhealth_check = CronJob(\n    function=check_health,\n    cron=\"*/15 * * * *\",\n    timeout=60,\n    retries=1,\n)\n```\n\n### Worker Setup\n\n```python\nfrom saq import Worker\n\nworker = Worker(\n    queue,\n    functions=[send_email, process_order, generate_report],\n    cron_jobs=[hourly_report, health_check],\n    concurrency=10,\n    startup=startup_hook,\n    shutdown=shutdown_hook,\n    before_process=before_process_hook,\n    after_process=after_process_hook,\n)\n\n# Run the worker (blocks)\nimport asyncio\nasyncio.run(worker.start())\n```\n\n### Job Options Reference\n\n| Option | Type | Default | Description |\n|---|---|---|---|\n| `timeout` | `int` | `None` | Seconds before job times out. **Always set this.** |\n| `retries` | `int` | `0` | Number of retry attempts on failure |\n| `ttl` | `int` | `600` | Seconds to retain result after completion |\n| `key` | `str` | `None` | Deduplication key — skip if a job with this key is already queued/active |\n| `heartbeat` | `int` | `0` | Seconds between heartbeat updates (use for long-running jobs) |\n| `scheduled` | `int` | `0` | Unix timestamp to delay job start |\n\n### Job Lifecycle\n\n```text\nqueued → active → complete\n                → failed\n                → aborted\n```\n\n### Context Dict\n\nThe `ctx` dict passed to every task contains:\n\n- `ctx[\"queue\"]` — the `Queue` instance\n- `ctx[\"job\"]` — the current `Job` object\n- Any keys added by your `startup` hook (e.g., `ctx[\"db\"]`, `ctx[\"mailer\"]`)\n\n<workflow>\n\n## Workflow\n\n### Step 1: Define Task Functions\n\nWrite `async def` functions with `ctx: dict` as the first positional arg and all task parameters as keyword-only args (after `*`). Keep task functions focused — each task does one thing.\n\n### Step 2: Configure the Queue\n\nCreate a `Queue` using `Queue.from_url()` with your Redis or Postgres DSN. Store the queue instance where it can be shared across your app (module-level, app state, or DI container).\n\n### Step 3: Define Lifecycle Hooks\n\nWrite `startup` and `shutdown` hooks to initialize and clean up shared resources (DB pools, HTTP clients, mailers). Attach resources to `ctx` in `startup` so all tasks can access them.\n\n### Step 4: Schedule CronJobs\n\nWrap any recurring work in `CronJob` instances with explicit cron expressions and timeouts. Do not use external cron tools (crontab, Kubernetes CronJob) for work that belongs in the queue.\n\n### Step 5: Create and Run Worker\n\nInstantiate `Worker` with the queue, task functions, cron jobs, concurrency limit, and lifecycle hooks. Run with `asyncio.run(worker.start())` or integrate into your process manager.\n\n### Step 6: Enqueue from Application Code\n\nCall `queue.enqueue()` for fire-and-forget or `queue.apply()` when you need the result. Use the `key` parameter for natural deduplication (e.g., per-user jobs that should not stack).\n\n</workflow>\n\n<guardrails>\n\n## Guardrails\n\n- **Always set `timeout`** — the default is no timeout. A hung task will block a worker slot forever.\n- **Use `heartbeat` for long-running jobs** — without heartbeat, SAQ may mark a long-active job as stuck and re-queue it. Set heartbeat to roughly 1/3 of expected runtime.\n- **Use `CronJob` for scheduled work** — do not schedule SAQ tasks from external cron tools. CronJobs are managed by the worker and participate in the job lifecycle (retries, timeouts, observability).\n- **First arg is always `ctx`** — SAQ injects the context dict as the first positional argument. Keyword-only task params come after `*`.\n- **Handle graceful shutdown** — call `await worker.stop()` on SIGTERM/SIGINT. Abrupt process kills can leave jobs stranded in `active` state.\n- **Use `key` for deduplication** — if the same logical job can be enqueued multiple times (e.g., per-user sync), set a stable `key` to prevent stacking.\n- **Set appropriate `concurrency`** — default is 10. Lower for CPU/memory-intensive tasks, higher for I/O-bound tasks. Consider backend connection pool sizes.\n- **Do not share mutable state between tasks** — use the context dict (populated per-worker in `startup`) for shared resources like DB pools and HTTP clients.\n\n</guardrails>\n\n<validation>\n\n### Validation Checkpoint\n\nBefore delivering SAQ code, verify:\n\n- [ ] Every task function is `async def` with `ctx: dict` as the first positional arg\n- [ ] All task parameters are keyword-only (defined after `*`)\n- [ ] `timeout` is set on all long-running jobs and `CronJob` definitions\n- [ ] `heartbeat` is set for jobs that run longer than ~30 seconds\n- [ ] Shared resources (DB, HTTP client) are initialized in `startup` hook and attached to `ctx`\n- [ ] `CronJob` is used for scheduled/recurring work (not external cron)\n- [ ] `key` is used where job deduplication is needed\n- [ ] Worker handles graceful shutdown\n\n</validation>\n\n<example>\n\n## Example\n\n**Task:** Background email sender with startup hook, cron health check, and deduplication.\n\n```python\nimport asyncio\nfrom saq import CronJob, Queue, Worker\n\n\n# --- Shared queue (module-level) ---\nqueue = Queue.from_url(\"redis://localhost\")\n\n\n# --- Lifecycle hooks ---\nasync def startup(ctx: dict) -> None:\n    \"\"\"Initialize shared resources and attach to context.\"\"\"\n    # Example: async HTTP client for sending email\n    import httpx\n    ctx[\"http\"] = httpx.AsyncClient()\n\n\nasync def shutdown(ctx: dict) -> None:\n    \"\"\"Clean up shared resources.\"\"\"\n    await ctx[\"http\"].aclose()\n\n\n# --- Task definitions ---\nasync def send_welcome_email(ctx: dict, *, user_id: int, email: str) -> None:\n    \"\"\"Send a welcome email to a new user.\n\n    Args:\n        ctx: SAQ context dict.\n        user_id: ID of the new user.\n        email: Recipient email address.\n    \"\"\"\n    http: httpx.AsyncClient = ctx[\"http\"]\n    await http.post(\n        \"https://api.email-provider.com/send\",\n        json={\"to\": email, \"template\": \"welcome\", \"user_id\": user_id},\n    )\n\n\nasync def process_export(ctx: dict, *, export_id: int) -> dict:\n    \"\"\"Process a data export job.\n\n    Args:\n        ctx: SAQ context dict.\n        export_id: ID of the export record to process.\n\n    Returns:\n        Dict with export result metadata.\n    \"\"\"\n    # Long-running — heartbeat prevents SAQ from marking it stuck\n    job = ctx[\"job\"]\n    # ... processing logic ...\n    return {\"export_id\": export_id, \"rows\": 1000}\n\n\nasync def check_queue_health(ctx: dict) -> None:\n    \"\"\"Scheduled health check — logs queue stats.\"\"\"\n    q: Queue = ctx[\"queue\"]\n    info = await q.info()\n    print(f\"Queue stats: {info}\")\n\n\n# --- CronJob ---\nhealth_check = CronJob(\n    function=check_queue_health,\n    cron=\"*/5 * * * *\",\n    timeout=30,\n)\n\n\n# --- Worker ---\nworker = Worker(\n    queue,\n    functions=[send_welcome_email, process_export],\n    cron_jobs=[health_check],\n    concurrency=10,\n    startup=startup,\n    shutdown=shutdown,\n)\n\n\n# --- Enqueueing from application code ---\nasync def on_user_created(user_id: int, email: str) -> None:\n    await queue.enqueue(\n        \"send_welcome_email\",\n        user_id=user_id,\n        email=email,\n        timeout=30,\n        retries=2,\n        key=f\"welcome-{user_id}\",  # deduplicate: only one welcome email per user\n    )\n\n\nasync def start_export(export_id: int) -> None:\n    await queue.enqueue(\n        \"process_export\",\n        export_id=export_id,\n        timeout=600,\n        heartbeat=120,  # update heartbeat every 2 minutes\n        key=f\"export-{export_id}\",\n    )\n\n\nif __name__ == \"__main__\":\n    asyncio.run(worker.start())\n```\n\n</example>\n\n---\n\n## References Index\n\nFor detailed guides and patterns, refer to the following documents in `references/`:\n\n- **[Advanced Patterns](references/patterns.md)** -- Heartbeat management, dead letter handling, job chaining, queue priorities, worker lifecycle hooks, Postgres backend.\n\n---\n\n## Official References\n\n- <https://github.com/tobymao/saq>\n- <https://saq-py.readthedocs.io/en/latest/>\n- <https://pypi.org/project/saq/>\n\n## Cross-References\n\n- For Litestar integration (SAQPlugin, DI, web UI, CLI): see `flow:litestar` → litestar-saq section.\n\n## Shared Styleguide Baseline\n\n- Use shared styleguides for generic language/framework rules to reduce duplication in this skill.\n- [General Principles](https://github.com/cofin/flow/blob/main/templates/styleguides/general.md)\n- [Python](https://github.com/cofin/flow/blob/main/templates/styleguides/languages/python.md)\n- Keep this skill focused on tool-specific workflows, edge cases, and integration details.","tags":["saq","flow","cofin","agent-skills","ai-agents","beads","claude-code","codex","cursor","developer-tools","gemini-cli","opencode"],"capabilities":["skill","source-cofin","skill-saq","topic-agent-skills","topic-ai-agents","topic-beads","topic-claude-code","topic-codex","topic-cursor","topic-developer-tools","topic-gemini-cli","topic-opencode","topic-plugin","topic-slash-commands","topic-spec-driven-development"],"categories":["flow"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/cofin/flow/saq","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add cofin/flow","source_repo":"https://github.com/cofin/flow","install_from":"skills.sh"}},"qualityScore":"0.455","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 11 github stars · SKILL.md body (10,321 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:07:39.242Z","embedding":null,"createdAt":"2026-04-23T13:04:01.404Z","updatedAt":"2026-05-18T19:07:39.242Z","lastSeenAt":"2026-05-18T19:07:39.242Z","tsv":"'/15':285 '/5':1159 '/cofin/flow/blob/main/templates/styleguides/general.md)':1339 '/cofin/flow/blob/main/templates/styleguides/languages/python.md)':1343 '/en/latest/':1297 '/project/saq/':1300 '/send':1057 '/tobymao/saq':1294 '0':271,359,392,405 '1':289,455 '1/3':706 '10':314,810,1177 '1000':1123 '120':1243 '15':276 '2':491,1211,1247 '3':243,528 '30':241,901,1161,1209 '300':273 '3600':245 '4':562 '5':595 '6':625 '60':287 '600':368,1241 '604':74 'abort':419 'abrupt':769 'access':559 'aclos':1009 'across':516 'activ':416,693,777 'ad':443 'address':174,1048 'advanc':1273 'alreadi':388 'alway':107,354,661,742 'annot':87 'api.email-provider.com':1056 'api.email-provider.com/send':1055 'app':518,522 'applic':628,1184 'appropri':806 'arg':159,470,479,740,870,1033,1082 'argument':101,753 'async':12,24,30,37,53,67,98,139,460,861,971,985,996,1012,1067,1124,1186,1224 'async-n':23,52 'asyncio':42,336,953 'asyncio.run':337,616,1257 'attach':549,914,981 'attempt':363 'await':190,201,217,230,765,1006,1053,1143,1197,1232 'backend':47,122,128,820,1289 'background':10,157,940 'base':63 'baselin':1321 'belong':590 'block':334,673 'bodi':149,179,181,194,209,225,238 'broker':58 'built':40 'call':630,764 'case':1354 'chain':1282 'check':279,282,312,948,1126,1134,1152,1155,1175 'checkpoint':851 'class':62 'class-bas':61 'clean':540,1002 'cli':1311 'client':547,849,907,987 'code':69,629,855,1185 'come':759 'complet':374,417 'concurr':313,609,807,1176 'configur':19,492 'connect':821 'consid':819 'contain':164,429,526 'content':182 'context':109,162,420,747,833,983,1036,1085 'cpu/memory-intensive':813 'creat':495,596,1190 'creation':115 'cron':270,284,307,574,582,607,722,925,946,1158,1172 'cronjob':16,250,256,266,280,564,570,586,711,724,890,917,957,1150,1153 'crontab':584 'cross':1302 'cross-refer':1301 'ctx':111,143,160,184,423,430,435,449,451,464,552,743,864,916,974,993,999,1007,1017,1034,1051,1071,1083,1113,1129,1140 'current':438 'custom':168 'data':1079 'db':450,544,845,905 'dead':1278 'dedupl':248,378,650,782,931,950,1217 'def':99,140,461,862,972,997,1013,1068,1125,1187,1225 'default':344,665,808 'defin':456,529,878 'definit':137,891,1011 'delay':409 'deliv':853 'descript':345 'design':48 'detail':1262,1357 'di':525,1308 'dict':110,144,163,421,424,465,748,834,865,975,1000,1018,1037,1072,1076,1086,1097,1130 'docstr':92 'document':1270 'dsn':506 'duplic':1331 'e.g':448,651,793 'edg':1353 'edit':4 'email':142,154,172,176,180,204,220,233,302,941,990,1016,1022,1028,1045,1047,1060,1169,1194,1201,1206,1207,1221 'email-user@example.com':247 'enqueu':14,195,211,626,790,1182 'everi':103,262,275,427,857,1246 'exampl':938,984 'expect':708 'explicit':573 'export':1070,1073,1080,1087,1092,1099,1118,1120,1171,1227,1228,1235,1236,1238,1251,1252 'express':575 'extern':581,721,924 'f':1146,1213,1250 'fail':418 'failur':365 'fire':198,634 'fire-and-forget':633 'first':100,468,739,751,868 'flow':1313 'focus':484,1347 'follow':1269 'forev':677 'forget':200,636 'function':68,95,105,267,281,300,458,462,483,606,859,1154,1166 'futur':85 'general':1335 'generat':268,305 'generic':1326 'github.com':1293,1338,1342 'github.com/cofin/flow/blob/main/templates/styleguides/general.md)':1337 'github.com/cofin/flow/blob/main/templates/styleguides/languages/python.md)':1341 'github.com/tobymao/saq':1292 'googl':90 'google-styl':89 'grace':762,936 'guardrail':660 'guid':1263 'handl':761,935,1280 'health':278,283,311,947,1128,1133,1151,1157,1174 'heartbeat':390,395,679,686,703,892,1105,1242,1245,1276 'hello':208,224,237 'higher':815 'hook':189,317,320,325,330,447,531,536,613,912,945,970,1287 'hour':263,264,309 'http':546,848,906,986,994,1008,1049,1052 'http.post':1054 'httpx':992 'httpx.asyncclient':995,1050 'hung':670 'i/o-bound':817 'id':1020,1039,1040,1064,1066,1074,1088,1089,1119,1121,1192,1203,1205,1216,1229,1237,1239,1253 'import':9,86,119,255,295,335,952,956,991 'index':1260 'info':1142,1149 'initi':538,909,977 'inject':186,745 'instanc':434,510,571 'instanti':600 'int':347,358,367,391,404,1021,1075,1193,1230 'integr':619,1306,1356 'job':11,15,166,196,228,308,339,351,383,402,410,412,436,439,608,655,684,694,734,774,787,888,896,930,1081,1112,1114,1173,1281 'json':1058 'keep':481,1344 'key':170,246,249,375,379,386,442,646,780,801,926,1212,1249 'keyword':477,755,876 'keyword-on':476,754,875 'kill':771 'kubernet':585 'language/framework':1327 'leav':773 'letter':1279 'level':521,964 'lifecycl':21,413,530,612,735,969,1286 'lightweight':36 'like':844 'limit':610 'line':178 'litestar':1305,1314,1316 'litestar-saq':1315 'localhost':126,968 'localhost/db':135 'log':1135 'logic':786,1116 'long':400,682,692,886,1103 'long-act':691 'long-run':399,681,885,1102 'longer':899 'lower':811 'mailer':183,185,452,548 'mailer.send':191 'main':1256 'manag':623,726,1277 'mark':689,1109 'may':688 'metadata':1101 'minut':277,1248 'modul':520,963 'module-level':519,962 'multipl':791 'must':96 'mutabl':827 'name':1255 'nativ':25,54 'natur':649 'need':641,933 'never':82 'new':1031,1043 'none':78,151,348,377,976,1001,1024,1131,1196,1231 'number':360 'object':440 'observ':738 'offici':1290 'one':488,1219 'option':80,229,340,342 'order':304 'param':758 'paramet':474,647,873 'particip':731 'pass':134,425 'pattern':55,1265,1274 'pep':73 'per':653,795,837,1222 'per-us':652,794 'per-work':836 'plain':66 'pool':545,822,846 'popul':835 'posit':469,752,869 'postgr':46,127,505,1288 'postgresql':132 'prevent':803,1106 'principl':1336 'print':1145 'prioriti':1284 'process':27,59,303,322,324,327,329,622,770,1069,1077,1095,1115,1170,1234 'pypi.org':1299 'pypi.org/project/saq/':1298 'python':116,138,197,252,292,951,1340 'q':1138 'q.info':1144 'queu':415 'queue':7,18,31,39,114,120,123,129,165,299,431,433,494,497,509,593,604,700,958,961,965,1127,1136,1139,1141,1147,1156,1165,1283 'queue.apply':218,638 'queue.enqueue':202,231,631,1198,1233 'queue.from':124,130,499,966 'queued/active':389 'quick':112 're':699 're-queu':698 'recipi':145,171,173,192,205,221,234,1046 'record':1093 'recur':567 'redi':44,121,503 'reduc':1330 'refer':113,341,1259,1266,1272,1291,1303 'references/patterns.md':1275 'report':265,269,306,310 'resourc':543,550,843,904,979,1005 'result':215,216,372,643,1100 'retain':371 'retri':242,288,357,362,736,1210 'return':1096,1117 'rough':705 'row':1122 'rule':71,1328 'run':257,274,331,401,598,614,683,887,898,1104 'runtim':709 'saq':1,5,8,28,33,118,161,254,294,687,718,744,854,955,1035,1084,1107,1317 'saq-py.readthedocs.io':1296 'saq-py.readthedocs.io/en/latest/':1295 'saqplugin':1307 'schedul':17,251,403,563,713,717,1132 'scheduled/recurring':921 'second':349,369,393,902 'section':1318 'see':1312 'send':141,152,203,219,232,301,989,1014,1025,1167,1199 'sender':942 'separ':57 'set':355,662,702,798,805,882,894 'setup':291 'share':515,542,826,842,903,960,978,1004,1319,1323 'shutdown':318,319,535,763,937,998,1180,1181 'sigterm/sigint':768 'simpl':29 'simplic':50 'size':823 'skill':32,1334,1346 'skill-saq' 'skip':380 'slot':676 'source-cofin' 'specif':1351 'stabl':800 'stack':659,804 'start':411,1226 'startup':169,188,315,316,446,533,554,840,911,944,973,1178,1179 'stat':1137,1148 'state':523,778,828 'step':454,490,527,561,594,624 'store':507 'str':146,148,150,376,1023,1195 'strand':775 'stuck':696,1111 'style':70,91 'styleguid':1320,1324 'subject':147,175,177,193,207,223,236 'support':43 'sync':797 'task':6,26,38,64,94,104,136,158,428,457,473,482,486,557,605,671,719,757,814,818,830,858,872,939,1010 'templat':1061 'text':414 'thing':489 'time':352,792 'timeout':240,272,286,346,577,663,668,737,880,1160,1208,1240 'timestamp':407 'tool':583,723,1350 'tool-specif':1349 'top':260 'topic-agent-skills' 'topic-ai-agents' 'topic-beads' 'topic-claude-code' 'topic-codex' 'topic-cursor' 'topic-developer-tools' 'topic-gemini-cli' 'topic-opencode' 'topic-plugin' 'topic-slash-commands' 'topic-spec-driven-development' 'ttl':244,366 'type':343 'ui':1310 'union':76 'unix':406 'updat':396,1244 'url':125,131,500,967 'use':2,72,83,88,397,498,580,644,678,710,779,831,919,928,1322 'user':133,654,796,1019,1032,1038,1044,1063,1065,1189,1191,1202,1204,1215,1223 'user@example.com':206,222,235 'valid':850 'verifi':856 'via':187 'wait':213 'web':1309 'welcom':1015,1027,1062,1168,1200,1214,1220 'without':685 'work':568,588,714,922 'worker':13,20,290,296,297,298,333,599,601,675,729,838,934,959,1162,1163,1164,1285 'worker.start':338,617,1258 'worker.stop':766 'workflow':453,1352 'world':210,226,239 'wrap':565 'write':459,532","prices":[{"id":"e3495259-7e6a-4f0a-8e1a-9253c871988c","listingId":"6a1dcf63-e592-4106-b066-dc7a268687e6","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"cofin","category":"flow","install_from":"skills.sh"},"createdAt":"2026-04-23T13:04:01.404Z"}],"sources":[{"listingId":"6a1dcf63-e592-4106-b066-dc7a268687e6","source":"github","sourceId":"cofin/flow/saq","sourceUrl":"https://github.com/cofin/flow/tree/main/skills/saq","isPrimary":false,"firstSeenAt":"2026-04-23T13:04:01.404Z","lastSeenAt":"2026-05-18T19:07:39.242Z"}],"details":{"listingId":"6a1dcf63-e592-4106-b066-dc7a268687e6","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"cofin","slug":"saq","github":{"repo":"cofin/flow","stars":11,"topics":["agent-skills","ai-agents","beads","claude-code","codex","context-driven-development","cursor","developer-tools","gemini-cli","opencode","plugin","slash-commands","spec-driven-development","subagents","tdd","workflow"],"license":"apache-2.0","html_url":"https://github.com/cofin/flow","pushed_at":"2026-04-27T19:07:26Z","description":"Context-Driven Development toolkit for AI agents — spec-first planning, TDD workflow, and Beads integration.","skill_md_sha":"2845b7b78423b9f98d9f9932783b7a3170724c44","skill_md_path":"skills/saq/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/cofin/flow/tree/main/skills/saq"},"layout":"multi","source":"github","category":"flow","frontmatter":{"name":"saq","description":"Use when editing SAQ task queues, saq imports, background jobs, async workers, enqueueing jobs, CronJob schedules, queue configuration, worker lifecycle, or async-native task processing."},"skills_sh_url":"https://skills.sh/cofin/flow/saq"},"updatedAt":"2026-05-18T19:07:39.242Z"}}