Skip to content

feat(adapters): FastAPI / Starlette durable background tasks - #94

Merged
sdageltc merged 1 commit into
sdageltc:mainfrom
h4syy:feat/fastapi-durable-background-tasks
Aug 31, 2026
Merged

feat(adapters): FastAPI / Starlette durable background tasks#94
sdageltc merged 1 commit into
sdageltc:mainfrom
h4syy:feat/fastapi-durable-background-tasks

Conversation

@h4syy

@h4syy h4syy commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Description

FastAPI's BackgroundTasks run inside the worker process and are lost if the worker restarts, redeploys, or is killed mid-task. This adds DurableBackgroundTasks, a drop-in dependency backed by LetItLoop's zero-daemon WAL: every task is journaled (fsync'd) before it runs, and any task interrupted by a crash is resumed automatically on the next startup — no Redis, no Celery, no separate worker.

Usage stays exactly as the issue describes:

from fastapi import FastAPI
from letitloop.adapters.fastapi import (
    DurableBackgroundTasks, durable_task, install_durable_background_tasks,
)

app = FastAPI()
install_durable_background_tasks(app)          # attaches WAL + resume-on-startup

@durable_task()                                # optional: stable key across renames
async def run_durable_report(report_id: str): ...

@app.post("/generate-report/{report_id}")
async def generate_report(report_id: str, background_tasks: DurableBackgroundTasks):
    background_tasks.add_task(run_durable_report, report_id)
    return {"status": "queued"}

How it works

  • DurableTaskManager — the WAL ledger (tasks.wal.jsonl) plus the resume engine.
  • DurableBackgroundTasks — exposed as Annotated[..., Depends(...)], so the bare bg: DurableBackgroundTasks annotation works with full type hints and native DI (rather than subclassing BackgroundTasks, which FastAPI would re-instantiate as the base type).
  • durable_task(...) — optional decorator to pin a stable key; falls back to module:qualname so the snippet above works with no extra API.
  • install_durable_background_tasks(app) — stores the manager on app.state and wraps the app lifespan to resume interrupted tasks on boot (any existing lifespan= is preserved).
  • Two durability layers: at-least-once at the task level (PENDINGCOMPLETED), and step-level skip via each task running inside a per-task @durable context.

FastAPI/Starlette are imported lazily, so import letitloop never requires them and the adapter keeps the suite's zero-mandatory-dependency guarantee.

Closes #93

Type of Change

  • Bug fix
  • New feature / capability
  • New framework adapter (agent-durability-bench)
  • Documentation update
  • CI / Tooling improvement

Verification Checklist

  • Unit tests added/updated and passing (pytest) — 14 new tests in tests/adapters/test_fastapi.py, including the crash-then-resume contract; DI tests skip cleanly when FastAPI is absent. Full -m fast suite (505 passed) with no regressions.
  • AST invariants and types preserved — additive adapter only; no changes to the core kernel.
  • Zero unapproved dependency additions — no new mandatory runtime deps; only an optional [fastapi] extra plus fastapi/httpx added to the dev extra so CI exercises the DI path.
  • Self-reviewed for edge cases and clean error handling — non-serializable args fail fast; failed tasks are terminal (no retry storm); the resume sweep isolates per-task failures; stale per-task locks are auto-stolen on resume.

@h4syy
h4syy requested a review from sdageltc as a code owner August 30, 2026 18:12
FastAPI's BackgroundTasks run in the worker process and are lost on
restart, redeploy, or crash. This adds DurableBackgroundTasks, a drop-in
DI dependency backed by LetItLoop's zero-daemon WAL: each task is
recorded before it runs and any task interrupted by a crash is resumed
automatically on startup - no Redis, no separate worker.

- letitloop/adapters/fastapi.py: DurableTaskManager (WAL + resume),
  DurableBackgroundTasks (Annotated + Depends DI), durable_task registry
  (explicit key or auto module:qualname), install_durable_background_tasks
  (wraps app lifespan to resume on startup). FastAPI imported lazily so
  `import letitloop` never requires it.
- register the adapter in the adapters package and get_available_adapters()
- tests/adapters/test_fastapi.py: durability contract incl. crash+resume
- docs/adapters.md: FastAPI/Starlette section
- examples/fastapi_durable_background.py: canonical app + runnable demo
- pyproject: add `fastapi` optional extra; add fastapi/httpx to dev extras

Closes sdageltc#93
@h4syy
h4syy force-pushed the feat/fastapi-durable-background-tasks branch from dcf2836 to 1897f27 Compare August 30, 2026 18:16

@sdageltc sdageltc left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic contribution! The DurableBackgroundTasks dependency and install_durable_background_tasks(app) integration are clean, robust, and well-tested.

All 14 tests in tests/adapters/test_fastapi.py pass cleanly. Merging into main!

@sdageltc
sdageltc merged commit ec858f2 into sdageltc:main Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature: FastAPI / Starlette Background Tasks Durability Middleware

2 participants