Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@ concurrency:
jobs:
test:
name: python
runs-on:
- self-hosted
- modal
- ci
- job-${{ github.run_id }}-${{ github.job }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
Expand Down
6 changes: 1 addition & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ permissions:

jobs:
release:
runs-on:
- self-hosted
- modal
- ci
- job-${{ github.run_id }}-${{ github.job }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Concise always-on rules for this repo. Prefer Modal SDK shapes, idiomatic Python

## Errors, HTTP, secrets

- Soft absence → `None` or HTTP 204 (e.g. undeployed `url`, ignored webhook). Failures raise a small set: `ValueError`, `LookupError`, `AuthError`, `ConcurrencyLimitError`, `JobTimeoutError`. Base `RunnerError` is rare.
- Soft absence → `None` or HTTP 204 (e.g. undeployed `url`, ignored webhook). Failures raise a small set: `ValueError`, `LookupError`, `AuthError`, `ConcurrencyLimitError`. Base `RunnerError` is rare.
- Do not wrap Modal / httpx failures in `RunnerError`. Propagate; map only product/auth cases.
- FastAPI: HTTP status conveys success. Response models without `ok: bool`. Use `HTTPException` for 401 / 400 / 503.
- Sync I/O (Modal SDK, httpx): use sync `def` endpoints or `asyncio.to_thread`. Never block `async def` handlers with sync Modal/httpx calls.
Expand Down Expand Up @@ -90,5 +90,5 @@ Concise always-on rules for this repo. Prefer Modal SDK shapes, idiomatic Python

## Workspace

- This repo’s GitHub Actions CI and Release workflows run on self-hosted `runner-modal` runners (`self-hosted`, `modal`, `ci`, unique `job-…` pin), not GitHub-hosted runners.
- Maintainer CI App is `scripts/ci_app.py` (App `runner-modal-ci-app`).
- This repo’s GitHub Actions CI and Release workflows run on ``ubuntu-latest``.
- Maintainer CI App is `scripts/ci_app.py` (App `runner-modal-ci-app`) for dogfooding Jobs, not for repo CI.
42 changes: 18 additions & 24 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,5 @@
# Changelog

## Unreleased

### Breaking

- Job Sandbox entrypoint module is `runner_modal.entrypoint` (`python -m runner_modal.entrypoint`); was `runner_modal.job`.
- `Runner.Job.wait()` matches unbounded `Sandbox.wait` (no `timeout=` / `JobTimeoutError` path).
- `DeliveryStore.claim` / `bind` replace `try_claim` / `bind_object` / `bind_object_durable`.
- `Runner.admits` removed; use `admit_reason` (returns `None` when admitted).
- `Runner.objects.create` requires non-empty `repositories` (`owner/repo`), same as `Runner.create`.
- `Runner.Job.create` rejects jobs that fail admission (repo allowlist + labels).

### Added

- `Runner.objects.delete` also removes `{name}-runner-deliveries`.
- Stateful delivery claim/bind/release tests (in-memory Dict).

### Changed

- Bind failure: release claim only after confirmed `terminate`; otherwise leave claim and return 503.
- Stale delivery reclaim overwrites in place (no delete-then-put).
- Maintainer CI App lives at `scripts/ci_app.py` (not under `examples/`).
- Docs: install/Image bake, SECURITY Advisories URL, LICENSE copyright, last-deploy-wins meta.

## 0.1.0

### Breaking
Expand All @@ -33,13 +10,30 @@
- `cpu` / `memory` / `gpu` on `Runner.create` are Job Sandbox defaults (not control-plane resources).
- Default `min_containers=0`, `buffer_containers=1`, `cache=False`.
- Images install runtime deps from PyPI and bake local `runner_modal` (`add_local_python_source`).
- `Runner.Job.create` takes `github_secret=`. Server/webhook mounts only `webhook_secret`; Jobs mount only `github_secret`.
- `Runner.Job.create` takes `github_secret=`. Webhook mounts only `webhook_secret`; Jobs mount only `github_secret`.
- Module `api` / `server` → `webhook`. Removed `StageClock` / `runner_modal.profile`.
- Job Sandbox entrypoint module is `runner_modal.entrypoint` (`python -m runner_modal.entrypoint`).
- `Runner.Job.wait()` matches unbounded `Sandbox.wait` (no `timeout=`).
- `DeliveryStore.claim` / `bind` replace earlier claim/bind helpers.
- `admit_reason` is the admission API (`None` when admitted).
- `Runner.objects.create` requires non-empty `repositories` (`owner/repo`).
- `Runner.Job.create` rejects jobs that fail admission.
- Removed unused public `JobTimeoutError`.

### Added

- Delivery claim/bind before accept; webhook outcome headers and `/health`.
- `Runner.admit_reason` for fail-loud admission.
- `Runner.objects.delete` also removes `{name}-runner-deliveries`.
- Consumer kits: `examples/basic/`, `examples/gpu/`.
- GitHub Release workflow on `v*` tags (source dist; not PyPI).
- `SECURITY.md`, `CONTRIBUTING.md`
- Stateful delivery claim/bind/release tests.

### Changed

- Bind failure: release claim only after confirmed `terminate`; otherwise leave claim and return 503.
- Stale delivery reclaim overwrites in place (no delete-then-put).
- Maintainer CI App lives at `scripts/ci_app.py` (not under `examples/`).
- Install pin is `git+…@v0.1.0` (`PACKAGE_SPEC`).
- CI and Release workflows run on GitHub-hosted `ubuntu-latest` (not self-hosted dogfood runners).
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ uv run ruff format --check src/runner_modal tests examples
uv run ty check
```

## CI

GitHub Actions runs on ``ubuntu-latest``. Run the setup commands above locally before opening a PR. The maintainer control plane (``scripts/ci_app.py``) is for dogfooding this product on real Jobs, not for repository CI.

## Conventions

See [AGENTS.md](AGENTS.md): entity-based public DX (`Runner` / `Runner.Job`), no free public helpers, explicit kwargs / named Secrets (no ambient credential `getenv` in primitives), claim-before-create, soft capacity.
Expand Down
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@ You deploy your own Modal App and Secrets. This is not a managed multi-tenant se

### 1. Install

Until a PyPI release, install from GitHub (prefer a tag once one exists):

```bash
uv add git+https://github.com/modal-projects/runner-modal.git@main
# after first release: …runner-modal.git@v0.1.0
uv add git+https://github.com/modal-projects/runner-modal.git@v0.1.0
```

Deploy from a checkout (or editable install) so Modal Image builds can
Expand Down
Binary file removed docs/concurrency.png
Binary file not shown.
Binary file removed docs/idle-cost.png
Binary file not shown.
Binary file removed docs/queue-budget.png
Binary file not shown.
Binary file removed docs/time-to-ready.png
Binary file not shown.
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ build-backend = "uv_build"

[dependency-groups]
dev = [
"matplotlib>=3.9",
"pytest>=8.0",
"ruff>=0.15.21",
"ty>=0.0.62",
Expand Down
197 changes: 0 additions & 197 deletions scripts/charts.py

This file was deleted.

2 changes: 0 additions & 2 deletions src/runner_modal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from runner_modal.exceptions import (
AuthError,
ConcurrencyLimitError,
JobTimeoutError,
RunnerError,
)
from runner_modal.meta import PACKAGE_VERSION
Expand All @@ -16,7 +15,6 @@
__all__ = [
"AuthError",
"ConcurrencyLimitError",
"JobTimeoutError",
"Runner",
"RunnerError",
"RunnerInfo",
Expand Down
6 changes: 1 addition & 5 deletions src/runner_modal/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Public exception types.

Soft absence → ``None`` (e.g. ``runner.url``, ignored webhook).
Failures → raise (auth, timeout, concurrency, or ``ValueError`` / ``LookupError``).
Failures → raise (auth, concurrency, or ``ValueError`` / ``LookupError``).
"""

from __future__ import annotations
Expand All @@ -17,7 +17,3 @@ class AuthError(RunnerError):

class ConcurrencyLimitError(RunnerError):
pass


class JobTimeoutError(RunnerError):
pass
2 changes: 1 addition & 1 deletion src/runner_modal/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
# Documented install pin for operators (`uv add …`). Image builds install
# runtime deps from PyPI and bake local ``runner_modal`` via
# ``add_local_python_source`` until a PyPI release.
PACKAGE_SPEC = "git+https://github.com/modal-projects/runner-modal.git@main"
PACKAGE_SPEC = "git+https://github.com/modal-projects/runner-modal.git@v0.1.0"
CONTROL_PLANE_DEPS = (
"fastapi>=0.115.0",
"httpx>=0.28.0",
Expand Down
9 changes: 2 additions & 7 deletions tests/unit/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,11 @@

import pytest

from runner_modal.exceptions import (
AuthError,
ConcurrencyLimitError,
JobTimeoutError,
RunnerError,
)
from runner_modal.exceptions import AuthError, ConcurrencyLimitError, RunnerError


def test_product_errors_are_runner_errors() -> None:
for exc in (AuthError, ConcurrencyLimitError, JobTimeoutError):
for exc in (AuthError, ConcurrencyLimitError):
assert issubclass(exc, RunnerError)
with pytest.raises(RunnerError):
raise exc("boom")
Loading