Release notes
v0.5.0 - Correctness & Stability (Phase 1/6) - 2026-07-26
Phase 1 of the roadmap to v1.0.0: three concurrency bug fixes.
Behavior changes
run_task_immediately()now raisesTaskNotActiveErrorwhen the task is notactive. Previously arunningtask could be dispatched a second time concurrently (breaking the no-overlap guarantee) and apausedtask was silently un-paused. Resume paused tasks explicitly withresume_task().
What's new
shutdown(timeout=...)/stop(timeout=...)— optional bound on how long shutdown waits for the scheduler thread and in-flight jobs. Jobs that do not exit within the deadline are abandoned on their worker threads with a warning, so a hung handler can no longer block application shutdown forever. Default (timeout=None) keeps the previous wait-forever behavior.- New exception
TaskNotActiveError(exported fromquiv).
Fixes
remove_task()racing the dispatch loop could raise aKeyErrorthat stalled the scheduler for 5 seconds; dispatch now skips removed tasks gracefully, and the shared handler/callback/stop-event registries are protected by a lock.shutdown()now signals cancellation only torunningjobs instead of scanning the entire retained job history.
Housekeeping
- Roadmap phases renumbered: v0.3.x/v0.4.x were already consumed by earlier feature releases, so Phase 1 ships as
v0.5.0and later phases shift down accordingly (Phase 6 staysv1.0.0). - 7 new regression tests; docs, release notes, and AI-tooling artifacts updated; version bumped to
0.5.0. - Docs cleanup: all Markdown files unwrapped to one paragraph per continuous line (zensical can break rendering when a sentence/paragraph is split across source lines). No content changes.
v0.4.1 - AI Tooling & Async teardown fix - 2026-07-20
What's new
-
AI tooling — quiv now teaches AI coding assistants (Claude Code, Cursor, Copilot, …) how to use it correctly, in #51:
- Packaged agent guide — every install ships a condensed agent-facing reference at
quiv/AGENTS.mdinside the package (lands insite-packagesnext to the code): API surface, handler injection rules, cancellation semantics, FastAPI wiring, and common pitfalls. AI tools exploring installed dependencies pick it up with zero setup. - llms.txt — the docs site publishes llms.txt (index linking every docs page as raw Markdown) and llms-full.txt (the full docs in one file) following the llms.txt convention.
- Claude Code plugin — the quiv repository doubles as a plugin marketplace shipping a
quivskill that Claude loads automatically whenever a conversation touches quiv. Install once with/plugin marketplace add nandyalu/quivthen/plugin install quiv@quiv;/plugin update quivpicks up the latest guidance.
See the new AI Tools docs page for all three entry points.
- Packaged agent guide — every install ships a condensed agent-facing reference at
Fixes
- Per-invocation async event loops now finalize outstanding async generators and shut down the loop's default executor before closing. Previously, if a handler raised while an async generator was still active, Python could emit
Task was destroyed but it is pendingandasync_generator_athrow was never awaitedwarnings during teardown. The original handler exception remains visible. by @d4rk22 in #50
Other changes
- ci: skip coverage PR comment for fork PRs
- upd: roadmap for v1.0.0
- build(deps-dev): bump zensical from 0.0.43 to 0.0.45 by @dependabot[bot] in #42
- build(deps): bump actions/checkout from 6 to 7 by @dependabot[bot] in #44
- build(deps-dev): bump pytest from 9.0.3 to 9.1.1 by @dependabot[bot] in #45
- build(deps): bump sqlmodel from 0.0.38 to 0.0.39 by @dependabot[bot] in #46
- build(deps-dev): bump zensical from 0.0.45 to 0.0.50 by @dependabot[bot] in #47
- build(deps): bump tzdata from 2026.2 to 2026.3 by @dependabot[bot] in #48
- build(deps-dev): bump mypy from 2.1.0 to 2.2.0 by @dependabot[bot] in #49
- fix: finalize async resources before closing job loops by @d4rk22 in #50
New Contributors
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.4.0...v0.4.1
v0.4.0 - run_on_main helper method - 2026-05-31
What's new
-
quiv.run_on_main(func, *args, **kwargs)— fire-and-forget helper that dispatches a callable onto the active Quiv instance's main event loop. Importable at module level (from quiv import run_on_main) and callable from anywhere in a task handler's call stack - no_progress_hookparameter to thread through intermediate functions.- Auto-detects whether the caller is already on the main loop's thread: sync targets run inline on-loop and async targets are scheduled via
main_loop.create_task. From a worker thread, sync targets dispatch viacall_soon_threadsafeand async targets viarun_coroutine_threadsafe. - The same helper works from a FastAPI route handler on the main loop and from inside a Quiv task — one utility shared between request and task code (e.g., a WebSocket broadcast whose connected-clients state lives on uvicorn's loop).
- Exceptions raised by the target are logged on the active Quiv's logger and swallowed, mirroring
_progress_hookand event-listener semantics.
See Running on the main event loop for the full walkthrough.
- Auto-detects whether the caller is already on the main loop's thread: sync targets run inline on-loop and async targets are scheduled via
Other changes
Quiv.start()now registers the instance as the process-level "active" Quiv (cleared byshutdown()) sorun_on_maincan resolve a target loop when called outside a task context. Multiple concurrent instances log a warning naming the most recently started instance as the winner for out-of-task callers.- Inside
_run_job, aContextVaris set to the runningQuivinstance for the duration of each handler invocation; this propagates into nested sync calls, the per-job async event loop, andasyncio.create_taskspawned inside an async handler.
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.3.5...v0.4.0
v0.3.5 - Cleanup noise & update packages - 2026-05-27
What's new
- Removed a debug logging statement from the
set_timezone_to_utcmethod to reduce log verbosity by @nandyalu in #38
Other changes
- Updated various python libraries to latest versions.
- build(deps): bump actions/upload-pages-artifact from 4 to 5 by @dependabot[bot] in #24
- build(deps-dev): bump zensical from 0.0.32 to 0.0.33 by @dependabot[bot] in #25
- build(deps-dev): bump mypy from 1.20.0 to 1.20.1 by @dependabot[bot] in #26
- build(deps-dev): bump zensical from 0.0.33 to 0.0.36 by @dependabot[bot] in #29
- build(deps): bump tzdata from 2026.1 to 2026.2 by @dependabot[bot] in #28
- build(deps-dev): bump mypy from 1.20.1 to 1.20.2 by @dependabot[bot] in #27
- build(deps): bump orgoro/coverage from 3.2 to 3.3 by @dependabot[bot] in #30
- build(deps-dev): bump zensical from 0.0.36 to 0.0.39 by @dependabot[bot] in #31
- build(deps-dev): bump zensical from 0.0.39 to 0.0.41 by @dependabot[bot] in #32
- build(deps-dev): bump mypy from 1.20.2 to 2.0.0 by @dependabot[bot] in #33
- build(deps-dev): bump zensical from 0.0.41 to 0.0.42 by @dependabot[bot] in #34
- build(deps-dev): bump mypy from 2.0.0 to 2.1.0 by @dependabot[bot] in #35
- build(deps): bump pymdown-extensions from 10.21.2 to 10.21.3 in the uv group across 1 directory by @dependabot[bot] in #36
- build(deps-dev): bump zensical from 0.0.42 to 0.0.43 by @dependabot[bot] in #37
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.3.4...v0.3.5
v0.3.4 - Job and Task attrs datetime aware - 2026-04-10
Bug fixes
- Job datetime normalization:
Jobdatetimes (started_at,ended_at) loaded from SQLite are now correctly normalized to UTC-aware. Previously, the@model_validatoronJobwas dead code because SQLAlchemy bypasses Pydantic validators when hydrating table classes.
Improvements
- Generic datetime normalization via
@reconstructor: Added a@reconstructormethod onQuivModelBasethat automatically normalizes alldatetimefields to UTC-aware on every DB load. This applies to bothTaskDBandJobmodels (and any future models), replacing the per-model dead-code validators. - Removed dead
@model_validatorfromTaskDBandJobmodels. delete_task()now has test coverage forTaskNotFoundErroron missing task.- updated python packages to latest versions.
Documentation
- New Testing page documenting all 108 tests with a breakdown of edge cases covered across 18 categories.
CI
docs-release.ymlnow waits for the release to be visible in the GitHub API before generating the changelog, fixing a race condition where the current release was missing from the generated docs.
v0.3.3 - fixed inteval option for tasks - 2026-04-09
Breaking changes
- Default interval scheduling changed to fixed intervals:
fixed_intervaldefaults toTrue, meaning next run is now scheduled from the job start time rather than completion time. Setfixed_interval=Falseto restore the previous wait-between-runs behavior.
What's new
-
fixed_intervalper-task scheduling mode:add_task()accepts a newfixed_intervalparameter:True(default) — next run at fixed intervals from job start time. If a run exceeds the interval, missed intervals are skipped.False— next runintervalseconds after job completion (old behavior).
Other changes
finalize_task_after_job()acceptsjob_started_atfor fixed-interval scheduling.
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.3.2...v0.3.3
v0.3.2 - Removed unique task name constraint - 2026-04-09
Breaking changes
-
Task operations now use
task_idinstead oftask_name: All public methods that previously accepted atask_namestring now accept thetask_id(UUID string) returned byadd_task(). This removes the uniqueness constraint on task names — multiple tasks can now share the sametask_name.Affected methods: -
remove_task(task_id)— previouslyremove_task(task_name)-pause_task(task_id)— previouslypause_task(task_name)-resume_task(task_id)— previouslyresume_task(task_name)-run_task_immediately(task_id)— previouslyrun_task_immediately(task_name)-get_task(task_id)— previouslyget_task(task_name)(by-name lookup)Removed methods: -
get_task_by_id()— merged intoget_task(task_id)Migration: Store the return value of
add_task()and pass it to all task operations:# Before scheduler.add_task("my-task", handler, interval=60) scheduler.pause_task("my-task") # After task_id = scheduler.add_task("my-task", handler, interval=60) scheduler.pause_task(task_id) -
Event listeners receive typed model objects instead of dicts
Event listener callbacks now receive
TaskandJobmodel objects directly instead of untypeddict[str, Any].TASK_*events:callback(event: Event, task: Task)JOB_*events:callback(event: Event, task: Task, job: Job)
Migration:
# Before def on_completed(event, data): print(data["task_name"], data["duration"]) # After from quiv.models import Task, Job def on_completed(event: Event, task: Task, job: Job): print(task.task_name, job.duration_seconds)
What's new
-
Duplicate task names allowed
add_task()no longer raisesConfigurationErroron duplicatetask_name. Each call returns a uniquetask_id(UUID), so multiple tasks can share a display name. This is especially useful for one-shot tasks that may be scheduled repeatedly with the same name. -
duration_secondsanderror_messageon the Job modelThe
Jobmodel now includes two new fields:duration_seconds: float | None— job runtime in seconds, set when the job finisheserror_message: str | None— error description, set when a job fails
Both fields are persisted in the database and available via
get_job()andget_all_jobs(), making it easy to inspect job history without parsing logs. -
Typed event listener callbacks
Event listeners now receive real
TaskandJobmodel objects with full IDE autocomplete and type checking.JOB_*events include the parentTaskalongside theJob, so listeners have full context without extra lookups.
Documentation
- Updated all docs to reflect
task_id-based API across getting-started, API reference, architecture, event listeners, bigger applications, progress callbacks, and exceptions pages. - Updated all code examples to use
task_idfor runtime operations. - Added admonitions and footnotes throughout docs for better readability.
- Added
_job_idtracing section to the "Why quiv?" page, describing how Trailarr uses injected job IDs as trace context for log correlation. - Rewrote event listeners documentation with typed callback signatures, updated examples, and new FastAPI WebSocket example using model objects.
Other changes
- Internal handler and progress callback registries are now keyed by
task_idinstead oftask_name. prepare_invocation()in the execution layer usestask_idfor progress callback dispatch.- Removed
get_task_by_name(),get_task_id_by_name()from the persistence layer. - Renamed
get_task_by_id()toget_task()in the persistence layer. delete_task()andqueue_task_for_immediate_run()in the persistence layer now accepttask_idinstead oftask_name.finalize_job()now accepts optionalduration_secondsanderror_messageparameters.- Removed unused
timezoneimport from persistence module.
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.3.1...v0.3.2
v0.3.1 - Better task pickling - 2026-04-07
Breaking Changes
- The public methods
get_task(),get_task_by_id(), andget_all_tasks()returnTaskobjects with unpickledargs(tuple) andkwargs(dict) — ready for JSON serialization in FastAPI endpoints. - Internal model renamed: The SQLModel database model is now
TaskDB(internal only, not exported). External modelTaskis still the same - but is now only used for Public API responses and has correct types.
What's New
- Async callbacks without event loop: Async progress callbacks and event listeners now run in a temporary event loop when no main loop is available, instead of being skipped with a warning.
- Eager main loop resolution: The main event loop is now resolved at
start()time (in addition to lazy resolution on first callback), improving reliability in FastAPI apps.
Documentation
- Added return types to all method signatures in API docs (e.g.,
get_task(task_name: str) -> Task. - Updated architecture, progress-callbacks, and event-listeners docs to reflect new async callback behavior
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.3.0...v0.3.1
v0.3.0 - Better pickling and Event listeners - 2026-04-07
What's Changed
- Changed argument serialization for task persistence from JSON to pickle, allowing most Python objects (except lambdas and inner functions) to be scheduled as arguments by @nandyalu in #19
- Added support for global event listeners via
add_listener(event, callback)andremove_listener(event, callback), including both sync and async callbacks, with robust dispatch and error handling. Events include all major task and job lifecycle transitions. by @nandyalu in #19 - Introduced
startup()as an alias forstart(), andstop()as an alias forshutdown(), makingstart/stoppairs more natural in user code by @nandyalu in #19 Job.idnow usesUUIDand can be injected into task function (as_job_id) if function accepts it - can be used for task tracing / logging by @nandyalu in #19- Updated all relevant documentation by @nandyalu in #19
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.2.4...v0.3.0
v0.2.4 - Preserve task args order - 2026-04-07
What's Changed
add_taskargs as tuple to preserve order by @nandyalu in #18loggeracceptslogging.Loggeras well aslogging.LoggerAdapterby @nandyalu in #18
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.2.3...v0.2.4
v0.2.3 - Exception logging improvements - 2026-04-06
What's Changed
- Bump zensical from 0.0.24 to 0.0.27 by @dependabot[bot] in #8
- Bump zensical from 0.0.27 to 0.0.28 by @dependabot[bot] in #9
- Bump pytest-cov from 7.0.0 to 7.1.0 by @dependabot[bot] in #10
- Bump actions/configure-pages from 5 to 6 by @dependabot[bot] in #11
- Bump actions/deploy-pages from 4 to 5 by @dependabot[bot] in #12
- Bump zensical from 0.0.28 to 0.0.30 by @dependabot[bot] in #13
- Bump sqlmodel from 0.0.37 to 0.0.38 by @dependabot[bot] in #14
- Bump tzdata from 2025.3 to 2026.1 by @dependabot[bot] in #15
- Bump mypy from 1.19.1 to 1.20.0 by @dependabot[bot] in #16
- Improve job logging with task names and error details by @nandyalu in #17
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.2.2...v0.2.3
v0.2.2 - SQLModel Registry Fix - 2026-03-13
What's Changed
-
fix: quiv registry to not include user models. Updated the private
registryusage forSQLModelmodels ofQuivto the method from https://github.com/fastapi/sqlmodel/discussions/1539#discussioncomment-14229572 by @nandyalu in #7 -
Added a test to ensure user's
SQLModelwithtable=Truedoes not get created in Quiv database by @nandyalu in #7
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.2.0...v0.2.2
v0.2.1 - Fix Release Build - 2026-03-11
What's Changed
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.2.0...v0.2.1
v0.2.0-Bug Fixes and minor updates - 2026-03-09
Breaking Changes
timezone_namerenamed totimezone— BothQuiv()andQuivConfig()now usetimezonefor the display timezone parameter. Update anytimezone_name=keyword arguments.register_handler/register_progress_callbackare now private — Renamed to_register_handler/_register_progress_callback. Useadd_task()instead, which handles registration internally.add_task()raises on duplicate task names — Callremove_task()first if you need to replace a task.
New Features
remove_task(task_name)— Remove a task and its handler/callback registrations.- Cooperative cancellation —
_stop_eventinjection with per-jobthreading.Event. See Cancellation docs. - Progress callbacks with four dispatch paths — Async/sync callbacks work with or without an event loop. See Progress Callbacks docs.
- Lazy event loop resolution —
Quiv()can be instantiated at module level before any asyncio loop exists. The event loop is resolved on first progress callback dispatch. - Backpressure — Scheduler defers dispatch when the thread pool is full. Late-starting jobs log a warning suggesting to increase
pool_size. TaskStatus.RUNNING— Tasks are markedRUNNINGduring execution, preventing concurrent runs of the same task.
Bug Fixes
- Removed
logger.setLevel(logging.DEBUG)— the library no longer forces a log level - Removed
asyncio.get_event_loop()at init — fixes deprecation warnings and module-level instantiation shutdown()now cleans up SQLite WAL (-wal,-shm) sidecar files- Cancellation detection no longer depends on handler accepting
_stop_event
Improvements
TaskStatus/JobStatusare now proper(str, Enum)classes- History cleanup uses SQL-level filtering with
col()wrapper (runs every 60s, not every tick) - Next run scheduled from job completion time, not dispatch time
- All log timestamps use the configured display timezone consistently
Documentation
- New pages: Bigger Applications, Progress Callbacks, Cancellation (with mermaid diagrams)
- Architecture page now has a sequence diagram
- API page: added pool size guidance, logger/timezone note blocks
- Tabbed uv/pip install commands across all pages
- GitHub repo link in docs header with edit/view source buttons
Tests
- 8 new tests covering backpressure, late start warnings, concurrent run prevention, remove_task, progress callbacks without event loop, and task lifecycle
- All existing tests updated for API changes
Repository
- Added CODEOWNERS, issue templates, CONTRIBUTING.md, CODE_OF_CONDUCT.md
- CI workflows now include
pyproject.tomlin path filters - Removed
masterbranch reference from docs deploy workflow
Initial Release (v0.1.0) - 2026-03-08
Initial Release