You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add `@offwork.task` to a function. offwork captures its source, all dependencies, and all imports automatically. Workers reconstruct and execute everything from scratch — no shared filesystem, no deployment pipeline. Missing packages are installed on the fly.
9
+
**Run any Python function on a remote worker with just two lines of code.**
10
+
11
+
Put `.connect()` somewhere at the start of your script, add `@offwork.task` to your function, that's it.
12
+
You can now run it remotely — no shared codebase, no deployment pipeline.
13
+
14
+
`offwork` captures its entire dependency graph (helpers, imports, closures, constants) and ships it to the worker as a self-contained payload. The worker doesn't need to have any prior knowledge of your code.
12
15
13
16
## Quick start
14
17
15
18
```bash
16
19
pip install offwork
20
+
offwork worker --backend local://localhost:9748 --tmp # start a worker in a temp venv
17
21
```
18
22
19
23
```python
20
24
import asyncio, math, offwork
21
-
import offwork
22
25
23
26
offwork.connect("local://localhost:9748")
24
27
25
-
defadd(a, b):
28
+
defadd(a: float, b: float) -> float:
26
29
return a + b
27
30
28
-
@offwork.task
31
+
@offwork.task# only the entry point needs this - add() is captured automatically
29
32
defhypotenuse(a: float, b: float) -> float:
30
33
return math.sqrt(add(a**2, b**2))
31
34
@@ -35,36 +38,37 @@ async def main():
35
38
asyncio.run(main())
36
39
```
37
40
38
-
Only the entry point needs `@offwork.task` — everything it calls is captured automatically.
41
+
`.run()` serializes the function graph, submits it to the worker, and returns the result. The worker reconstructs source, installs any missing packages, and executes.
42
+
43
+
### Multi-machine
44
+
45
+
Swap `local://` for Redis or RabbitMQ to run on a remote worker:
39
46
40
47
```bash
41
-
offwork worker --backend local://localhost:9748 --tmp # start a worker
Copy file name to clipboardExpand all lines: docs/AGENTS.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# offwork — Context for Coding Assistants
2
2
3
-
This file is a compact, technical orientation for AI coding assistants. For user-facing docs see [README.md](../README.md), [docs/QUICK_START.md](QUICK_START.md), [docs/TECHNICAL_OVERVIEW.md](TECHNICAL_OVERVIEW.md), [docs/SIGNING.md](SIGNING.md), [docs/SANDBOX.md](SANDBOX.md).
3
+
This file is a compact, technical orientation for AI coding assistants. For user-facing docs see [README.md](../README.md), [docs/FEATURES.md](FEATURES.md), [docs/TECHNICAL_OVERVIEW.md](TECHNICAL_OVERVIEW.md), [docs/SIGNING.md](SIGNING.md), [docs/SANDBOX.md](SANDBOX.md).
offwork itself has zero runtime dependencies. Backend extras are only needed when you actually use the corresponding URL scheme.
13
+
offwork has zero required runtime dependencies. Backend extras are only needed when you use the corresponding URL scheme.
12
14
13
15
## Remote execution
14
16
15
-
Add `@offwork.task` to the entry point. Everything it calls is captured automatically.
17
+
Add `@offwork.task` to the entry point. Everything it calls is captured automatically: helper functions, imports, constants, closures — the full dependency tree, resolved by AST analysis.
`--tmp` runs the worker in an isolated venv, cleaned up on exit. For multi-machine, swap `local://` for `redis://`.
42
+
`--tmp` runs the worker in an isolated venv, cleaned up on exit. For multi-machine, swap `local://` for `redis://` or `amqp://` and point to the broker's address.
41
43
42
44
## Async API
43
45
@@ -219,8 +221,22 @@ offwork run examples/remote_execution.py # Terminal 2
219
221
220
222
`offwork run` creates a temporary venv, auto-detects dependencies, installs them, and runs the script.
221
223
224
+
## What gets captured automatically
225
+
226
+
`@offwork.task` only needs to be on the **entry point**. offwork captures everything else automatically:
227
+
228
+
-**Helper functions** — any function called from the traced function, recursively
229
+
-**Classes** — constructors, methods, base classes, class attributes, decorators
230
+
-**Imports** — only what the function actually uses
231
+
-**Module-level constants** — referenced variables like `MAX_RETRIES = 5`
232
+
-**Closures** — variables captured from enclosing scopes (via `repr()`, pickle, or dependency edges)
233
+
-**Third-party packages** — detected and auto-installed on the worker
234
+
235
+
Not captured: standard library and third-party packages (kept as imports).
236
+
222
237
## Next steps
223
238
224
239
-**[Technical Overview](TECHNICAL_OVERVIEW.md)** — Architecture, serialization format, internals
225
240
-**[Sandbox](SANDBOX.md)** — Docker container isolation setup and management
Copy file name to clipboardExpand all lines: docs/TECHNICAL_OVERVIEW.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# Technical Overview
2
2
3
-
This document covers offwork's internal architecture, execution flow, serialization format, and transport backends. For a usage-oriented guide, see the [Quick Start](QUICK_START.md).
3
+
This document covers offwork's internal architecture, execution flow, serialization format, and transport backends. For a usage-oriented guide, see the [Features](FEATURES.md).
offwork run examples/<script>.py # Terminal 2 — run the example
8
+
```
9
+
10
+
`offwork run` creates a temporary venv, auto-detects and installs dependencies, and runs the script. No global installs required.
11
+
12
+
---
13
+
14
+
## [remote_execution.py](remote_execution.py)
15
+
16
+
The baseline: one decorator on an entry-point function, plain helpers around it. Demonstrates that offwork captures the full call graph without any extra annotations.
17
+
18
+
```python
19
+
defadd(a, b): ...# plain helper — no @offwork.task
20
+
defmultiply(a, b): ...# another helper
21
+
22
+
@offwork.task
23
+
defdot_product(u, v):
24
+
returnsum(multiply(a, b) for a, b inzip(u, v)) # both helpers are captured
Workers auto-install missing packages before execution. Also shows `worker_only_import` — packages that exist only on the worker, resolved to lightweight stubs on the client:
42
+
43
+
```python
44
+
from offwork import worker_only_import, install_package_as
45
+
46
+
with worker_only_import(): # client never installs this
47
+
import requests
48
+
49
+
with install_package_as("PyYAML"): # pip name differs from import name
50
+
import yaml
51
+
```
52
+
53
+
## [progress_reporting.py](progress_reporting.py)
54
+
55
+
Real-time progress from a long-running task. `offwork.progress()` is a no-op when called outside a worker:
56
+
57
+
```python
58
+
@offwork.task
59
+
defprocess(n: int) -> int:
60
+
for i inrange(n):
61
+
offwork.progress(i +1, n, message=f"step {i+1}/{n}")
62
+
...
63
+
64
+
future =await process.start(100)
65
+
whilenotawait future.done():
66
+
p =await future.progress()
67
+
print(f"{p.percent:.0f}%")
68
+
```
69
+
70
+
## [cancellation.py](cancellation.py)
71
+
72
+
Cooperative task cancellation. The client cancels a pending or in-flight task; awaiting it raises `TaskCancelled`:
73
+
74
+
```python
75
+
future =await slow_task.start()
76
+
await asyncio.sleep(1)
77
+
await future.cancel()
78
+
79
+
try:
80
+
await future
81
+
except offwork.TaskCancelled:
82
+
print("cancelled")
83
+
```
84
+
85
+
## [scheduling.py](scheduling.py)
86
+
87
+
Three scheduling modes — delayed, point-in-time, and recurring:
88
+
89
+
```python
90
+
await func.run_in(timedelta(seconds=5), *args) # after a delay
91
+
await func.run_at(datetime(2026, 6, 1, 9, 0), *args) # at a specific time
Rate-limiting and fault tolerance in the decorator:
101
+
102
+
```python
103
+
@offwork.task(throttle=timedelta(minutes=30)) # at most once per 30 min
104
+
defrate_limited(): ...
105
+
106
+
@offwork.task(retries=3, timeout=10) # up to 3 retries, 10s each
107
+
defflaky(): ...
108
+
```
109
+
110
+
Calling a throttled task during the cooldown raises `ThrottleError` immediately.
111
+
112
+
## [large_module.py](large_module.py)
113
+
114
+
Stress test: a module with 47 functions across 7 files, 3 classes, and deep dependency chains. Only the entry point is decorated — offwork discovers everything else automatically. Useful for verifying auto-discovery at scale.
Fan-out ETL pattern: split a large CSV into chunks, process each chunk on a worker, merge results. Each task is pure (bytes in, dict out). Demonstrates `.map()` for batch submission:
124
+
125
+
```python
126
+
chunks = [(0, 500), (500, 1000), (1000, 1500)]
127
+
results =await summarize_chunk.map([(data, start, end) for start, end in chunks])
128
+
merged = merge(results)
129
+
```
130
+
131
+
## [pdf_report.py](pdf_report.py)
132
+
133
+
FastAPI endpoint that offloads PDF rendering to a worker. The web process stays lightweight; heavy CPU work (`reportlab`) runs on the worker pool. Shows the typical "offload CPU work from a web handler" pattern:
134
+
135
+
```python
136
+
@offwork.task
137
+
defrender_report(data: dict) -> bytes: # returns PDF bytes
Stateful poller (IMAP connection, seen-UID tracking) stays local; per-attachment analysis is offloaded. Illustrates the pattern of keeping stateful I/O local while farming out pure compute:
Recurring backup job via `.run_every()`. The task is small; it delegates to three plain helpers (`_archive`, `_compress`, `_upload`) that offwork discovers automatically:
0 commit comments