Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9d7f4bf
Setup tables for streams
joefreeman Apr 18, 2026
d43bcf4
Setup streams module
joefreeman Apr 18, 2026
ab58549
Close streams when execution completes
joefreeman Apr 18, 2026
326328b
Update wire protocol
joefreeman Apr 18, 2026
aba392e
Implement generator detector and driver
joefreeman Apr 18, 2026
24ddfc1
Setup stream consumers
joefreeman Apr 18, 2026
6afcba2
Update topic, and update epoch generation
joefreeman Apr 18, 2026
e87ab2f
Add serialiser for stream
joefreeman Apr 18, 2026
b340d49
Fix typing
joefreeman Apr 18, 2026
f2b3f4d
Add tests
joefreeman Apr 18, 2026
5d8d86c
Various fixes
joefreeman Apr 19, 2026
9870be5
Add stream topic
joefreeman Apr 19, 2026
90d5b6f
Tidy stream IDs/terminology
joefreeman Apr 19, 2026
589612b
Support configuring buffer/backpressure
joefreeman Apr 19, 2026
4932cb3
Use completions to determine execution state
joefreeman Apr 19, 2026
aecdb71
Consolidate stream filters into a computed stride
joefreeman Apr 19, 2026
ab62a2f
Support configuring timeouts on streams
joefreeman Apr 20, 2026
f870911
Tidy imports
joefreeman Apr 20, 2026
d0611ed
Fix replay after producer terminated
joefreeman Apr 20, 2026
a77d391
Handle stream error/timeout as dedicated completion kinds
joefreeman Apr 20, 2026
ef18a4a
Capture full stream error
joefreeman Apr 20, 2026
71ef39b
Fix resolving result for execution with stream error
joefreeman Apr 20, 2026
982d75c
Dispatch worker requests async
joefreeman Apr 21, 2026
9dd66db
Don't use memoised execution with errored/timed-out stream
joefreeman Apr 21, 2026
d007aa0
Track stream dependencies
joefreeman Apr 22, 2026
95a5221
Merge branch 'main' into streaming
joefreeman May 17, 2026
aba6c91
Fix issues
joefreeman Jun 19, 2026
96e58b4
Acknowledge stream consumption
joefreeman Aug 29, 2026
c41135a
Release abandoned stream subscriptions
joefreeman Aug 29, 2026
20031f9
Have worker error if workflows can't be loaded
joefreeman Aug 30, 2026
4c69a64
Support async stream iterators
joefreeman Aug 30, 2026
d94f782
Update workflow topic to expose run statuses
joefreeman Aug 30, 2026
37cc013
Format/lint
joefreeman Aug 30, 2026
883c7ab
More format/lint
joefreeman Aug 30, 2026
4477e27
Fix test
joefreeman Aug 30, 2026
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
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ jobs:
uvx ruff format --check .
uvx ruff check .

- name: Run adapter tests
working-directory: adapters/python
run: uv run --with pytest pytest tests/

