Background work runs as a task, not a workflow - #316
Merged
Conversation
czpython
force-pushed
the
commonzenpython/eng-826-task-author-door
branch
from
August 24, 2026 07:22
952fcc3 to
f3e4335
Compare
A Workflow carries a run row, a subject timeline, gates, and operator-tunable
settings — the weight is right for work an operator watches, and wrong for
plumbing. `task` is the lighter door: a durable, replay-recoverable function
that runs its body in one checkpointed step, with no run row, no timeline, no
subject, and no agent calls.
@task(every="*/15 * * * *")
async def refresh_tokens() -> None: ...
@task(retries=4)
async def sync_labels(pull_request_id: int) -> None: ...
`every=` runs it on a fixed cadence the code owns; `.enqueue(**kwargs)` runs one
in the background from a route, a subscriber, or a workflow body. `retries=`
sets retries after the first attempt, and `@step` now takes the same knob.
The token- and model-refresh chores become tasks — they never belonged on the
run timeline, and their cadence is no longer an operator setting. Apps declare
tasks in a `tasks.py` capability module.
A duplicate durable name raises at declaration instead of riding DBOS's warn-and-overwrite, enqueue() validates the input model synthesized from the signature and stores JSON — never a pickled live object — and refuses to run inside a @step, where a retry would enqueue again. Doctor now also flags a @task hiding in an off-canon module the discovery walk never imports.
czpython
force-pushed
the
commonzenpython/eng-826-task-author-door
branch
from
August 24, 2026 08:09
60df8c7 to
0f12587
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A
Workflowcarries a run row, a subject timeline, gates, and operator-tunable settings. That weight is right for work an operator watches on a board, and wrong for plumbing — periodic maintenance, a fire-and-forget side effect. Until now the only door wasWorkflow, so plumbing paid the full tax.taskis the lighter door: a durable, replay-recoverable function that runs its body in one checkpointed step.every=runs it on a fixed UTC cadence the code owns — a workflow'severy=stays the one an operator can retune.await sync_labels.enqueue(pull_request_id=7)runs one durably in the background, from a route, a subscriber, or a workflow body.retries=sets retries after the first attempt;@stepnow takes the same knob.Workflow.The token- and model-refresh chores move to
core/tasks.py. They never belonged on the run timeline, and their cadence is no longer an operator setting. Apps declare tasks in atasks.pycapability module, discovered likeworkflows.py.