# faust-streaming: the maintained fork of Faust for Python stream processing

> faust-streaming is a fork of Robinhood's Faust, a Python port of the Kafka Streams ideas, taken over by a new maintainer group to restore a steady release cadence and to move onto current Kafka drivers. This looks at what the fork changed, how tables and the changelog work, and where it stops.

**faust-streaming/faust** — Python Stream Processing. A Faust fork

- Repository: https://github.com/faust-streaming/faust
- Website: https://faust-streaming.github.io/faust/
- Stars: 1,890 · Forks: 206
- Language: Python
- License: NOASSERTION
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/faust-streaming-faust

## The fork exists because the release line stopped

The readme of this repository opens with a sentence that says everything about the project: Python Stream Processing. A Faust fork. The description in the package metadata is the same.

The reason for the fork is stated plainly. The maintainers decided to fork the original Faust project because there is a critical process of releasing new versions which causes uncertainty in the community. The list of what they want from the fork is short and specific: ensure continuous release, improve code quality, use the latest versions of the Kafka drivers, and for now only aiokafka, support Kafka transactions, and update the documentation.

Kafka transactions are worth dwelling on. Kafka transactions are on the fork's list of goals, and a stream processor that maintains a changelog and local state usually wants the offset commit and the state write to be atomic. A fork is a heavy way to get that, and the fact that a fork was necessary is itself a signal about the original project's pace.

The origin story is still on the readme. Faust is used at Robinhood to build high performance distributed systems and real-time data pipelines that process billions of events every day, and the original authors are Ask Solem Hoel and Vineet Goel. The fork's maintainers are Vikram Patki and William Barnhart, and the readme says that everybody is welcome to contribute and that you can be added as a maintainer.

The package on PyPI is `faust-streaming`, and the documentation lives at faust-streaming.github.io/faust.

## Agents and records, with no DSL in the way

Faust is a stream processing library that ports the ideas of Kafka Streams to Python, and the readme insists on one point: it does not use a DSL, it is just Python. The practical consequence is that you can use your favourite Python libraries while stream processing, and the readme names NumPy, PyTorch, Pandas, NLTK, Django, Flask and SQLAlchemy as examples.

The programming model has three pieces. A topic is a stream you read from or write to. A record is a model that describes how keys and values are serialized, using modern Python syntax. And an agent is the decorator that defines a stream processor, essentially something that consumes from a Kafka topic and does something for every event it receives.

```python
app = faust.App('myapp', broker='kafka://localhost')

# Models describe how messages are serialized:
# {"account_id": "3fae-...", amount: 3}
class Order(faust.Record):
    account_id: str
    amount: int

@app.agent(value_type=Order)
async def order(orders):
    async for order in orders:
        # process infinite stream of orders.
        print(f'Order for {order.account_id}: {order.amount}')
```

Because the agent is an `async def` function, it can perform other operations asynchronously while it processes, such as web requests. That is the point of the asyncio base: the agent is not a callback handler, it is a coroutine that can await other I/O without blocking the event loop.

Static typing is part of the design rather than an afterthought, since Faust is statically typed using the mypy type checker. The `faust.Record` subclass above is a normal class with annotations, and the serializer derives the wire format from it, so a schema change is a type change and mypy sees it.

## Tables are dictionaries backed by RocksDB and a changelog topic

This system can persist state, acting like a database. Tables are named distributed key/value stores that you can use as regular Python dictionaries, and that is the feature that makes Faust more than a consumer loop.

The storage has three parts. Tables are stored locally on each machine using a super fast embedded database written in C++, called RocksDB. For reliability, the library uses a Kafka topic as a write-ahead-log: whenever a key is changed, the change is published to the changelog. Standby nodes consume from that changelog to keep an exact replica of the data, which enables instant recovery should any of the nodes fail.

From the user's side, a table is just a dictionary, but the data is persisted between restarts and replicated across nodes, so on failover other nodes can take over automatically. That is the whole point of the design: state survives both a crash and a node loss without the application writing a persistence layer.