- name: Check version consistency
shell: python3 {0}
run: |
Expand Down
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,17 @@ A **workflow** is the entry point for a run. It can call **tasks**, which are th
```python
import coflux as cf


@cf.task(retries=cf.Retries(3, when=ConnectionError))
def fetch_data(url: str) -> dict:
return requests.get(url).json()


@cf.task(cache=True)
def transform(data: dict) -> list:
return sorted(data["items"], key=lambda x: x["score"], reverse=True)


@cf.workflow()
def my_pipeline(url: str):
return transform(fetch_data(url))
Expand All @@ -56,10 +59,12 @@ Reuse results across runs, with TTLs, parameter filtering, and cross-workspace c
@cf.task(cache=True)
def get_user(user_id): ...


# cache for 10 minutes
@cf.task(cache=600)
def fetch_prices(): ...


# cache by specific params only
@cf.task(cache=cf.Cache(params=["product_id"]))
def get_product(product_id, include_reviews=False): ...
Expand All @@ -71,8 +76,7 @@ Retry on specific exceptions with configurable backoff:

```python
@cf.task(retries=cf.Retries(5, backoff=(1, 60), when=TransientError))
def call_api():
...
def call_api(): ...
```

### Parallel execution
Expand All @@ -82,9 +86,9 @@ Submit tasks concurrently and collect results:
```python
@cf.workflow()
def process_order(user_id, product_id):
user = load_user.submit(user_id) # starts immediately
product = load_product.submit(product_id) # starts immediately
create_order(user.result(), product.result()) # waits for results before calling
user = load_user.submit(user_id) # starts immediately
product = load_product.submit(product_id) # starts immediately
create_order(user.result(), product.result()) # waits for results before calling
```

### Assets
Expand All @@ -97,6 +101,7 @@ def generate_report() -> cf.Asset:
Path("report.csv").write_text(build_csv())
return cf.asset(match="*.csv")


@cf.workflow()
def my_workflow():
report = generate_report()
Expand All @@ -112,6 +117,7 @@ Memoise task calls within a single run (unlike caching, which works across runs)
def send_email(recipient):
mailer.send(recipient.email, ...)


@cf.workflow()
def notify(campaign_id):
for r in get_recipients(campaign_id):
Expand All @@ -125,6 +131,7 @@ Record numeric values from tasks and visualise them in Studio:
```python
loss = cf.Metric("loss", group="training")


@cf.task()
def train(epochs):
for epoch in range(epochs):
Expand Down Expand Up @@ -191,10 +198,12 @@ Or [run it with Docker](https://docs.coflux.com/getting_started/server).

import coflux as cf


@cf.task()
def greet(name: str) -> str:
return f"Hello, {name}!"


@cf.workflow()
def hello(name: str):
print(greet(name))
Expand Down
2 changes: 2 additions & 0 deletions adapters/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ poetry install
```python
from coflux import task, workflow, log_info


@task()
def process_item(item_id: int) -> dict:
log_info("Processing item {id}", id=item_id)
return {"id": item_id, "status": "done"}


@workflow()
def batch_process(items: list[int]) -> list[dict]:
results = []
Expand Down
28 changes: 24 additions & 4 deletions adapters/python/coflux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,31 @@
from .errors import (
ExecutionAbandoned,
ExecutionCancelled,
ExecutionCrashed,
ExecutionError,
ExecutionTerminated,
ExecutionTimeout,
InputDismissed,
StreamSuperseded,
)
from .metric import Metric, MetricGroup, MetricScale, progress
from .models import Asset, AssetEntry, AssetMetadata, Execution, Input, ModelSchema
from .models import (
Asset,
AssetEntry,
AssetMetadata,
AsyncStreamIterator,
Execution,
Input,
Stream,
StreamIterator,
)
from .prompt import Prompt
from .state import get_context
from .target import Cache, Defer, Retries
from .streams import stream
from .target import Cache, Defer, Retries, Streams

__all__ = [
# Grouped by category rather than sorted alphabetically.
__all__ = [ # noqa: RUF022
# Version
"__version__",
# Decorators
Expand All @@ -41,19 +54,26 @@
"ExecutionCancelled",
"ExecutionTimeout",
"ExecutionAbandoned",
"ExecutionCrashed",
"StreamSuperseded",
"InputDismissed",
"Input",
"ModelSchema",
"Metric",
"MetricGroup",
"MetricScale",
"Prompt",
"Cache",
"Defer",
"Retries",
"Streams",
"Asset",
"AssetEntry",
"AssetMetadata",
"Stream",
"StreamIterator",
"AsyncStreamIterator",
# Producer-side stream helper
"stream",
# Context functions
"group",
"suspense",
Expand Down
3 changes: 2 additions & 1 deletion adapters/python/coflux/_version.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Package version, isolated to avoid circular imports."""

from importlib.metadata import PackageNotFoundError, version as _pkg_version
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as _pkg_version

try:
__version__ = _pkg_version("coflux")
Expand Down
Loading
Loading