{"id":"408249b5-4c0c-4416-a3b6-c1faed0993d5","shortId":"QzxxYx","kind":"skill","title":"litestar-saq","tagline":"Auto-activate for litestar_saq imports, SAQPlugin, SAQConfig, QueueConfig, TaskQueues, CronJob, litestar workers run, background jobs, scheduled jobs, or SAQ web UI. Use when adding first-party SAQ background work to Litestar. Not for Celery, RQ, Dramatiq, raw SAQ outside Litesta","description":"# litestar-saq\n\n`litestar-saq` is the first-party plugin that integrates [SAQ (Simple Async Queue)](https://github.com/tobymao/saq) with Litestar. It provides:\n\n- `SAQPlugin` — registers queues, workers, lifespan management, and DI for `TaskQueues`\n- `SAQConfig` / `QueueConfig` — declarative queue + worker configuration\n- `litestar workers run` — CLI to start workers in-process or as a separate process\n- Optional web UI mounted under the Litestar app\n- DI injection of `TaskQueues` into route handlers for ergonomic enqueueing\n\n## Code Style Rules\n\n- Use PEP 604 unions: `T | None`, never `Optional[T]`\n- Consumer Litestar app modules MAY use `from __future__ import annotations` — canonical Litestar apps do.\n- Async all I/O — task bodies and enqueue calls are `async def`.\n- First positional arg of every task is `ctx: dict` (the SAQ context dict).\n- Task params after `*` are keyword-only.\n\n## Quick Reference\n\n### Plugin Setup (canonical pattern)\n\nThe canonical pattern from [litestar-fullstack](https://github.com/litestar-org/litestar-fullstack) (`src/py/app/server/plugins.py`) uses lazy initialization and `use_server_lifespan=True` so workers share the app's lifespan with the web process:\n\n```python\n# Branch A — SAQ with Redis as the broker (pick when Redis is already in-stack\n# for cache / sessions, or when you want the SAQ web UI + multi-queue fanout).\nfrom litestar_saq import SAQConfig, SAQPlugin, QueueConfig, CronJob\n\nfrom app.lib.settings import get_settings\n\n\ndef create_saq_plugin() -> SAQPlugin:\n    settings = get_settings()\n    return SAQPlugin(\n        config=SAQConfig(\n            dsn=settings.redis.url,              # redis://... — Redis broker\n            use_server_lifespan=True,            # workers run inside web process by default\n            web_enabled=settings.saq.web_enabled,\n            queue_configs=[\n                QueueConfig(\n                    name=\"default\",\n                    tasks=[\"app.domain.system.tasks.send_email\"],\n                    scheduled_tasks=[\n                        CronJob(\n                            function=\"app.domain.system.tasks.cleanup_sessions\",\n                            cron=\"*/15 * * * *\",\n                            timeout=120,\n                        ),\n                    ],\n                ),\n            ],\n        ),\n    )\n\n\nsaq_plugin = create_saq_plugin()\n```\n\n```python\n# Branch B — SAQ with PostgreSQL as the broker (pick when the project is\n# PG-only, you want one less piece of infra, or throughput is moderate).\ndef create_saq_plugin_pg() -> SAQPlugin:\n    settings = get_settings()\n    return SAQPlugin(\n        config=SAQConfig(\n            dsn=settings.database.url,           # postgresql+asyncpg://... — PG broker\n            use_server_lifespan=True,\n            web_enabled=settings.saq.web_enabled,\n            queue_configs=[\n                QueueConfig(\n                    name=\"default\",\n                    tasks=[\"app.domain.system.tasks.send_email\"],\n                ),\n            ],\n        ),\n    )\n```\n\n**Pick Branch A (SAQ + Redis) when:** Redis is already in-stack (cache, sessions, Channels), you need multi-queue fanout across many workers, want the SAQ web UI and dead-letter dashboards, or have high throughput (>1k jobs/s per queue).\n\n**Pick Branch B (SAQ + PostgreSQL) when:** PG-only deployment (Cloud SQL, AlloyDB, self-hosted single DB), avoiding extra infra, moderate throughput (<1k jobs/s), or you want jobs and business data in the same transactional boundary.\n\n**Pick Branch C (custom PG-native, no SAQ) when:** you need `pg_notify` wake-ups with zero polling lag, `FOR UPDATE SKIP LOCKED` atomic task claim, or multi-target execution routing (`local` / `cloudrun` / `immediate`) — and you're willing to own a thin `TaskService + WorkerPlugin` pair. See below.\n\n**Anti-pattern:** hard-coding `dsn=settings.redis.url` in a PG-only project just because Redis is the \"default\" example. Match the broker to the stack.\n\n### Branch C — Custom PostgreSQL-native queue (no SAQ)\n\nSome projects reject SAQ entirely in favor of a thin `TaskService + WorkerPlugin` pair directly over PostgreSQL. This wins when you want `FOR UPDATE SKIP LOCKED` for atomic task claiming, `pg_notify` wake-ups (no polling lag), and multi-target execution routing (`local` / `cloudrun` / `immediate`) with zero extra dependencies beyond your existing Postgres connection.\n\nIn this pattern, the `WorkerPlugin` wires task discovery and the in-process worker into the Litestar app lifecycle; the `@task` decorator registers callables and optional cron schedules; `TaskService.create_task` persists tasks to a `job` table.\n\nSee [references/postgresql-native.md](references/postgresql-native.md) for the full pattern.\n\n```python\n# NOTE: do NOT use `from __future__ import annotations` in modules that define\n# @task-decorated functions — the decorator inspects signatures at registration time.\n\nfrom app.lib.worker.jobs import task\n\n\n@task(cron=\"0 2 * * *\", timeout=120)\nasync def nightly_cleanup() -> None:\n    \"\"\"Purge soft-deleted records every night at 02:00 UTC.\"\"\"\n    ...\n\n\n@task(priority=5, retries=1, timeout=300, execution_target=\"cloudrun\")\nasync def generate_report(*, report_id: int) -> None:\n    \"\"\"Export report — runs on Cloud Run for isolation.\"\"\"\n    ...\n\n\n# Enqueue imperatively (from a handler or service):\nawait generate_report.enqueue(execution_target=\"cloudrun\", report_id=42)\n```\n\n### Wire into Litestar\n\n```python\nfrom litestar import Litestar\nfrom app.server.plugins import saq_plugin\n\napp = Litestar(\n    route_handlers=[...],\n    plugins=[saq_plugin],\n)\n```\n\n### Define a Task\n\n```python\n# app/domain/system/tasks.py\nasync def send_email(ctx: dict, *, recipient: str, subject: str, body: str) -> None:\n    \"\"\"Send an email as a background job.\n\n    Args:\n        ctx: SAQ context dict (queue, job, app-state).\n        recipient: To address.\n        subject: Email subject.\n        body: Email body.\n    \"\"\"\n    email_service = ctx[\"state\"][\"email_service\"]\n    await email_service.send(recipient, subject, body)\n```\n\n### Enqueue from a Handler (DI of TaskQueues)\n\n```python\nfrom litestar import Controller, post\nfrom litestar_saq import TaskQueues\n\n\nclass NotificationController(Controller):\n    path = \"/api/notifications\"\n\n    @post(\"/\")\n    async def queue_notification(\n        self,\n        data: NotificationCreate,\n        task_queues: TaskQueues,\n    ) -> dict[str, str]:\n        queue = task_queues.get(\"default\")\n        await queue.enqueue(\n            \"send_email\",\n            recipient=data.email,\n            subject=data.subject,\n            body=data.body,\n            timeout=30,\n            retries=2,\n            key=f\"notify-{data.email}\",\n        )\n        return {\"status\": \"queued\"}\n```\n\n### CLI\n\n```bash\n# Run workers (uses the same Litestar app)\nlitestar --app app:app workers run\n\n# Run workers in a separate process (production)\nlitestar --app app:app workers run --process\n\n# Inspect queues\nlitestar --app app:app workers status\n```\n\n### Web UI\n\nWhen `web_enabled=True`, the SAQ web UI is mounted under the Litestar app for queue introspection and job retry.\n\n### Job Options\n\n| Option | Default | Use |\n| --- | --- | --- |\n| `timeout` | `None` | **Always set** — bound how long a job can run |\n| `retries` | `0` | Retry count on exception |\n| `ttl` | `600` | Seconds to retain result after completion |\n| `key` | `None` | Deduplication key — skip if already queued |\n| `heartbeat` | `0` | Heartbeat interval for long-running jobs |\n| `scheduled` | `0` | Unix timestamp to delay start |\n\n<workflow>\n\n## Workflow\n\n### Step 1: Install\n\n```bash\npip install litestar-saq\n```\n\n### Step 2: Define Queues\n\nBuild `QueueConfig` instances for each logical queue (`\"default\"`, `\"emails\"`, `\"reports\"`). Reference task functions by dotted path; the plugin imports them at startup.\n\n### Step 3: Configure Plugin\n\nWrap `QueueConfig`s in `SAQConfig` with the broker DSN. Pick Redis (`redis://...`) when Redis is already in the stack; pick PostgreSQL (`postgresql+asyncpg://...`) when the project is PG-only. Both brokers are fully supported — see Plugin Setup above for both patterns. Set `use_server_lifespan=True` so workers run inside the web process by default. Toggle `web_enabled` for the introspection UI.\n\n### Step 4: Define Tasks\n\nPlace task functions in `app/domain/<domain>/tasks.py`. First arg `ctx: dict`, rest keyword-only. Pull shared resources (DB, HTTP client, email service) from `ctx[\"state\"]`.\n\n### Step 5: Schedule Cron Work\n\nAdd `CronJob` entries to `QueueConfig.scheduled_tasks` for recurring work. Always set `timeout`. Do not use external cron tools for work that belongs in the queue.\n\n### Step 6: Enqueue from Handlers\n\nInject `TaskQueues` into route handlers. Use `task_queues.get(\"name\")` then `await queue.enqueue(\"task_name\", ...)`. Use `key=` for deduplication.\n\n### Step 7: Publish to Channels (optional)\n\nFor real-time updates after a job completes, publish to Litestar Channels from inside the task. See `../litestar-realtime/references/websockets.md`.\n\n### Step 8: Run\n\nFor dev: `litestar run` (workers + web in one process via `use_server_lifespan=True`).\nFor production: `litestar workers run --process` separately from `litestar run`.\n\n</workflow>\n\n<guardrails>\n\n## Guardrails\n\n- **Use `litestar-saq`, not raw SAQ, in Litestar apps** — the plugin handles DI, lifespan, CLI, and the web UI. Raw SAQ misses all of that.\n- **Always set `timeout`** on tasks and CronJobs — default is no timeout; a hung task pins a worker slot forever.\n- **Use `heartbeat`** for jobs that run longer than ~30s, otherwise SAQ may mark them stuck and re-queue.\n- **Inject `TaskQueues` via DI** — don't import a global queue inside handlers. The plugin owns the queue lifecycle.\n- **Use `CronJob` for scheduled work** — not external cron. CronJobs participate in retries, timeouts, and observability.\n- **Use `key=` for deduplication** — same logical job (per-user sync, per-resource refresh) should not stack.\n- **`use_server_lifespan=True`** for dev and small-to-mid apps (workers inside the web process). Switch to `--process` for high-throughput production.\n- **Publish to Litestar Channels from tasks** when the job result must update connected websocket clients. See `../litestar-realtime/references/websockets.md`.\n- **Pull shared resources from `ctx[\"state\"]`**, not module-level globals — keeps tests deterministic and supports per-worker init.\n- **Reach for a custom PG-native queue instead of SAQ when** you need `pg_notify` wake-ups, `FOR UPDATE SKIP LOCKED` atomic claim, or execution-target routing across Cloud Run / local. For every other PG-only case, SAQ+PG is the simpler default. See [references/postgresql-native.md](references/postgresql-native.md).\n\n</guardrails>\n\n<validation>\n\n### Validation Checkpoint\n\nBefore delivering Litestar + SAQ code, verify:\n\n- [ ] `SAQPlugin` is in `app.plugins`\n- [ ] `SAQConfig.use_server_lifespan` is set explicitly\n- [ ] Each `QueueConfig` lists tasks by dotted path; the imports resolve\n- [ ] All tasks have `ctx: dict` as the first positional arg, keyword-only params after `*`\n- [ ] Every task has `timeout` set\n- [ ] Long-running jobs (>30s) have `heartbeat` set\n- [ ] CronJobs have `timeout` and a sensible `cron` expression\n- [ ] Handlers enqueue via injected `TaskQueues`, not module globals\n- [ ] Job dedup uses `key=` where applicable\n- [ ] Production deploys run workers as a separate process (`litestar workers run --process`)\n\n</validation>\n\n<example>\n\n## Example\n\n**Task:** A Litestar app with a default queue, an email task, a cleanup CronJob, and a handler that enqueues notifications. This example uses Redis as the SAQ broker; swap `dsn=settings.redis.url` for `dsn=settings.database.url` if the project is PG-only — see Quick Reference above for both patterns.\n\n```python\n# app/server/plugins.py\nfrom litestar_saq import SAQConfig, SAQPlugin, QueueConfig, CronJob\n\nfrom app.lib.settings import get_settings\n\n\ndef create_saq_plugin() -> SAQPlugin:\n    settings = get_settings()\n    return SAQPlugin(\n        config=SAQConfig(\n            dsn=settings.redis.url,          # Redis broker — swap for settings.database.url in PG-only stacks\n            use_server_lifespan=True,\n            web_enabled=settings.saq.web_enabled,\n            queue_configs=[\n                QueueConfig(\n                    name=\"default\",\n                    tasks=[\n                        \"app.domain.system.tasks.send_email\",\n                        \"app.domain.system.tasks.cleanup_sessions\",\n                    ],\n                    scheduled_tasks=[\n                        CronJob(\n                            function=\"app.domain.system.tasks.cleanup_sessions\",\n                            cron=\"*/15 * * * *\",\n                            timeout=120,\n                        ),\n                    ],\n                ),\n            ],\n        ),\n    )\n\n\nsaq_plugin = create_saq_plugin()\n```\n\n```python\n# app/domain/system/tasks.py\nasync def send_email(ctx: dict, *, recipient: str, subject: str, body: str) -> None:\n    \"\"\"Send an email as a background job.\"\"\"\n    email = ctx[\"state\"][\"email_service\"]\n    await email.send(recipient, subject, body)\n\n\nasync def cleanup_sessions(ctx: dict) -> None:\n    \"\"\"Purge expired sessions every 15 minutes.\"\"\"\n    db = ctx[\"state\"][\"db\"]\n    await db.execute(\"DELETE FROM session WHERE expires_at < now()\")\n```\n\n```python\n# app/domain/notifications/controllers.py\nfrom litestar import Controller, post\nfrom litestar_saq import TaskQueues\n\nfrom app.domain.notifications.schemas import NotificationCreate\n\n\nclass NotificationController(Controller):\n    path = \"/api/notifications\"\n    tags = [\"Notifications\"]\n\n    @post(\"/\")\n    async def queue_notification(\n        self,\n        data: NotificationCreate,\n        task_queues: TaskQueues,\n    ) -> dict[str, str]:\n        queue = task_queues.get(\"default\")\n        await queue.enqueue(\n            \"send_email\",\n            recipient=data.email,\n            subject=data.subject,\n            body=data.body,\n            timeout=30,\n            retries=2,\n            key=f\"notify-{data.email}\",\n        )\n        return {\"status\": \"queued\"}\n```\n\n```python\n# app.py\nfrom litestar import Litestar\n\nfrom app.domain.notifications.controllers import NotificationController\nfrom app.server.plugins import saq_plugin\n\n\napp = Litestar(\n    route_handlers=[NotificationController],\n    plugins=[saq_plugin],\n)\n```\n\n```bash\n# Dev: workers + web in one process\nlitestar --app app:app run\n\n# Prod: separate worker process\nlitestar --app app:app workers run --process\n```\n\n</example>\n\n---\n\n## References Index\n\n- **[Advanced Patterns](references/patterns.md)** — Heartbeat tuning, dead-letter handling, job chaining, queue priorities, worker lifecycle hooks, Postgres backend.\n- **[PostgreSQL-Native Queue (no SAQ)](references/postgresql-native.md)** — TaskService + WorkerPlugin pattern: FOR UPDATE SKIP LOCKED claim, pg_notify wake-ups, @task decorator + ScheduleConfig cron registry, execution_target routing (local / cloudrun / immediate).\n\n## Cross-References\n\n- **[litestar](../litestar/SKILL.md)** — Litestar app initialization, plugins, and lifespan.\n- **[litestar websockets reference](../litestar-realtime/references/websockets.md)** — Publish from a SAQ task to Litestar Channels for real-time UI updates.\n\n## Official References\n\n- <https://github.com/litestar-org/litestar-saq>\n- <https://github.com/tobymao/saq>\n- <https://saq-py.readthedocs.io/en/latest/>\n\n## Shared Styleguide Baseline\n\n- Use shared styleguides for generic language/framework rules to reduce duplication in this skill.\n- [General Principles](../litestar-styleguide/references/general.md)\n- [Python](../litestar-styleguide/references/python.md)\n- [Litestar](../litestar-styleguide/references/litestar.md)\n- Keep this skill focused on tool-specific workflows, edge cases, and integration details.","tags":["litestar","saq","skills","litestar-org","advanced-alchemy","agent-skills","agentskills","ai-agents","claude-code-plugin","claude-code-skills","gemini-cli-extension","htmx"],"capabilities":["skill","source-litestar-org","skill-litestar-saq","topic-advanced-alchemy","topic-agent-skills","topic-agentskills","topic-ai-agents","topic-claude-code-plugin","topic-claude-code-skills","topic-gemini-cli-extension","topic-htmx","topic-inertia","topic-litestar","topic-mcp","topic-python"],"categories":["litestar-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/litestar-org/litestar-skills/litestar-saq","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add litestar-org/litestar-skills","source_repo":"https://github.com/litestar-org/litestar-skills","install_from":"skills.sh"}},"qualityScore":"0.453","qualityRationale":"deterministic score 0.45 from registry signals: · indexed on github topic:agent-skills · 7 github stars · SKILL.md body (15,881 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:13:54.668Z","embedding":null,"createdAt":"2026-05-18T13:20:58.566Z","updatedAt":"2026-05-18T19:13:54.668Z","lastSeenAt":"2026-05-18T19:13:54.668Z","tsv":"'/15':305,1643 '/api/notifications':826,1729 '/en/latest/':1906 '/litestar-org/litestar-fullstack)':193 '/litestar-org/litestar-saq':1900 '/litestar-realtime/references/websockets.md':1184,1369,1881 '/litestar-styleguide/references/general.md':1925 '/litestar-styleguide/references/litestar.md':1929 '/litestar-styleguide/references/python.md':1927 '/litestar/skill.md':1871 '/tasks.py':1088 '/tobymao/saq':1903 '/tobymao/saq)':67 '0':668,941,963,972 '00':686 '02':685 '1':692,980 '120':307,671,1645 '15':1694 '1k':413,440 '2':669,857,989,1762 '3':1015 '30':855,1760 '300':694 '30s':1266,1492 '4':1080 '42':728 '5':690,1109 '6':1139 '600':947 '604':126 '7':1161 '8':1186 'across':396,1420 'activ':6 'ad':29 'add':1113 'address':786 'advanc':1818 'alloydb':429 'alreadi':227,383,960,1032 'alway':931,1122,1239 'annot':142,646 'anti':505 'anti-pattern':504 'app':110,135,145,207,612,742,782,873,875,876,877,888,889,890,897,898,899,917,1222,1339,1534,1785,1801,1802,1803,1810,1811,1812,1873 'app-stat':781 'app.domain.notifications.controllers':1777 'app.domain.notifications.schemas':1722 'app.domain.system.tasks.cleanup':302,1634,1640 'app.domain.system.tasks.send':296,373,1632 'app.lib.settings':255,1590 'app.lib.worker.jobs':663 'app.plugins':1451 'app.py':1771 'app.server.plugins':738,1781 'app/domain':1087 'app/domain/notifications/controllers.py':1710 'app/domain/system/tasks.py':753,1652 'app/server/plugins.py':1580 'applic':1517 'arg':160,774,1090,1477 'async':63,147,156,672,698,754,828,1653,1683,1733 'atom':479,566,1413 'auto':5 'auto-activ':4 'avoid':435 'await':721,799,844,1152,1678,1700,1749 'b':315,419 'backend':1835 'background':19,34,772,1671 'baselin':1909 'bash':866,982,1793 'belong':1134 'beyond':590 'bodi':151,764,790,792,803,852,1663,1682,1757 'bound':933 'boundari':453 'branch':215,314,376,418,455,531 'broker':222,274,321,358,527,1025,1047,1558,1609 'build':992 'busi':447 'c':456,532 'cach':232,387 'call':154 'callabl':618 'canon':143,182,185 'case':1430,1940 'celeri':40 'chain':1828 'channel':389,1164,1178,1356,1889 'checkpoint':1441 'claim':481,568,1414,1850 'class':822,1725 'cleanup':675,1543,1685 'cli':91,865,1228 'client':1102,1367 'cloud':427,710,1421 'cloudrun':489,584,697,725,1865 'code':121,509,1446 'complet':953,1174 'config':269,291,352,368,1604,1627 'configur':87,1016 'connect':594,1365 'consum':133 'context':169,777 'control':815,824,1714,1727 'count':943 'creat':260,310,342,1595,1648 'cron':304,621,667,1111,1129,1302,1502,1642,1859 'cronjob':15,253,300,1114,1245,1296,1303,1496,1544,1588,1638 'cross':1868 'cross-refer':1867 'ctx':165,758,775,795,1091,1106,1374,1471,1657,1674,1687,1697 'custom':457,533,1393 'dashboard':408 'data':448,833,1738 'data.body':853,1758 'data.email':849,861,1754,1766 'data.subject':851,1756 'db':434,1100,1696,1699 'db.execute':1701 'dead':406,1824 'dead-lett':405,1823 'declar':84 'decor':616,653,656,1857 'dedup':1513 'dedupl':956,1159,1313 'def':157,259,341,673,699,755,829,1594,1654,1684,1734 'default':285,294,371,523,843,927,999,1071,1246,1436,1537,1630,1748 'defin':650,749,990,1081 'delay':976 'delet':680,1702 'deliv':1443 'depend':589 'deploy':426,1519 'detail':1943 'determinist':1383 'dev':1189,1333,1794 'di':79,111,808,1226,1280 'dict':166,170,759,778,838,1092,1472,1658,1688,1743 'direct':553 'discoveri':602 'dot':1006,1463 'dramatiq':42 'dsn':271,354,510,1026,1560,1563,1606 'duplic':1919 'edg':1939 'email':297,374,757,769,788,791,793,797,847,1000,1103,1540,1633,1656,1668,1673,1676,1752 'email.send':1679 'email_service.send':800 'enabl':287,289,364,366,906,1074,1623,1625 'enqueu':120,153,714,804,1140,1505,1549 'entir':544 'entri':1115 'ergonom':119 'everi':162,682,1425,1483,1693 'exampl':524,1530,1552 'except':945 'execut':486,581,695,723,1417,1861 'execution-target':1416 'exist':592 'expir':1691,1706 'explicit':1457 'export':706 'express':1503 'extern':1128,1301 'extra':436,588 'f':859,1764 'fanout':245,395 'favor':546 'first':31,56,158,1089,1475 'first-parti':30,55 'focus':1933 'forev':1257 'full':636 'fulli':1049 'fullstack':190 'function':301,654,1004,1085,1639 'futur':140,644 'general':1923 'generat':700 'generate_report.enqueue':722 'generic':1914 'get':257,265,348,1592,1600 'github.com':66,192,1899,1902 'github.com/litestar-org/litestar-fullstack)':191 'github.com/litestar-org/litestar-saq':1898 'github.com/tobymao/saq':1901 'github.com/tobymao/saq)':65 'global':1285,1380,1511 'guardrail':1212 'handl':1225,1826 'handler':117,718,745,807,1142,1147,1288,1504,1547,1788 'hard':508 'hard-cod':507 'heartbeat':962,964,1259,1494,1821 'high':411,1350 'high-throughput':1349 'hook':1833 'host':432 'http':1101 'hung':1251 'i/o':149 'id':703,727 'immedi':490,585,1866 'imper':715 'import':10,141,249,256,645,664,735,739,814,820,1010,1283,1466,1584,1591,1713,1719,1723,1774,1778,1782 'in-process':95,605 'in-stack':228,384 'index':1817 'infra':336,437 'init':1389 'initi':197,1874 'inject':112,1143,1277,1507 'insid':281,1066,1180,1287,1341 'inspect':657,894 'instal':981,984 'instanc':994 'instead':1398 'int':704 'integr':60,1942 'interv':965 'introspect':920,1077 'isol':713 'job':20,22,445,629,773,780,922,924,937,970,1173,1261,1316,1361,1491,1512,1672,1827 'jobs/s':414,441 'keep':1381,1930 'key':858,954,957,1157,1311,1515,1763 'keyword':176,1095,1479 'keyword-on':175,1094,1478 'lag':474,576 'language/framework':1915 'lazi':196 'less':333 'letter':407,1825 'level':1379 'lifecycl':613,1294,1832 'lifespan':76,201,209,277,361,1061,1200,1227,1330,1454,1620,1877 'list':1460 'litesta':46 'litestar':2,8,16,37,48,51,69,88,109,134,144,189,247,611,731,734,736,743,813,818,872,874,887,896,916,986,1177,1190,1204,1210,1215,1221,1355,1444,1526,1533,1582,1712,1717,1773,1775,1786,1800,1809,1870,1872,1878,1888,1928 'litestar-fullstack':188 'litestar-saq':1,47,50,985,1214 'local':488,583,1423,1864 'lock':478,564,1412,1849 'logic':997,1315 'long':935,968,1489 'long-run':967,1488 'longer':1264 'manag':77 'mani':397 'mark':1270 'match':525 'may':137,1269 'mid':1338 'minut':1695 'miss':1235 'moder':340,438 'modul':136,648,1378,1510 'module-level':1377 'mount':106,913 'multi':243,393,484,579 'multi-queu':242,392 'multi-target':483,578 'must':1363 'name':293,370,1150,1155,1629 'nativ':460,536,1396,1838 'need':391,465,1403 'never':130 'night':674,683 'none':129,676,705,766,930,955,1665,1689 'note':639 'notif':831,1550,1731,1736 'notifi':467,570,860,1405,1765,1852 'notificationcontrol':823,1726,1779,1789 'notificationcr':834,1724,1739 'observ':1309 'offici':1896 'one':332,1195,1798 'option':103,131,620,925,926,1165 'otherwis':1267 'outsid':45 'own':1291 'pair':501,552 'param':172,1481 'parti':32,57 'particip':1304 'path':825,1007,1464,1728 'pattern':183,186,506,597,637,1057,1578,1819,1845 'pep':125 'per':415,1318,1322,1387 'per-resourc':1321 'per-us':1317 'per-work':1386 'persist':625 'pg':328,345,357,424,459,466,515,569,1044,1395,1404,1428,1432,1570,1615,1851 'pg-nativ':458,1394 'pg-on':327,423,514,1043,1427,1569,1614 'pick':223,322,375,417,454,1027,1036 'piec':334 'pin':1253 'pip':983 'place':1083 'plugin':58,180,262,309,312,344,741,746,748,1009,1017,1052,1224,1290,1597,1647,1650,1784,1790,1792,1875 'poll':473,575 'posit':159,1476 'post':816,827,1715,1732 'postgr':593,1834 'postgresql':318,356,421,535,555,1037,1038,1837 'postgresql-n':534,1836 'principl':1924 'prioriti':689,1830 'process':97,102,213,283,607,885,893,1069,1196,1207,1344,1347,1525,1529,1799,1808,1815 'prod':1805 'product':886,1203,1352,1518 'project':325,517,541,1041,1567 'provid':71 'publish':1162,1175,1353,1882 'pull':1097,1370 'purg':677,1690 'python':214,313,638,732,752,811,1579,1651,1709,1770,1926 'queu':864,961,1769 'queue':64,74,85,244,290,367,394,416,537,779,830,836,841,895,919,991,998,1137,1276,1286,1293,1397,1538,1626,1735,1741,1746,1829,1839 'queue.enqueue':845,1153,1750 'queueconfig':13,83,252,292,369,993,1019,1459,1587,1628 'queueconfig.scheduled':1117 'quick':178,1573 'raw':43,1218,1233 're':493,1275 're-queu':1274 'reach':1390 'real':1168,1892 'real-tim':1167,1891 'recipi':760,784,801,848,1659,1680,1753 'record':681 'recur':1120 'redi':219,225,273,379,381,520,1028,1030,1554,1608 'reduc':1918 'refer':179,1002,1574,1816,1869,1880,1897 'references/patterns.md':1820 'references/postgresql-native.md':632,633,1438,1439,1842 'refresh':1324 'regist':73,617 'registr':660 'registri':1860 'reject':542 'report':701,702,707,726,1001 'resolv':1467 'resourc':1099,1323,1372 'rest':1093 'result':951,1362 'retain':950 'retri':691,856,923,940,942,1306,1761 'return':267,350,862,1602,1767 'rout':116,487,582,744,1146,1419,1787,1863 'rq':41 'rule':123,1916 'run':18,90,280,708,711,867,879,880,892,939,969,1065,1187,1191,1206,1211,1263,1422,1490,1520,1528,1804,1814 'saq':3,9,24,33,44,49,52,61,168,217,239,248,261,308,311,316,343,378,401,420,462,539,543,740,747,776,819,909,987,1216,1219,1234,1268,1400,1431,1445,1557,1583,1596,1646,1649,1718,1783,1791,1841,1885 'saq-py.readthedocs.io':1905 'saq-py.readthedocs.io/en/latest/':1904 'saqconfig':12,82,250,270,353,1022,1585,1605 'saqconfig.use':1452 'saqplugin':11,72,251,263,268,346,351,1448,1586,1598,1603 'schedul':21,298,622,971,1110,1298,1636 'scheduleconfig':1858 'second':948 'see':502,631,1051,1183,1368,1437,1572 'self':431,832,1737 'self-host':430 'send':756,767,846,1655,1666,1751 'sensibl':1501 'separ':101,884,1208,1524,1806 'server':200,276,360,1060,1199,1329,1453,1619 'servic':720,794,798,1104,1677 'session':233,303,388,1635,1641,1686,1692,1704 'set':258,264,266,347,349,932,1058,1123,1240,1456,1487,1495,1593,1599,1601 'settings.database.url':355,1564,1612 'settings.redis.url':272,511,1561,1607 'settings.saq.web':288,365,1624 'setup':181,1053 'share':205,1098,1371,1907,1911 'signatur':658 'simpl':62 'simpler':1435 'singl':433 'skill':1922,1932 'skill-litestar-saq' 'skip':477,563,958,1411,1848 'slot':1256 'small':1336 'small-to-mid':1335 'soft':679 'soft-delet':678 'source-litestar-org' 'specif':1937 'sql':428 'src/py/app/server/plugins.py':194 'stack':230,386,530,1035,1327,1617 'start':93,977 'startup':1013 'state':783,796,1107,1375,1675,1698 'status':863,901,1768 'step':979,988,1014,1079,1108,1138,1160,1185 'str':761,763,765,839,840,1660,1662,1664,1744,1745 'stuck':1272 'style':122 'styleguid':1908,1912 'subject':762,787,789,802,850,1661,1681,1755 'support':1050,1385 'swap':1559,1610 'switch':1345 'sync':1320 'tabl':630 'tag':1730 'target':485,580,696,724,1418,1862 'task':150,163,171,295,299,372,480,567,601,615,624,626,652,665,666,688,751,835,1003,1082,1084,1118,1154,1182,1243,1252,1358,1461,1469,1484,1531,1541,1631,1637,1740,1856,1886 'task-decor':651 'task_queues.get':842,1149,1747 'taskqueu':14,81,114,810,821,837,1144,1278,1508,1720,1742 'taskservic':499,550,1843 'taskservice.create':623 'test':1382 'thin':498,549 'throughput':338,412,439,1351 'time':661,1169,1893 'timeout':306,670,693,854,929,1124,1241,1249,1307,1486,1498,1644,1759 'timestamp':974 'toggl':1072 'tool':1130,1936 'tool-specif':1935 'topic-advanced-alchemy' 'topic-agent-skills' 'topic-agentskills' 'topic-ai-agents' 'topic-claude-code-plugin' 'topic-claude-code-skills' 'topic-gemini-cli-extension' 'topic-htmx' 'topic-inertia' 'topic-litestar' 'topic-mcp' 'topic-python' 'transact':452 'true':202,278,362,907,1062,1201,1331,1621 'ttl':946 'tune':1822 'ui':26,105,241,403,903,911,1078,1232,1894 'union':127 'unix':973 'up':470,573,1408,1855 'updat':476,562,1170,1364,1410,1847,1895 'use':27,124,138,195,199,275,359,642,869,928,1059,1127,1148,1156,1198,1213,1258,1295,1310,1328,1514,1553,1618,1910 'user':1319 'utc':687 'valid':1440 'verifi':1447 'via':1197,1279,1506 'wake':469,572,1407,1854 'wake-up':468,571,1406,1853 'want':237,331,399,444,560 'web':25,104,212,240,282,286,363,402,902,905,910,1068,1073,1193,1231,1343,1622,1796 'websocket':1366,1879 'will':494 'win':557 'wire':600,729 'work':35,1112,1121,1132,1299 'worker':17,75,86,89,94,204,279,398,608,868,878,881,891,900,1064,1192,1205,1255,1340,1388,1521,1527,1795,1807,1813,1831 'workerplugin':500,551,599,1844 'workflow':978,1938 'wrap':1018 'zero':472,587","prices":[{"id":"0f8c5c3b-ec29-4471-b7ef-9b4c895f907a","listingId":"408249b5-4c0c-4416-a3b6-c1faed0993d5","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"litestar-org","category":"litestar-skills","install_from":"skills.sh"},"createdAt":"2026-05-18T13:20:58.566Z"}],"sources":[{"listingId":"408249b5-4c0c-4416-a3b6-c1faed0993d5","source":"github","sourceId":"litestar-org/litestar-skills/litestar-saq","sourceUrl":"https://github.com/litestar-org/litestar-skills/tree/main/skills/litestar-saq","isPrimary":false,"firstSeenAt":"2026-05-18T13:20:58.566Z","lastSeenAt":"2026-05-18T19:13:54.668Z"}],"details":{"listingId":"408249b5-4c0c-4416-a3b6-c1faed0993d5","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"litestar-org","slug":"litestar-saq","github":{"repo":"litestar-org/litestar-skills","stars":7,"topics":["advanced-alchemy","agent-skills","agentskills","ai-agents","claude-code-plugin","claude-code-skills","gemini-cli-extension","htmx","inertia","litestar","mcp","python","sqlspec"],"license":"mit","html_url":"https://github.com/litestar-org/litestar-skills","pushed_at":"2026-05-13T16:04:09Z","description":"Opinionated first-party agent skills, plugins, subagents, slash commands, and MCP servers for the Litestar framework ecosystem — publishable to Claude Code, Gemini CLI, Codex CLI, Cursor, OpenCode, and VS Code/Copilot from a single repo.","skill_md_sha":"82b11ace64257d0bf1b47700d727b6698139130f","skill_md_path":"skills/litestar-saq/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/litestar-org/litestar-skills/tree/main/skills/litestar-saq"},"layout":"multi","source":"github","category":"litestar-skills","frontmatter":{"name":"litestar-saq","description":"Auto-activate for litestar_saq imports, SAQPlugin, SAQConfig, QueueConfig, TaskQueues, CronJob, litestar workers run, background jobs, scheduled jobs, or SAQ web UI. Use when adding first-party SAQ background work to Litestar. Not for Celery, RQ, Dramatiq, raw SAQ outside Litestar, or custom PostgreSQL-native queues unless explicitly chosen."},"skills_sh_url":"https://skills.sh/litestar-org/litestar-skills/litestar-saq"},"updatedAt":"2026-05-18T19:13:54.668Z"}}