Tables can also store aggregate counts that are optionally windowed, so you can keep track of the number of clicks from the last day or the last hour. Like Kafka Streams, the implementation supports tumbling, hopping and sliding windows of time, and old windows can be expired to stop data from filling up. The windowing model is the part where a Python implementation has to match the semantics people expect from the JVM, and the readme presents it as a first class feature rather than an approximation.

## Partitioned keys decide which worker sees which event

The counting example in the readme shows how the pieces fit together and, more importantly, how partitioning constrains what you can compute.

```python
# data sent to 'clicks' topic sharded by URL key.
# e.g. key="http://example.com" value="1"
click_topic = app.topic('clicks', key_type=str, value_type=int)

# default value for missing URL will be 0 with `default=int`
counts = app.Table('click_counts', default=int)

@app.agent(click_topic)
async def count_click(clicks):
    async for url, count in clicks.items():
        counts[url] += count
```

The data sent to the clicks topic is partitioned, which means the clicks are sharded by URL so that every count for the same URL is delivered to the same Faust worker instance. That is the mechanism that makes `counts[url] += count` correct rather than racy: the key determines the partition, the partition determines the worker, and the table is a local store on that worker.

The trade is the usual one. Keying by URL means all events for a URL serialise through one worker, so a hot key becomes a bottleneck, and the windowing and table features are only as correct as the key distribution. A team porting a pipeline from Kafka Streams will recognise the constraint immediately, because it is the same constraint in the same place.

Faust supports any type of stream data, bytes, Unicode and serialized structures, and the readme also shows a `Record` subclass with a default value for a field, which is how a schema with an optional field is expressed.

## Installing faust-streaming and running a worker

Installation is a single pip command, and the package name differs from the import name, which is worth noting before you write it into a requirements file:

```bash
pip install faust-streaming
```

The package registers a console script called `faust`, mapped in the manifest to `faust.cli.faust:cli`, so the worker is started with `faust` rather than with `python -m faust`.

For local development, the readme gives a three step sequence: clone the project, create a virtual environment and activate it, then install the requirements with the script in the repository's `scripts/` directory. The virtual environment step is spelled out:

```bash
python3.7 -m venv venv && source venv/bin/activate
```

That command is worth pausing on, because it predates the current package metadata. The manifest now declares `requires-python = ">=3.10"` and the setup script raises an error for anything below 3.10, while the readme prose still says Faust requires Python 3.6 or later for the new `async/await` syntax. The classifiers list Python 3.10 through 3.15, and the setup script has a matching hard check. So the readme text on the Python version is stale, and the executable truth is in the manifest and the setup script.

The same inconsistency shows up in the local development instruction, which names Python 3.7 in the virtual environment command. If you follow the readme literally on a modern machine, you will hit the version guard in setup.py before you get anywhere.

## Where faust-streaming is the wrong tool

The boundaries are structural, and they come from the Kafka centric design.

The first is the broker. The examples use `kafka://localhost`, the fork's stated goal mentions the latest versions of the Kafka drivers with aiokafka as the only one for now, and the topic list in the repository's topic labels names Kafka and Redis. There is no equivalent of the Kafka Streams abstraction over multiple brokers, so a team wanting a broker agnostic stream layer is looking at a different tool.

The second is state. Tables require RocksDB locally, which is a compiled C++ dependency, and the setup script carries a bundle list that includes `rocksdb`, `rocksdict`, `aerospike`, `redis`, `uvloop`, `orjson` and `cython`, with an opt-in Cython path. A deployment that cannot run a native extension, or a serverless environment without a local disk, does not get the table feature, and the readme does not describe a pure-Python fallback for local state.

The third is the operational model. Faust is a set of long-running worker processes that consume from topics and maintain state, and it needs a Kafka cluster to be useful. A team that wants to run a stream processor as a library inside an existing async service is using a framework designed around standalone workers.

The fourth is the fork risk itself, and it deserves a straight statement. A fork exists because the upstream project stopped releasing, which means the fork's own continuity depends on the new maintainers. The readme asks for contributors and offers maintainer roles, the package classifiers say Development Status:: 5 - Production/Stable, and the release history shows v0.15.1, v0.15.2 and v0.15.3 published within five days in August 2026, which is a good sign. But the project is pre-1.0, the readme still carries text from the original project including the old banner asset, and the version scheme continues the original numbering, so a team that depends on it should pin versions and expect to read the changelog.

