flowlit 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.
Files changed (132) hide show
  1. flowlit-0.1.0/.gitignore +12 -0
  2. flowlit-0.1.0/LICENSE +21 -0
  3. flowlit-0.1.0/PKG-INFO +198 -0
  4. flowlit-0.1.0/README.md +168 -0
  5. flowlit-0.1.0/doc/api-reference.md +357 -0
  6. flowlit-0.1.0/doc/architecture.md +256 -0
  7. flowlit-0.1.0/doc/concurrency.md +276 -0
  8. flowlit-0.1.0/doc/configuration.md +104 -0
  9. flowlit-0.1.0/doc/examples.md +99 -0
  10. flowlit-0.1.0/doc/executors.md +396 -0
  11. flowlit-0.1.0/doc/getting-started.md +140 -0
  12. flowlit-0.1.0/doc/observability.md +160 -0
  13. flowlit-0.1.0/doc/plan-format.md +573 -0
  14. flowlit-0.1.0/doc/testing.md +110 -0
  15. flowlit-0.1.0/examples/hello_workflow.yaml +26 -0
  16. flowlit-0.1.0/examples/http_shell_noop_pipeline.yaml +33 -0
  17. flowlit-0.1.0/examples/optional_step_plan.yaml +46 -0
  18. flowlit-0.1.0/examples/sleep_sequential_workflow.yaml +34 -0
  19. flowlit-0.1.0/examples/sleep_workflow.yaml +34 -0
  20. flowlit-0.1.0/examples/with_bash_plan.yaml +49 -0
  21. flowlit-0.1.0/examples/with_http_plan.yaml +29 -0
  22. flowlit-0.1.0/pyproject.toml +73 -0
  23. flowlit-0.1.0/src/flowlit/__init__.py +30 -0
  24. flowlit-0.1.0/src/flowlit/application/__init__.py +7 -0
  25. flowlit-0.1.0/src/flowlit/application/dto.py +100 -0
  26. flowlit-0.1.0/src/flowlit/application/errors.py +119 -0
  27. flowlit-0.1.0/src/flowlit/application/events.py +201 -0
  28. flowlit-0.1.0/src/flowlit/application/executor_dispatcher.py +112 -0
  29. flowlit-0.1.0/src/flowlit/application/executor_registry.py +26 -0
  30. flowlit-0.1.0/src/flowlit/application/orchestrator.py +393 -0
  31. flowlit-0.1.0/src/flowlit/application/ports/__init__.py +6 -0
  32. flowlit-0.1.0/src/flowlit/application/ports/event_bus.py +31 -0
  33. flowlit-0.1.0/src/flowlit/application/ports/execution_coordinator.py +45 -0
  34. flowlit-0.1.0/src/flowlit/application/ports/executor.py +153 -0
  35. flowlit-0.1.0/src/flowlit/application/ports/plan_parser.py +28 -0
  36. flowlit-0.1.0/src/flowlit/application/ports/workflow_repository.py +24 -0
  37. flowlit-0.1.0/src/flowlit/application/workflow_service.py +342 -0
  38. flowlit-0.1.0/src/flowlit/domain/__init__.py +7 -0
  39. flowlit-0.1.0/src/flowlit/domain/dag.py +99 -0
  40. flowlit-0.1.0/src/flowlit/domain/errors.py +245 -0
  41. flowlit-0.1.0/src/flowlit/domain/spec_resolution.py +232 -0
  42. flowlit-0.1.0/src/flowlit/domain/step.py +333 -0
  43. flowlit-0.1.0/src/flowlit/domain/workflow.py +746 -0
  44. flowlit-0.1.0/src/flowlit/infrastructure/__init__.py +7 -0
  45. flowlit-0.1.0/src/flowlit/infrastructure/event_bus/__init__.py +1 -0
  46. flowlit-0.1.0/src/flowlit/infrastructure/event_bus/asyncio_event_bus.py +72 -0
  47. flowlit-0.1.0/src/flowlit/infrastructure/execution/__init__.py +3 -0
  48. flowlit-0.1.0/src/flowlit/infrastructure/execution/asyncio_execution_coordinator.py +140 -0
  49. flowlit-0.1.0/src/flowlit/infrastructure/executors/__init__.py +11 -0
  50. flowlit-0.1.0/src/flowlit/infrastructure/executors/base_executor.py +102 -0
  51. flowlit-0.1.0/src/flowlit/infrastructure/executors/http_executor.py +191 -0
  52. flowlit-0.1.0/src/flowlit/infrastructure/executors/noop_executor.py +40 -0
  53. flowlit-0.1.0/src/flowlit/infrastructure/executors/shell_executor.py +133 -0
  54. flowlit-0.1.0/src/flowlit/infrastructure/executors/sleep_executor.py +56 -0
  55. flowlit-0.1.0/src/flowlit/infrastructure/repositories/__init__.py +1 -0
  56. flowlit-0.1.0/src/flowlit/infrastructure/repositories/in_memory_repository.py +36 -0
  57. flowlit-0.1.0/src/flowlit/infrastructure/yaml_loader/__init__.py +1 -0
  58. flowlit-0.1.0/src/flowlit/infrastructure/yaml_loader/loader.py +103 -0
  59. flowlit-0.1.0/src/flowlit/infrastructure/yaml_loader/schema.py +61 -0
  60. flowlit-0.1.0/src/flowlit/interfaces/__init__.py +6 -0
  61. flowlit-0.1.0/src/flowlit/interfaces/cli/__init__.py +1 -0
  62. flowlit-0.1.0/src/flowlit/interfaces/cli/main.py +170 -0
  63. flowlit-0.1.0/src/flowlit/interfaces/composition_root.py +92 -0
  64. flowlit-0.1.0/src/flowlit/plugins.py +91 -0
  65. flowlit-0.1.0/src/flowlit/py.typed +0 -0
  66. flowlit-0.1.0/tests/__init__.py +0 -0
  67. flowlit-0.1.0/tests/fixtures/concurrent_sleep_plan.yaml +14 -0
  68. flowlit-0.1.0/tests/fixtures/custom_executor_plan.yaml +8 -0
  69. flowlit-0.1.0/tests/fixtures/cyclic_plan.yaml +12 -0
  70. flowlit-0.1.0/tests/fixtures/else_cascade_taint_plan.yaml +25 -0
  71. flowlit-0.1.0/tests/fixtures/else_fallback_plan.yaml +22 -0
  72. flowlit-0.1.0/tests/fixtures/else_not_fired_plan.yaml +22 -0
  73. flowlit-0.1.0/tests/fixtures/four_independent_sleeps_plan.yaml +26 -0
  74. flowlit-0.1.0/tests/fixtures/generic_timeout_optional_plan.yaml +16 -0
  75. flowlit-0.1.0/tests/fixtures/generic_timeout_plan.yaml +11 -0
  76. flowlit-0.1.0/tests/fixtures/idempotency_key_plan.yaml +8 -0
  77. flowlit-0.1.0/tests/fixtures/invalid_spec_plan.yaml +10 -0
  78. flowlit-0.1.0/tests/fixtures/long_sleep_plan.yaml +8 -0
  79. flowlit-0.1.0/tests/fixtures/malformed_when_plan.yaml +15 -0
  80. flowlit-0.1.0/tests/fixtures/optional_blocking_plan.yaml +22 -0
  81. flowlit-0.1.0/tests/fixtures/optional_non_blocking_plan.yaml +16 -0
  82. flowlit-0.1.0/tests/fixtures/output_passing_plan.yaml +14 -0
  83. flowlit-0.1.0/tests/fixtures/per_edge_blocking_plan.yaml +26 -0
  84. flowlit-0.1.0/tests/fixtures/placeholder_resolves_to_invalid_spec_plan.yaml +20 -0
  85. flowlit-0.1.0/tests/fixtures/placeholder_typed_spec_plan.yaml +22 -0
  86. flowlit-0.1.0/tests/fixtures/required_failure_cancels_sibling_plan.yaml +14 -0
  87. flowlit-0.1.0/tests/fixtures/retry_plan.yaml +10 -0
  88. flowlit-0.1.0/tests/fixtures/sample_plan.yaml +20 -0
  89. flowlit-0.1.0/tests/fixtures/silent_optional_failure_plan.yaml +10 -0
  90. flowlit-0.1.0/tests/fixtures/singleton_plan.yaml +10 -0
  91. flowlit-0.1.0/tests/fixtures/singleton_sleep_plan.yaml +10 -0
  92. flowlit-0.1.0/tests/fixtures/sleep_plan.yaml +8 -0
  93. flowlit-0.1.0/tests/fixtures/sleep_then_reference_plan.yaml +14 -0
  94. flowlit-0.1.0/tests/fixtures/status_condition_plan.yaml +23 -0
  95. flowlit-0.1.0/tests/fixtures/undeclared_output_reference_plan.yaml +17 -0
  96. flowlit-0.1.0/tests/fixtures/unknown_blocking_override_plan.yaml +18 -0
  97. flowlit-0.1.0/tests/fixtures/unknown_step_type_plan.yaml +14 -0
  98. flowlit-0.1.0/tests/fixtures/unresolvable_output_reference_plan.yaml +20 -0
  99. flowlit-0.1.0/tests/fixtures/unresolved_dep_plan.yaml +7 -0
  100. flowlit-0.1.0/tests/fixtures/when_false_non_blocking_plan.yaml +23 -0
  101. flowlit-0.1.0/tests/fixtures/when_false_plan.yaml +21 -0
  102. flowlit-0.1.0/tests/fixtures/when_not_plan.yaml +15 -0
  103. flowlit-0.1.0/tests/fixtures/when_references_optional_step_plan.yaml +16 -0
  104. flowlit-0.1.0/tests/fixtures/when_true_plan.yaml +15 -0
  105. flowlit-0.1.0/tests/fixtures/when_true_step_fails_plan.yaml +21 -0
  106. flowlit-0.1.0/tests/integration/__init__.py +0 -0
  107. flowlit-0.1.0/tests/integration/test_end_to_end.py +988 -0
  108. flowlit-0.1.0/tests/unit/__init__.py +0 -0
  109. flowlit-0.1.0/tests/unit/application/__init__.py +0 -0
  110. flowlit-0.1.0/tests/unit/application/test_executor_dispatcher.py +363 -0
  111. flowlit-0.1.0/tests/unit/application/test_executor_registry.py +31 -0
  112. flowlit-0.1.0/tests/unit/application/test_orchestrator.py +484 -0
  113. flowlit-0.1.0/tests/unit/application/test_workflow_service.py +196 -0
  114. flowlit-0.1.0/tests/unit/domain/__init__.py +0 -0
  115. flowlit-0.1.0/tests/unit/domain/test_dag.py +143 -0
  116. flowlit-0.1.0/tests/unit/domain/test_spec_resolution.py +257 -0
  117. flowlit-0.1.0/tests/unit/domain/test_step_state.py +213 -0
  118. flowlit-0.1.0/tests/unit/domain/test_workflow_state.py +1348 -0
  119. flowlit-0.1.0/tests/unit/infrastructure/__init__.py +0 -0
  120. flowlit-0.1.0/tests/unit/infrastructure/test_asyncio_event_bus.py +136 -0
  121. flowlit-0.1.0/tests/unit/infrastructure/test_asyncio_execution_coordinator.py +289 -0
  122. flowlit-0.1.0/tests/unit/infrastructure/test_base_executor.py +93 -0
  123. flowlit-0.1.0/tests/unit/infrastructure/test_http_executor.py +283 -0
  124. flowlit-0.1.0/tests/unit/infrastructure/test_in_memory_repository.py +75 -0
  125. flowlit-0.1.0/tests/unit/infrastructure/test_shell_executor.py +138 -0
  126. flowlit-0.1.0/tests/unit/infrastructure/test_sleep_executor.py +76 -0
  127. flowlit-0.1.0/tests/unit/infrastructure/test_yaml_loader.py +411 -0
  128. flowlit-0.1.0/tests/unit/interfaces/__init__.py +0 -0
  129. flowlit-0.1.0/tests/unit/interfaces/cli/__init__.py +0 -0
  130. flowlit-0.1.0/tests/unit/interfaces/cli/test_main.py +241 -0
  131. flowlit-0.1.0/tests/unit/interfaces/test_composition_root.py +36 -0
  132. flowlit-0.1.0/tests/unit/test_plugins.py +77 -0
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ venv/
6
+ .mypy_cache/
7
+ .ruff_cache/
8
+ .pytest_cache/
9
+ dist/
10
+ build/
11
+ .coverage
12
+ htmlcov/
flowlit-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shadiwazir
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.
flowlit-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.5
2
+ Name: flowlit
3
+ Version: 0.1.0
4
+ Summary: A lightweight, event-driven workflow execution engine.
5
+ Project-URL: Homepage, https://github.com/shadiwazir/flowlit
6
+ Project-URL: Repository, https://github.com/shadiwazir/flowlit
7
+ Project-URL: Issues, https://github.com/shadiwazir/flowlit/issues
8
+ Author: shadiwazir
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: aiohttp>=3.9
21
+ Requires-Dist: pydantic>=2.0
22
+ Requires-Dist: pyyaml>=6.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: mypy>=1.11; extra == 'dev'
25
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
26
+ Requires-Dist: pytest>=8.0; extra == 'dev'
27
+ Requires-Dist: ruff>=0.6; extra == 'dev'
28
+ Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Flowlit
32
+
33
+ A lightweight, event-driven workflow execution engine. Hand it a YAML
34
+ plan — a set of steps, their dependencies, and per-step instructions —
35
+ and Flowlit validates it as a DAG, runs it, and hands each step to
36
+ whichever **executor** is registered for that step's type, running every
37
+ step the DAG allows to run at once *genuinely concurrently*, not one at a
38
+ time.
39
+
40
+ ```bash
41
+ pip install -e ".[dev]"
42
+ flowlit examples/hello_workflow.yaml
43
+ ```
44
+
45
+ ```
46
+ -> greet (noop): submitted
47
+ step greet (noop): Hello from Flowlit!
48
+ <- greet: completed
49
+ -> build (noop): submitted
50
+ step build (noop): Pretending to build the project...
51
+ <- build: completed
52
+ -> test (noop): submitted
53
+ step test (noop): Pretending to run tests...
54
+ <- test: completed
55
+ -> report (noop): submitted
56
+ step report (noop): Build and test both finished.
57
+ <- report: completed
58
+ Workflow hello-workflow: completed
59
+ ```
60
+
61
+ ## Why Flowlit
62
+
63
+ - **The DAG alone decides concurrency.** Two steps that share nothing but
64
+ a parent run genuinely at once, as independent `asyncio.Task`s — not
65
+ serialized because they happen to share a step type, and not
66
+ artificially parallelized either. See
67
+ [Concurrency model](doc/concurrency.md).
68
+ - **Data flow is explicit and auditable.** A step reads a prior step's
69
+ output only via a small, closed
70
+ `${{ steps.<id>.output.<path> }}` placeholder syntax — no expressions,
71
+ no filters, nothing a semi-trusted plan author (an AI agent proposing a
72
+ workflow, say) could use to smuggle in arbitrary logic. See
73
+ [Plan format](doc/plan-format.md).
74
+ - **Failure handling is a first-class, per-step decision.** `optional`,
75
+ `blocking`, `silent_failures`, and per-edge `blocking_overrides` let
76
+ two different dependents of the same step disagree about whether
77
+ they're blocked by its failure — expressive enough for "best-effort
78
+ logging that should never stop a required deploy step." See
79
+ [Plan format](doc/plan-format.md#failure-handling-optional-blocking-silent_failures).
80
+ - **Branching without an expression language.** `when`/`when_not`/`else`
81
+ gate a step on a prior step's output or outcome, reusing the same
82
+ closed placeholder syntax — no new DSL to audit. A branch step is
83
+ implicitly optional, so picking one path over another can't itself
84
+ abort the workflow. See
85
+ [Branching](doc/plan-format.md#branching-when-when_not-else).
86
+ - **Clean, layered architecture, built to extend.** Four layers
87
+ (`domain` → `application` → `infrastructure` → `interfaces`), each only
88
+ depending on the ones below it, with ports (`Protocol`s) separating
89
+ contracts from implementations. Add a step type without forking the
90
+ repo via one call to `register_executor()`. See
91
+ [Architecture](doc/architecture.md).
92
+ - **Built for both a blocking CLI and a future async server.**
93
+ `run_to_completion()` blocks until done (right for a CLI); `start_workflow()`
94
+ + polling `get_status()` doesn't (right for an MCP server or any
95
+ poll-driven client) — same DAG, same engine, no separate code path. See
96
+ [API reference](doc/api-reference.md).
97
+
98
+ Everything runs in memory — nothing survives a process restart. That's a
99
+ deliberate v1 choice, not an oversight: the goal is to get the
100
+ architecture right first, with the `WorkflowRepository`/`EventBus` ports
101
+ already shaped so a persistent implementation can be dropped in later
102
+ without touching orchestration logic. The eventual goal is to run this as
103
+ an MCP server so agent tools (e.g. VS Code GitHub Copilot) can submit and
104
+ drive plans.
105
+
106
+ ## Key features
107
+
108
+ | | |
109
+ |---|---|
110
+ | **Real concurrency** | Independent steps run as genuinely parallel `asyncio.Task`s, driven purely by the dependency graph. |
111
+ | **Four built-in executors** | `noop`, `sleep`, `shell` (with timeout), `http` (with retry/backoff) — see [Executors](doc/executors.md). |
112
+ | **Typed, defaulted specs** | Any executor can declare a pydantic `spec_model` and get validation for free, at the earliest honest point. |
113
+ | **Pluggable executors** | `register_executor("my_type", MyExecutor())` — no fork required. |
114
+ | **Rich failure semantics** | Required vs. optional steps, blocking vs. non-blocking dependents (with per-edge overrides), silent vs. visible failures, cascading skips. |
115
+ | **Clean cancellation** | A required failure — or an explicit `cancel_workflow()` — stops every in-flight step promptly and deterministically. |
116
+ | **Output passing** | `${{ steps.<id>.output.<path> }}` placeholders, resolved just before dispatch. |
117
+ | **Two execution modes** | `run_to_completion()` (blocking) and `start_workflow()` + poll (non-blocking), from the exact same engine. |
118
+ | **JSON-ready DTOs** | `WorkflowService` returns primitives-only dataclasses — no domain leakage across the API boundary. |
119
+
120
+ ## Quickstart
121
+
122
+ ```bash
123
+ python -m venv .venv
124
+ source .venv/bin/activate # or `.venv\Scripts\activate` on Windows
125
+ pip install -e ".[dev]"
126
+
127
+ flowlit examples/hello_workflow.yaml
128
+ ```
129
+
130
+ ```bash
131
+ pytest # run the test suite
132
+ ruff check src tests # lint
133
+ mypy src # type-check
134
+ ```
135
+
136
+ See [Getting started](doc/getting-started.md) for a full walkthrough,
137
+ including using flowlit as a library and reading its CLI output.
138
+
139
+ ## Documentation
140
+
141
+ Detailed, modular guides live under [`doc/`](doc/):
142
+
143
+ | Guide | Covers |
144
+ |---|---|
145
+ | [Getting started](doc/getting-started.md) | Install, quickstart, CLI flags, using flowlit as a library. |
146
+ | [Plan format](doc/plan-format.md) | The YAML plan schema: steps, dependencies, output-passing placeholders, failure-handling fields. |
147
+ | [Architecture](doc/architecture.md) | The four-layer design, execution flow, event bus, composition root — with diagrams. |
148
+ | [Concurrency model](doc/concurrency.md) | How the DAG drives real concurrency, cancellation semantics, `max_concurrent_steps`. |
149
+ | [Executors](doc/executors.md) | The `noop`/`sleep`/`shell`/`http` executors, adding your own, registering one from outside the repo. |
150
+ | [API reference](doc/api-reference.md) | `WorkflowService`, ports, DTOs, events, and the full exception hierarchy. |
151
+ | [Configuration](doc/configuration.md) | CLI flags, `build_app_context()` parameters, per-step configuration. |
152
+ | [Observability](doc/observability.md) | Logging setup and live progress via the event bus. |
153
+ | [Testing](doc/testing.md) | Test suite layout and what each part covers. |
154
+ | [Examples](doc/examples.md) | A guided tour of every plan in [`examples/`](examples/). |
155
+
156
+ ## A minimal plan
157
+
158
+ ```yaml
159
+ id: build-and-deploy
160
+ name: Build and Deploy
161
+ steps:
162
+ - id: fetch_source
163
+ type: shell
164
+ spec:
165
+ command: "echo '{\"commit_sha\": \"abc123\"}'"
166
+ depends_on: []
167
+
168
+ - id: notify
169
+ type: noop
170
+ spec:
171
+ message: "deployed commit ${{ steps.fetch_source.output.commit_sha }}"
172
+ depends_on: [fetch_source]
173
+ ```
174
+
175
+ `type` selects the executor; `spec` is whatever that executor needs;
176
+ `depends_on` must form a DAG. See [Plan format](doc/plan-format.md) for
177
+ the complete schema, including `optional`/`blocking`/`silent_failures`
178
+ and `blocking_overrides`.
179
+
180
+ ## Project layout
181
+
182
+ ```
183
+ src/flowlit/
184
+ ├── domain/ Step/Workflow entities, DAG validation -- stdlib only
185
+ ├── application/ use cases, ports (Protocols), orchestration
186
+ ├── infrastructure/ YAML loading, in-memory event bus/repository, executors
187
+ ├── interfaces/ CLI today; a future MCP server; the composition root
188
+ └── plugins.py register_executor() -- the public extension seam
189
+ examples/ runnable YAML plans
190
+ tests/ unit + integration test suite
191
+ ```
192
+
193
+ See [Architecture](doc/architecture.md) for the reasoning behind this
194
+ layering.
195
+
196
+ ## License
197
+
198
+ MIT — see [`pyproject.toml`](pyproject.toml).
@@ -0,0 +1,168 @@
1
+ # Flowlit
2
+
3
+ A lightweight, event-driven workflow execution engine. Hand it a YAML
4
+ plan — a set of steps, their dependencies, and per-step instructions —
5
+ and Flowlit validates it as a DAG, runs it, and hands each step to
6
+ whichever **executor** is registered for that step's type, running every
7
+ step the DAG allows to run at once *genuinely concurrently*, not one at a
8
+ time.
9
+
10
+ ```bash
11
+ pip install -e ".[dev]"
12
+ flowlit examples/hello_workflow.yaml
13
+ ```
14
+
15
+ ```
16
+ -> greet (noop): submitted
17
+ step greet (noop): Hello from Flowlit!
18
+ <- greet: completed
19
+ -> build (noop): submitted
20
+ step build (noop): Pretending to build the project...
21
+ <- build: completed
22
+ -> test (noop): submitted
23
+ step test (noop): Pretending to run tests...
24
+ <- test: completed
25
+ -> report (noop): submitted
26
+ step report (noop): Build and test both finished.
27
+ <- report: completed
28
+ Workflow hello-workflow: completed
29
+ ```
30
+
31
+ ## Why Flowlit
32
+
33
+ - **The DAG alone decides concurrency.** Two steps that share nothing but
34
+ a parent run genuinely at once, as independent `asyncio.Task`s — not
35
+ serialized because they happen to share a step type, and not
36
+ artificially parallelized either. See
37
+ [Concurrency model](doc/concurrency.md).
38
+ - **Data flow is explicit and auditable.** A step reads a prior step's
39
+ output only via a small, closed
40
+ `${{ steps.<id>.output.<path> }}` placeholder syntax — no expressions,
41
+ no filters, nothing a semi-trusted plan author (an AI agent proposing a
42
+ workflow, say) could use to smuggle in arbitrary logic. See
43
+ [Plan format](doc/plan-format.md).
44
+ - **Failure handling is a first-class, per-step decision.** `optional`,
45
+ `blocking`, `silent_failures`, and per-edge `blocking_overrides` let
46
+ two different dependents of the same step disagree about whether
47
+ they're blocked by its failure — expressive enough for "best-effort
48
+ logging that should never stop a required deploy step." See
49
+ [Plan format](doc/plan-format.md#failure-handling-optional-blocking-silent_failures).
50
+ - **Branching without an expression language.** `when`/`when_not`/`else`
51
+ gate a step on a prior step's output or outcome, reusing the same
52
+ closed placeholder syntax — no new DSL to audit. A branch step is
53
+ implicitly optional, so picking one path over another can't itself
54
+ abort the workflow. See
55
+ [Branching](doc/plan-format.md#branching-when-when_not-else).
56
+ - **Clean, layered architecture, built to extend.** Four layers
57
+ (`domain` → `application` → `infrastructure` → `interfaces`), each only
58
+ depending on the ones below it, with ports (`Protocol`s) separating
59
+ contracts from implementations. Add a step type without forking the
60
+ repo via one call to `register_executor()`. See
61
+ [Architecture](doc/architecture.md).
62
+ - **Built for both a blocking CLI and a future async server.**
63
+ `run_to_completion()` blocks until done (right for a CLI); `start_workflow()`
64
+ + polling `get_status()` doesn't (right for an MCP server or any
65
+ poll-driven client) — same DAG, same engine, no separate code path. See
66
+ [API reference](doc/api-reference.md).
67
+
68
+ Everything runs in memory — nothing survives a process restart. That's a
69
+ deliberate v1 choice, not an oversight: the goal is to get the
70
+ architecture right first, with the `WorkflowRepository`/`EventBus` ports
71
+ already shaped so a persistent implementation can be dropped in later
72
+ without touching orchestration logic. The eventual goal is to run this as
73
+ an MCP server so agent tools (e.g. VS Code GitHub Copilot) can submit and
74
+ drive plans.
75
+
76
+ ## Key features
77
+
78
+ | | |
79
+ |---|---|
80
+ | **Real concurrency** | Independent steps run as genuinely parallel `asyncio.Task`s, driven purely by the dependency graph. |
81
+ | **Four built-in executors** | `noop`, `sleep`, `shell` (with timeout), `http` (with retry/backoff) — see [Executors](doc/executors.md). |
82
+ | **Typed, defaulted specs** | Any executor can declare a pydantic `spec_model` and get validation for free, at the earliest honest point. |
83
+ | **Pluggable executors** | `register_executor("my_type", MyExecutor())` — no fork required. |
84
+ | **Rich failure semantics** | Required vs. optional steps, blocking vs. non-blocking dependents (with per-edge overrides), silent vs. visible failures, cascading skips. |
85
+ | **Clean cancellation** | A required failure — or an explicit `cancel_workflow()` — stops every in-flight step promptly and deterministically. |
86
+ | **Output passing** | `${{ steps.<id>.output.<path> }}` placeholders, resolved just before dispatch. |
87
+ | **Two execution modes** | `run_to_completion()` (blocking) and `start_workflow()` + poll (non-blocking), from the exact same engine. |
88
+ | **JSON-ready DTOs** | `WorkflowService` returns primitives-only dataclasses — no domain leakage across the API boundary. |
89
+
90
+ ## Quickstart
91
+
92
+ ```bash
93
+ python -m venv .venv
94
+ source .venv/bin/activate # or `.venv\Scripts\activate` on Windows
95
+ pip install -e ".[dev]"
96
+
97
+ flowlit examples/hello_workflow.yaml
98
+ ```
99
+
100
+ ```bash
101
+ pytest # run the test suite
102
+ ruff check src tests # lint
103
+ mypy src # type-check
104
+ ```
105
+
106
+ See [Getting started](doc/getting-started.md) for a full walkthrough,
107
+ including using flowlit as a library and reading its CLI output.
108
+
109
+ ## Documentation
110
+
111
+ Detailed, modular guides live under [`doc/`](doc/):
112
+
113
+ | Guide | Covers |
114
+ |---|---|
115
+ | [Getting started](doc/getting-started.md) | Install, quickstart, CLI flags, using flowlit as a library. |
116
+ | [Plan format](doc/plan-format.md) | The YAML plan schema: steps, dependencies, output-passing placeholders, failure-handling fields. |
117
+ | [Architecture](doc/architecture.md) | The four-layer design, execution flow, event bus, composition root — with diagrams. |
118
+ | [Concurrency model](doc/concurrency.md) | How the DAG drives real concurrency, cancellation semantics, `max_concurrent_steps`. |
119
+ | [Executors](doc/executors.md) | The `noop`/`sleep`/`shell`/`http` executors, adding your own, registering one from outside the repo. |
120
+ | [API reference](doc/api-reference.md) | `WorkflowService`, ports, DTOs, events, and the full exception hierarchy. |
121
+ | [Configuration](doc/configuration.md) | CLI flags, `build_app_context()` parameters, per-step configuration. |
122
+ | [Observability](doc/observability.md) | Logging setup and live progress via the event bus. |
123
+ | [Testing](doc/testing.md) | Test suite layout and what each part covers. |
124
+ | [Examples](doc/examples.md) | A guided tour of every plan in [`examples/`](examples/). |
125
+
126
+ ## A minimal plan
127
+
128
+ ```yaml
129
+ id: build-and-deploy
130
+ name: Build and Deploy
131
+ steps:
132
+ - id: fetch_source
133
+ type: shell
134
+ spec:
135
+ command: "echo '{\"commit_sha\": \"abc123\"}'"
136
+ depends_on: []
137
+
138
+ - id: notify
139
+ type: noop
140
+ spec:
141
+ message: "deployed commit ${{ steps.fetch_source.output.commit_sha }}"
142
+ depends_on: [fetch_source]
143
+ ```
144
+
145
+ `type` selects the executor; `spec` is whatever that executor needs;
146
+ `depends_on` must form a DAG. See [Plan format](doc/plan-format.md) for
147
+ the complete schema, including `optional`/`blocking`/`silent_failures`
148
+ and `blocking_overrides`.
149
+
150
+ ## Project layout
151
+
152
+ ```
153
+ src/flowlit/
154
+ ├── domain/ Step/Workflow entities, DAG validation -- stdlib only
155
+ ├── application/ use cases, ports (Protocols), orchestration
156
+ ├── infrastructure/ YAML loading, in-memory event bus/repository, executors
157
+ ├── interfaces/ CLI today; a future MCP server; the composition root
158
+ └── plugins.py register_executor() -- the public extension seam
159
+ examples/ runnable YAML plans
160
+ tests/ unit + integration test suite
161
+ ```
162
+
163
+ See [Architecture](doc/architecture.md) for the reasoning behind this
164
+ layering.
165
+
166
+ ## License
167
+
168
+ MIT — see [`pyproject.toml`](pyproject.toml).