background-task
Skill241 starsChanged 29 days agoApache-2.0View on GitHub
Add or change work that runs outside the request/response cycle — document ingestion, syncs, trigger fires, reports, cleanups, scheduled jobs — or debug a job that died, looped or never ran. Use when something is slow or must survive a restart, when adding a periodic job, when touching app/worker/, and before reaching for asyncio.create_task (which drops work silently in this codebase's exact failure mode).
In this repository22 other files it gives its agents.
- AGENTS.mdAGENTS.md
- frontend/AGENTS.mdAGENTS.md
- llms.txtllms.txt
- agent-capabilitySkill
- agent-specSkill
- alembic-migrationSkill
- backend-testsSkill
- channel-botSkill
- dev-agentSkill
- e2e-testsSkill
- frontend-featureSkill
- mcp-connectionsSkill
- permissions-rbacSkill
- pr-commentsSkill
- pr-descriptionSkill
- project-docsSkill
- rag-knowledgeSkill
- resolve-changelog-conflictSkill
- review-mapSkill
- ste-writingSkill
- vault-secretsSkill
- vstorm-code-reviewSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 56
Write the handler in app/worker/tasks/<area>_tasks.py.
- Very specific: line 89
dedupe_key/dedupe_active/concurrency_key: instead of checking for a running job by hand - the partial unique indexes make them …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Background work — the job queue, and one in-process handoff
Read docs/howto/add-background-task.md. Jobs are rows in background_jobs
(PostgreSQL); python -m app.worker claims and runs them. Kinds are declared
in app/worker/jobs.py, handlers live in app/worker/tasks/ and are bound in
app/worker/handlers.py, schedules are in app/worker/schedules.py.
docs/configuration.md#background-work has the settings and the memory model.
Pick the right one
| Use for | Survives a restart | |
A job (enqueue) | Ingestion, syncs, fires, reports, anything scheduled, retryable or long | Yes |
spawn_after_commit() from app/core/background.py | Short best-effort work on the API's loop that reads a row this unit of work wrote | No |
spawn(), same module | The same, when it reads nothing the request wrote | No |
| Inline | Fast, transactional work the response depends on | n/a |
# AgentTriggerService.queue_fire, as it is
from app.services.background_jobs import enqueue
from app.worker.jobs import FIRE_TRIGGER, FireTrigger
await enqueue(
self.db,
FIRE_TRIGGER,
FireTrigger(trigger_id=trigger_id, event_context=event_context, claimed_at=claimed_at),
organization_id=organization_id,
)Queue in the caller's transaction, from a service. The job row commits with
the rows it names and NOTIFY fires only on commit, so the #417 race (a task
that started before the commit and could not see its row) cannot happen.
spawn_after_commit is still right for work that may be lost on a restart
without harm; anything that must happen is a job.
Never bare asyncio.create_task. The event loop holds it only weakly, and an
exception in a discarded task is never retrieved. app/core/background.py
fixes both; drain(timeout=30.0) is its shutdown counterpart, and a CLI command
that spawns work must call it before returning.
app/worker/background/channel.py is the older in-process fallback for channel
work, dispatched with fire_and_forget(coro, *, label=...): no retry, no
persistence. Do not add to it.
Add a job kind
- Declare a
Payloadand aTaskinapp/worker/jobs.py— pool, attempts, timeout, priority. Attempts follow one rule: 3 by default, 2 for ingestion, 1 for a scheduled sweep (its next tick is its retry) and for an API run. - Write the handler in
app/worker/tasks/<area>_tasks.py; it opens its own session (get_worker_db_context) and may return a Pydantic model as the job's result. - Bind it in
production()inapp/worker/handlers.py. A sweep that takes no payload is wrapped with_each_tick. - Queue it from a service with
enqueue, or add aScheduletoSCHEDULES.test_every_declared_schedule_names_a_task_the_worker_knowsfails a schedule with no handler.
Pick the pool by what the job spends: agent for model-bound runs, ingest for
parsing and embedding (each job in a child process under a memory watchdog),
system for short sweeps.
Rules
- The attempt is spent at the claim. A job that kills its process - OOM,
SIGKILL- has used one, so it cannot loop. Never move the increment out of the claim (_CLAIMinapp/repositories/background_job.py). This is a hard requirement, measured against five other engines in #2219. - Fence every write. A job is settled with the
attemptsit was claimed with; a worker whose lease was taken over writes nothing and the takeover is counted aslease_lost. Handlers that charge money or send mail must be safe to run twice anyway: a lost lease means it may have. - Serializable payloads only — ids and primitives, never ORM objects. The
handler re-reads the row and re-establishes authority (
ApiKeyService.context_for_keyfor a key, active membership for a person): by then it may be gone. - Heavy imports inside the handler module, not in
jobs.py, so the API that queues never imports the parser that runs. - Record what was spent even when the job fails. Embedding spend goes to
ingestion_spend; a cost that lands only on success is a budget with a hole. - No ambient tenant in a worker. A job that touches an org-scoped resource
carries the organization id in its payload;
organization_idonenqueueis only for the panel's filter. dedupe_key/dedupe_active/concurrency_keyinstead of checking for a running job by hand - the partial unique indexes make them race-free.
Debugging
- Admin → Background jobs shows every job's attempts and last error, the
workers alive, the paused kinds and the schedules.
agenticos cmd doctorfails when no worker has beaten for two minutes, a minute schedule stopped firing, or a kind is paused. - A kind paused by the breaker (
WORKER_BREAKER_DEATHSworker deaths in a row) stays paused until an owner resumes it; fix the cause first. - Tests:
tests/integration/test_background_job_queue.py,tests/integration/test_worker_runtime.py(real kills),tests/test_worker_child_pool.py; misbehaving test kinds are intests/worker_handlers.py. Seedocs/testing.md#the-workers-tests.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