## BSD terms, a busy release line, and a large example set

The licence situation is the usual fork arrangement. The package metadata carries the classifier `License :: OSI Approved :: BSD License` and declares the licence as a file, while the repository's own metadata lists it as a custom licence that GitHub cannot classify, with a pointer to the `LICENSE` file at the root. The readme does not restate the licence terms. The practical step is to read that file, since a fork can carry its own terms even when the classifier says BSD.

The releases tell a healthier story than the licence ambiguity. v0.15.1 was published on 2026-08-19, v0.15.2 on 2026-08-20, and v0.15.3 on 2026-08-23, and the last push to the repository was on 2026-10-14. Three releases in five days is exactly the continuous release the fork set out to restore, and the recent tags show patch-level work rather than a long silence.

The repository is a full project rather than a port of a small library. There is a `faust/` package directory, `tests/`, `docs/`, `examples/`, `scripts/`, `requirements/`, and a `Makefile` whose help target lists the development tasks, including the documentation build, a live documentation server, a develop target that installs the commit hooks, a C extension variant, and a distcheck target. `setup.py` carries the extension configuration, and `tox.ini` is present for multi-version testing.

The `examples/` directory is the best guide to what the library actually does day to day, and the file names are more informative than the readme: `hello_world.py`, `word_count.py`, `withdrawals.py`, `windowing/`, `windowed_aggregation.py`, `bound_agent.py`, `concurrency.py`, `crontab/`, `leader.py`, `livecheck.py`, `tabletest.py`, `tableofset.py`, `task_queue.py`, `fastapi_example.py`, `django/`, `kubernetes/` and `advanced/`. That list is the feature set: agents, tables, windowing, timers, scheduling, health checks and the usual web framework integrations.

## Conclusion

faust-streaming fits a Python team that wants Kafka Streams style stream processing without a DSL, that already runs Kafka, and that wants a project with an active release line and current drivers behind it. It does not fit a team that needs multi broker support today, since the fork lists aiokafka as its only Kafka driver for now, and it is the wrong choice if your pipeline is not Kafka based or if you want a Java or JVM option. Before you adopt it, check the `requires-python` value in `pyproject.toml`, which is 3.10 or newer even though the readme text still says Python 3.6, and plan for RocksDB as the local state store.

## FAQ

### How do I install faust-streaming?

Run `pip install faust-streaming`. The package registers a `faust` console script, and the manifest requires Python 3.10 or newer, even though the readme prose still mentions Python 3.6 and shows python3.7 in the local development instructions.

### Why was Faust forked into faust-streaming?

The maintainers say the original project had a critical process of releasing new versions which caused uncertainty in the community. Their goals are continuous release, code quality, the latest Kafka drivers with aiokafka for now, support for Kafka transactions, and updated documentation.

### How does Faust keep table state across a node failure?

Tables are stored locally on each machine using RocksDB, and for reliability a Kafka topic is used as a write-ahead-log. Whenever a key is changed the change is published to the changelog, and standby nodes consume from it to keep an exact replica, which enables instant recovery if a node fails.

### What is an agent in Faust?

An agent is the decorator that defines a stream processor. It consumes from a Kafka topic and does something for every event it receives, and because the agent is an `async def` function it can perform other operations asynchronously, such as web requests, while it processes the stream.

### Does faust-streaming support windowed aggregations?

Yes. Tables can store aggregate counts that are optionally windowed, for example the number of clicks in the last hour or the last day. Like Kafka Streams, it supports tumbling, hopping and sliding windows of time, and old windows can be expired to stop data from filling up.

## Sources

- [faust-streaming/faust on GitHub](https://github.com/faust-streaming/faust)
- [Issues](https://github.com/faust-streaming/faust/issues)
- [Project website](https://faust-streaming.github.io/faust/)
- [README](https://github.com/faust-streaming/faust/blob/master/README.md)
- [Releases](https://github.com/faust-streaming/faust/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/faust-streaming-faust
