flowli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flowli-0.1.0/LICENSE +21 -0
- flowli-0.1.0/PKG-INFO +207 -0
- flowli-0.1.0/README.md +157 -0
- flowli-0.1.0/pyproject.toml +101 -0
- flowli-0.1.0/setup.cfg +4 -0
- flowli-0.1.0/src/flowli/__init__.py +3 -0
- flowli-0.1.0/src/flowli/adapters/__init__.py +1 -0
- flowli-0.1.0/src/flowli/adapters/cairndb.py +832 -0
- flowli-0.1.0/src/flowli/adapters/cairndb_projection.py +766 -0
- flowli-0.1.0/src/flowli/adapters/evidence.py +76 -0
- flowli-0.1.0/src/flowli/adapters/memory.py +554 -0
- flowli-0.1.0/src/flowli/api/__init__.py +27 -0
- flowli-0.1.0/src/flowli/api/app.py +72 -0
- flowli-0.1.0/src/flowli/api/auth.py +223 -0
- flowli-0.1.0/src/flowli/api/catalog.py +124 -0
- flowli-0.1.0/src/flowli/api/deps.py +101 -0
- flowli-0.1.0/src/flowli/api/dto.py +236 -0
- flowli-0.1.0/src/flowli/api/frames.py +137 -0
- flowli-0.1.0/src/flowli/api/problems.py +87 -0
- flowli-0.1.0/src/flowli/api/reads.py +78 -0
- flowli-0.1.0/src/flowli/api/routes/__init__.py +17 -0
- flowli-0.1.0/src/flowli/api/routes/catalog.py +31 -0
- flowli-0.1.0/src/flowli/api/routes/evidence.py +125 -0
- flowli-0.1.0/src/flowli/api/routes/executions.py +280 -0
- flowli-0.1.0/src/flowli/api/routes/queues.py +60 -0
- flowli-0.1.0/src/flowli/api/routes/reviews.py +80 -0
- flowli-0.1.0/src/flowli/api/routes/worker.py +198 -0
- flowli-0.1.0/src/flowli/cli.py +595 -0
- flowli-0.1.0/src/flowli/cli_render.py +178 -0
- flowli-0.1.0/src/flowli/codec.py +89 -0
- flowli-0.1.0/src/flowli/domain/__init__.py +142 -0
- flowli-0.1.0/src/flowli/domain/channels.py +23 -0
- flowli-0.1.0/src/flowli/domain/errors.py +69 -0
- flowli-0.1.0/src/flowli/domain/execution.py +61 -0
- flowli-0.1.0/src/flowli/domain/frames.py +150 -0
- flowli-0.1.0/src/flowli/domain/journal.py +292 -0
- flowli-0.1.0/src/flowli/domain/names.py +55 -0
- flowli-0.1.0/src/flowli/domain/ports.py +310 -0
- flowli-0.1.0/src/flowli/domain/provenance.py +84 -0
- flowli-0.1.0/src/flowli/domain/tasks.py +83 -0
- flowli-0.1.0/src/flowli/domain/timers.py +30 -0
- flowli-0.1.0/src/flowli/evidence.py +181 -0
- flowli-0.1.0/src/flowli/log.py +89 -0
- flowli-0.1.0/src/flowli/patterns/__init__.py +18 -0
- flowli-0.1.0/src/flowli/patterns/delegate.py +72 -0
- flowli-0.1.0/src/flowli/patterns/fanout.py +25 -0
- flowli-0.1.0/src/flowli/patterns/review.py +112 -0
- flowli-0.1.0/src/flowli/patterns/saga.py +39 -0
- flowli-0.1.0/src/flowli/patterns/schedule.py +32 -0
- flowli-0.1.0/src/flowli/py.typed +0 -0
- flowli-0.1.0/src/flowli/runtime/__init__.py +56 -0
- flowli-0.1.0/src/flowli/runtime/consumer.py +351 -0
- flowli-0.1.0/src/flowli/runtime/context.py +714 -0
- flowli-0.1.0/src/flowli/runtime/engine.py +412 -0
- flowli-0.1.0/src/flowli/runtime/registry.py +48 -0
- flowli-0.1.0/src/flowli/runtime/retention.py +119 -0
- flowli-0.1.0/src/flowli/runtime/sweeper.py +250 -0
- flowli-0.1.0/src/flowli/runtime/worker.py +430 -0
- flowli-0.1.0/src/flowli.egg-info/PKG-INFO +207 -0
- flowli-0.1.0/src/flowli.egg-info/SOURCES.txt +62 -0
- flowli-0.1.0/src/flowli.egg-info/dependency_links.txt +1 -0
- flowli-0.1.0/src/flowli.egg-info/entry_points.txt +2 -0
- flowli-0.1.0/src/flowli.egg-info/requires.txt +35 -0
- flowli-0.1.0/src/flowli.egg-info/top_level.txt +1 -0
flowli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thomas Zamojski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
flowli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flowli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Workflow engine: a journaled coroutine execution stack on CairnDB
|
|
5
|
+
Author: Thomas Zamojski
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Documentation, https://quadratic-labs.github.io/flowli/
|
|
8
|
+
Project-URL: Repository, https://github.com/Quadratic-Labs/flowli
|
|
9
|
+
Project-URL: Changelog, https://github.com/Quadratic-Labs/flowli/blob/main/CHANGELOG.md
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Python: >=3.14
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: cairndb<0.5,>=0.4.1
|
|
19
|
+
Requires-Dist: cattrs>=24.1.0
|
|
20
|
+
Requires-Dist: structlog>=24.1.0
|
|
21
|
+
Provides-Extra: cli
|
|
22
|
+
Requires-Dist: typer>=0.9.0; extra == "cli"
|
|
23
|
+
Requires-Dist: rich>=13.8.0; extra == "cli"
|
|
24
|
+
Requires-Dist: uvicorn>=0.52.4; extra == "cli"
|
|
25
|
+
Provides-Extra: api
|
|
26
|
+
Requires-Dist: fastapi>=0.115.0; extra == "api"
|
|
27
|
+
Requires-Dist: pydantic>=2.7.0; extra == "api"
|
|
28
|
+
Requires-Dist: pyjwt[crypto]>=2.9.0; extra == "api"
|
|
29
|
+
Requires-Dist: uvicorn>=0.52.4; extra == "api"
|
|
30
|
+
Provides-Extra: docs
|
|
31
|
+
Requires-Dist: sphinx>=8.1.0; extra == "docs"
|
|
32
|
+
Requires-Dist: myst-parser>=4.0.0; extra == "docs"
|
|
33
|
+
Requires-Dist: furo>=2024.8.6; extra == "docs"
|
|
34
|
+
Requires-Dist: sphinx-design>=0.6.0; extra == "docs"
|
|
35
|
+
Requires-Dist: sphinx-copybutton>=0.5.2; extra == "docs"
|
|
36
|
+
Requires-Dist: sphinxcontrib-mermaid>=1.0.0; extra == "docs"
|
|
37
|
+
Requires-Dist: sphinx-autobuild>=2024.10.3; extra == "docs"
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
41
|
+
Requires-Dist: hypothesis>=6.98.0; extra == "dev"
|
|
42
|
+
Requires-Dist: ruff>=0.2.0; extra == "dev"
|
|
43
|
+
Requires-Dist: mypy>=1.8.0; extra == "dev"
|
|
44
|
+
Requires-Dist: fastapi>=0.115.0; extra == "dev"
|
|
45
|
+
Requires-Dist: httpx>=0.27.0; extra == "dev"
|
|
46
|
+
Requires-Dist: pyjwt[crypto]>=2.9.0; extra == "dev"
|
|
47
|
+
Requires-Dist: asgi-lifespan>=2.1.0; extra == "dev"
|
|
48
|
+
Requires-Dist: uvicorn>=0.52.4; extra == "dev"
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# flowli
|
|
52
|
+
|
|
53
|
+
A workflow engine that runs a workflow as a coroutine execution stack and
|
|
54
|
+
journals every frame to CairnDB.
|
|
55
|
+
|
|
56
|
+
**Documentation: <https://quadratic-labs.github.io/flowli/>** — the quickstart,
|
|
57
|
+
the concepts, the architecture, every setting, the hosting guide, and the API
|
|
58
|
+
reference from the docstrings. The specifications stay in `specs/`.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
uv sync --extra docs
|
|
62
|
+
uv run sphinx-build -b html -W docs docs/_build/html # or: make -C docs livehtml
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Layout:
|
|
66
|
+
|
|
67
|
+
- `src/flowli/domain/` — pure domain objects and ports. No I/O, no CairnDB import.
|
|
68
|
+
- `src/flowli/adapters/` — port implementations (CairnDB, in-memory).
|
|
69
|
+
- `src/flowli/api/` — the HTTP service (the `api` extra).
|
|
70
|
+
- `web/` — the operator interface over that service.
|
|
71
|
+
- `runner/` — `flowli-runner`: coding agents as delegate consumers.
|
|
72
|
+
- `codeflow/` — `flowli-codeflow`: the controller over the runner.
|
|
73
|
+
|
|
74
|
+
## Running the jobs
|
|
75
|
+
|
|
76
|
+
Install with the CLI extra, then point the CLI at your workflow registry:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install "flowli[cli]"
|
|
80
|
+
export CAIRNDB_STORAGE_TYPE=s3 CAIRNDB_S3_BUCKET=my-bucket # or --storage-path ./bucket
|
|
81
|
+
|
|
82
|
+
flowli worker --app myapp.flows:registry --queue default --queue finance
|
|
83
|
+
flowli sweeper --app myapp.flows:registry --interval 60
|
|
84
|
+
flowli retention --app myapp.flows:registry --delay-days 30 --once
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`--app module:attr` names a `Registry`, an `Engine`, or a zero-argument callable
|
|
88
|
+
returning one. With a `Registry` the CLI builds the CairnDB backend from the
|
|
89
|
+
`CAIRNDB_*` variables. `--once` runs one pass and exits, which suits a cron job.
|
|
90
|
+
`--projection PATH` makes the sweeper and retention read statuses from the SQLite
|
|
91
|
+
projection instead of folding the control log. Every job stops cleanly on
|
|
92
|
+
SIGINT or SIGTERM.
|
|
93
|
+
|
|
94
|
+
Operator commands share the same `--app` and storage options:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
flowli status EID --journal
|
|
98
|
+
flowli signal EID payments '{"amount": 100}' --by bank@example.com
|
|
99
|
+
flowli cancel EID --by ops@example.com
|
|
100
|
+
flowli migrate EID 2 --by ops@example.com
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`--by` names the actor recorded in provenance: an email, or `kind:id` with kind
|
|
104
|
+
`human`, `system`, `schedule` or `worker`. It defaults to the local user, or to
|
|
105
|
+
`FLOWLI_BY`. Commands exit with code 1 on an unknown execution or a refused
|
|
106
|
+
action, such as cancelling a finished execution.
|
|
107
|
+
|
|
108
|
+
## What the commands print
|
|
109
|
+
|
|
110
|
+
`status` answers the two questions an operator arrives with — where is it, and
|
|
111
|
+
what happened last:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
eid 01a093cc-265e-730a-b05c-e7e36d377452
|
|
115
|
+
status completed
|
|
116
|
+
workflow greet v1
|
|
117
|
+
queue default
|
|
118
|
+
created 2026-09-12T04:07:05.054046Z by human:thomas@example.com
|
|
119
|
+
last execution.completed at 2026-09-12T04:07:05.128988Z by worker:w-1 on host-3 epoch 1
|
|
120
|
+
payload
|
|
121
|
+
{
|
|
122
|
+
"value": "hi zoe"
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
A suspended execution names what it waits for instead of a payload:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
status suspended
|
|
130
|
+
last execution.suspended at 2026-09-12T04:07:05.193632Z by worker:w-1 on host-3 epoch 1
|
|
131
|
+
waiting channel:01a093cc-26cd-7373-8944-38c18b531442.go
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`--journal` appends the whole log, one event per line:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
seq type fid at actor
|
|
138
|
+
1000000 execution.started root 2026-09-12T04:07:05.128988Z worker:w-1
|
|
139
|
+
2000000 frame.started root/greet#0 2026-09-12T04:07:05.139587Z worker:w-1
|
|
140
|
+
3000000 frame.completed root/greet#0 2026-09-12T04:07:05.139587Z worker:w-1
|
|
141
|
+
4000000 execution.completed root 2026-09-12T04:07:05.128988Z worker:w-1
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Every `--once` job answers with one line of counters, which suits a cron job
|
|
145
|
+
that mails its output:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
flowli sweeper --once # timers_fired=1 recovered=0 restarted=0 repaired=0 waits_cleared=0
|
|
149
|
+
flowli retention --once # archived=1 cleaned=0, then one line per archived execution
|
|
150
|
+
flowli-runner --once # recovered=0 processed=1, then one line per reattached task
|
|
151
|
+
flowli-codeflow merge --once # merged=1
|
|
152
|
+
flowli-codeflow board --once # reconciled=3
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Colour
|
|
156
|
+
|
|
157
|
+
On a terminal the output is coloured: the status by what it means (green
|
|
158
|
+
completed, red failed, yellow suspended), workflow names in cyan, timestamps
|
|
159
|
+
and actor kinds dimmed so the identity stands out, and JSON payloads syntax
|
|
160
|
+
highlighted. The theme paints with the terminal's own sixteen colours, so it
|
|
161
|
+
suits a light background as well as a dark one.
|
|
162
|
+
|
|
163
|
+
**Piped output is plain text, byte for byte.** Redirect the output, capture it
|
|
164
|
+
in a test, or run it under `cron`, and you get exactly the columns above with
|
|
165
|
+
no escape sequences — so `grep`, `awk` and `cut` keep working. Set `NO_COLOR`
|
|
166
|
+
to drop the colour on a terminal too.
|
|
167
|
+
|
|
168
|
+
## The HTTP service and the interface
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
pip install "flowli[api]"
|
|
172
|
+
uv run python web/dev_server.py # an engine, a worker and the service
|
|
173
|
+
cd web && npm install && npm run dev # the operator interface
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`flowli.api.create_app(engine, projection=..., authenticator=...)` is the
|
|
177
|
+
service: the catalog and the control plane (`specs/09-http-api.md`,
|
|
178
|
+
sections 7 and 8), the worker plane for consumers that cannot reach the bucket
|
|
179
|
+
(section 9), and evidence (section 10). It holds no state of its own: reads
|
|
180
|
+
come from the projection, writes go through the engine, and the actor of every
|
|
181
|
+
write comes from the access token.
|
|
182
|
+
|
|
183
|
+
`web/` is the interface over it. `web/dev_server.py` runs a demo backend for
|
|
184
|
+
it. See `web/README.md`.
|
|
185
|
+
|
|
186
|
+
## Logging
|
|
187
|
+
|
|
188
|
+
Every component emits structured events through structlog, the library CairnDB
|
|
189
|
+
uses, so one configuration produces one stream:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from flowli.log import configure_logging
|
|
193
|
+
configure_logging("INFO", "json") # or "console"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
All three CLIs — `flowli`, `flowli-runner` and `flowli-codeflow` — take
|
|
197
|
+
`--log-level` and `--log-format console|json`; `flowli` also reads
|
|
198
|
+
`FLOWLI_LOG_LEVEL` and `FLOWLI_LOG_FORMAT`. Logs go to stderr, so a job's
|
|
199
|
+
report on stdout stays pipeable on its own. Events are named with snake_case
|
|
200
|
+
nouns such as `execution_started`, `execution_suspended`, `frame_failed`,
|
|
201
|
+
`execution_recovered` and `execution_archived`. While a worker holds a task,
|
|
202
|
+
`worker_id`, `task_id`, `eid` and `epoch` are bound to every event it emits, and
|
|
203
|
+
live frames add `fid` and `attempt`. Frame events are at DEBUG level.
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
MIT. See [LICENSE](https://github.com/Quadratic-Labs/flowli/blob/main/LICENSE).
|
flowli-0.1.0/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# flowli
|
|
2
|
+
|
|
3
|
+
A workflow engine that runs a workflow as a coroutine execution stack and
|
|
4
|
+
journals every frame to CairnDB.
|
|
5
|
+
|
|
6
|
+
**Documentation: <https://quadratic-labs.github.io/flowli/>** — the quickstart,
|
|
7
|
+
the concepts, the architecture, every setting, the hosting guide, and the API
|
|
8
|
+
reference from the docstrings. The specifications stay in `specs/`.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
uv sync --extra docs
|
|
12
|
+
uv run sphinx-build -b html -W docs docs/_build/html # or: make -C docs livehtml
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Layout:
|
|
16
|
+
|
|
17
|
+
- `src/flowli/domain/` — pure domain objects and ports. No I/O, no CairnDB import.
|
|
18
|
+
- `src/flowli/adapters/` — port implementations (CairnDB, in-memory).
|
|
19
|
+
- `src/flowli/api/` — the HTTP service (the `api` extra).
|
|
20
|
+
- `web/` — the operator interface over that service.
|
|
21
|
+
- `runner/` — `flowli-runner`: coding agents as delegate consumers.
|
|
22
|
+
- `codeflow/` — `flowli-codeflow`: the controller over the runner.
|
|
23
|
+
|
|
24
|
+
## Running the jobs
|
|
25
|
+
|
|
26
|
+
Install with the CLI extra, then point the CLI at your workflow registry:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install "flowli[cli]"
|
|
30
|
+
export CAIRNDB_STORAGE_TYPE=s3 CAIRNDB_S3_BUCKET=my-bucket # or --storage-path ./bucket
|
|
31
|
+
|
|
32
|
+
flowli worker --app myapp.flows:registry --queue default --queue finance
|
|
33
|
+
flowli sweeper --app myapp.flows:registry --interval 60
|
|
34
|
+
flowli retention --app myapp.flows:registry --delay-days 30 --once
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`--app module:attr` names a `Registry`, an `Engine`, or a zero-argument callable
|
|
38
|
+
returning one. With a `Registry` the CLI builds the CairnDB backend from the
|
|
39
|
+
`CAIRNDB_*` variables. `--once` runs one pass and exits, which suits a cron job.
|
|
40
|
+
`--projection PATH` makes the sweeper and retention read statuses from the SQLite
|
|
41
|
+
projection instead of folding the control log. Every job stops cleanly on
|
|
42
|
+
SIGINT or SIGTERM.
|
|
43
|
+
|
|
44
|
+
Operator commands share the same `--app` and storage options:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
flowli status EID --journal
|
|
48
|
+
flowli signal EID payments '{"amount": 100}' --by bank@example.com
|
|
49
|
+
flowli cancel EID --by ops@example.com
|
|
50
|
+
flowli migrate EID 2 --by ops@example.com
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`--by` names the actor recorded in provenance: an email, or `kind:id` with kind
|
|
54
|
+
`human`, `system`, `schedule` or `worker`. It defaults to the local user, or to
|
|
55
|
+
`FLOWLI_BY`. Commands exit with code 1 on an unknown execution or a refused
|
|
56
|
+
action, such as cancelling a finished execution.
|
|
57
|
+
|
|
58
|
+
## What the commands print
|
|
59
|
+
|
|
60
|
+
`status` answers the two questions an operator arrives with — where is it, and
|
|
61
|
+
what happened last:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
eid 01a093cc-265e-730a-b05c-e7e36d377452
|
|
65
|
+
status completed
|
|
66
|
+
workflow greet v1
|
|
67
|
+
queue default
|
|
68
|
+
created 2026-09-12T04:07:05.054046Z by human:thomas@example.com
|
|
69
|
+
last execution.completed at 2026-09-12T04:07:05.128988Z by worker:w-1 on host-3 epoch 1
|
|
70
|
+
payload
|
|
71
|
+
{
|
|
72
|
+
"value": "hi zoe"
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
A suspended execution names what it waits for instead of a payload:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
status suspended
|
|
80
|
+
last execution.suspended at 2026-09-12T04:07:05.193632Z by worker:w-1 on host-3 epoch 1
|
|
81
|
+
waiting channel:01a093cc-26cd-7373-8944-38c18b531442.go
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`--journal` appends the whole log, one event per line:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
seq type fid at actor
|
|
88
|
+
1000000 execution.started root 2026-09-12T04:07:05.128988Z worker:w-1
|
|
89
|
+
2000000 frame.started root/greet#0 2026-09-12T04:07:05.139587Z worker:w-1
|
|
90
|
+
3000000 frame.completed root/greet#0 2026-09-12T04:07:05.139587Z worker:w-1
|
|
91
|
+
4000000 execution.completed root 2026-09-12T04:07:05.128988Z worker:w-1
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Every `--once` job answers with one line of counters, which suits a cron job
|
|
95
|
+
that mails its output:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
flowli sweeper --once # timers_fired=1 recovered=0 restarted=0 repaired=0 waits_cleared=0
|
|
99
|
+
flowli retention --once # archived=1 cleaned=0, then one line per archived execution
|
|
100
|
+
flowli-runner --once # recovered=0 processed=1, then one line per reattached task
|
|
101
|
+
flowli-codeflow merge --once # merged=1
|
|
102
|
+
flowli-codeflow board --once # reconciled=3
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Colour
|
|
106
|
+
|
|
107
|
+
On a terminal the output is coloured: the status by what it means (green
|
|
108
|
+
completed, red failed, yellow suspended), workflow names in cyan, timestamps
|
|
109
|
+
and actor kinds dimmed so the identity stands out, and JSON payloads syntax
|
|
110
|
+
highlighted. The theme paints with the terminal's own sixteen colours, so it
|
|
111
|
+
suits a light background as well as a dark one.
|
|
112
|
+
|
|
113
|
+
**Piped output is plain text, byte for byte.** Redirect the output, capture it
|
|
114
|
+
in a test, or run it under `cron`, and you get exactly the columns above with
|
|
115
|
+
no escape sequences — so `grep`, `awk` and `cut` keep working. Set `NO_COLOR`
|
|
116
|
+
to drop the colour on a terminal too.
|
|
117
|
+
|
|
118
|
+
## The HTTP service and the interface
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pip install "flowli[api]"
|
|
122
|
+
uv run python web/dev_server.py # an engine, a worker and the service
|
|
123
|
+
cd web && npm install && npm run dev # the operator interface
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`flowli.api.create_app(engine, projection=..., authenticator=...)` is the
|
|
127
|
+
service: the catalog and the control plane (`specs/09-http-api.md`,
|
|
128
|
+
sections 7 and 8), the worker plane for consumers that cannot reach the bucket
|
|
129
|
+
(section 9), and evidence (section 10). It holds no state of its own: reads
|
|
130
|
+
come from the projection, writes go through the engine, and the actor of every
|
|
131
|
+
write comes from the access token.
|
|
132
|
+
|
|
133
|
+
`web/` is the interface over it. `web/dev_server.py` runs a demo backend for
|
|
134
|
+
it. See `web/README.md`.
|
|
135
|
+
|
|
136
|
+
## Logging
|
|
137
|
+
|
|
138
|
+
Every component emits structured events through structlog, the library CairnDB
|
|
139
|
+
uses, so one configuration produces one stream:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from flowli.log import configure_logging
|
|
143
|
+
configure_logging("INFO", "json") # or "console"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
All three CLIs — `flowli`, `flowli-runner` and `flowli-codeflow` — take
|
|
147
|
+
`--log-level` and `--log-format console|json`; `flowli` also reads
|
|
148
|
+
`FLOWLI_LOG_LEVEL` and `FLOWLI_LOG_FORMAT`. Logs go to stderr, so a job's
|
|
149
|
+
report on stdout stays pipeable on its own. Events are named with snake_case
|
|
150
|
+
nouns such as `execution_started`, `execution_suspended`, `frame_failed`,
|
|
151
|
+
`execution_recovered` and `execution_archived`. While a worker holds a task,
|
|
152
|
+
`worker_id`, `task_id`, `eid` and `epoch` are bound to every event it emits, and
|
|
153
|
+
live frames add `fid` and `attempt`. Frame events are at DEBUG level.
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT. See [LICENSE](https://github.com/Quadratic-Labs/flowli/blob/main/LICENSE).
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0"] # >=77: SPDX license expressions (PEP 639)
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "flowli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Workflow engine: a journaled coroutine execution stack on CairnDB"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.14"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{name = "Thomas Zamojski"}]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.14",
|
|
19
|
+
"Typing :: Typed",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"cairndb>=0.4.1,<0.5",
|
|
23
|
+
"cattrs>=24.1.0",
|
|
24
|
+
"structlog>=24.1.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Documentation = "https://quadratic-labs.github.io/flowli/"
|
|
29
|
+
Repository = "https://github.com/Quadratic-Labs/flowli"
|
|
30
|
+
Changelog = "https://github.com/Quadratic-Labs/flowli/blob/main/CHANGELOG.md"
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
cli = ["typer>=0.9.0", "rich>=13.8.0", "uvicorn>=0.52.4"]
|
|
34
|
+
api = [
|
|
35
|
+
"fastapi>=0.115.0",
|
|
36
|
+
"pydantic>=2.7.0",
|
|
37
|
+
"pyjwt[crypto]>=2.9.0",
|
|
38
|
+
"uvicorn>=0.52.4",
|
|
39
|
+
]
|
|
40
|
+
docs = [
|
|
41
|
+
"sphinx>=8.1.0",
|
|
42
|
+
"myst-parser>=4.0.0",
|
|
43
|
+
"furo>=2024.8.6",
|
|
44
|
+
"sphinx-design>=0.6.0",
|
|
45
|
+
"sphinx-copybutton>=0.5.2",
|
|
46
|
+
"sphinxcontrib-mermaid>=1.0.0",
|
|
47
|
+
"sphinx-autobuild>=2024.10.3",
|
|
48
|
+
]
|
|
49
|
+
dev = [
|
|
50
|
+
"pytest>=8.0.0",
|
|
51
|
+
"pytest-asyncio>=0.23.0",
|
|
52
|
+
"hypothesis>=6.98.0",
|
|
53
|
+
"ruff>=0.2.0",
|
|
54
|
+
"mypy>=1.8.0",
|
|
55
|
+
"fastapi>=0.115.0",
|
|
56
|
+
"httpx>=0.27.0",
|
|
57
|
+
"pyjwt[crypto]>=2.9.0",
|
|
58
|
+
"asgi-lifespan>=2.1.0",
|
|
59
|
+
"uvicorn>=0.52.4",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[project.scripts]
|
|
63
|
+
flowli = "flowli.cli:app"
|
|
64
|
+
|
|
65
|
+
[tool.setuptools.packages.find]
|
|
66
|
+
where = ["src"]
|
|
67
|
+
|
|
68
|
+
[tool.setuptools.package-data]
|
|
69
|
+
"flowli" = ["py.typed"]
|
|
70
|
+
|
|
71
|
+
[tool.pytest.ini_options]
|
|
72
|
+
asyncio_mode = "auto"
|
|
73
|
+
testpaths = ["tests"]
|
|
74
|
+
|
|
75
|
+
[tool.ruff]
|
|
76
|
+
line-length = 100
|
|
77
|
+
target-version = "py314"
|
|
78
|
+
|
|
79
|
+
[tool.ruff.format]
|
|
80
|
+
# Code blocks in the docs and specs are aligned by hand.
|
|
81
|
+
exclude = ["*.md"]
|
|
82
|
+
|
|
83
|
+
[tool.ruff.lint]
|
|
84
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
85
|
+
|
|
86
|
+
[tool.ruff.lint.per-file-ignores]
|
|
87
|
+
"docs/conf.py" = ["E501"] # the inline GitHub icon
|
|
88
|
+
|
|
89
|
+
[tool.mypy]
|
|
90
|
+
strict = true
|
|
91
|
+
python_version = "3.14"
|
|
92
|
+
|
|
93
|
+
[[tool.mypy.overrides]]
|
|
94
|
+
module = ["msgpack"]
|
|
95
|
+
ignore_missing_imports = true
|
|
96
|
+
follow_untyped_imports = true
|
|
97
|
+
|
|
98
|
+
[dependency-groups]
|
|
99
|
+
dev = [
|
|
100
|
+
"mutmut>=3.8.0",
|
|
101
|
+
]
|
flowli-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Port implementations. `memory` for tests, `cairndb` for production."""
|