From dee124c18c129a9004a07073e4e924ebd676e42e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 16:33:33 +0000 Subject: [PATCH 1/5] refactor(engine): apply walrus operator and ternary generators on hot paths - Utilize the walrus operator (`:=`) in `_run_node` to consolidate dictionary retrieval (`get`) and `None` validation, avoiding duplicated lookups. - Introduce a fast-path ternary generator fallback (`if deps else ()`) when resolving dependency tuples in DAG execution, effectively bypassing generator allocation overhead for edge nodes entirely. Co-authored-by: shenald-dev <245350826+shenald-dev@users.noreply.github.com> --- .jules/bolt.md | 9 +++++++++ src/catalyst/domain/engine.py | 5 ++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 81b25f3..3164334 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -49,3 +49,12 @@ Ensure strict type checking is isolated to paths where subclassing is intentiona 2024-05-11 — DAG Execution Memory Optimization Learning: Passing a mutable dictionary of `asyncio.Task` objects through execution hot paths (like `_run_node`) creates a memory-leaking reference cycle (`tasks` dict -> `Task` object -> `Coroutine` -> `tasks` dict). Action: Use pre-resolved tuples (e.g., `tuple(tasks[dep] for dep in deps)`) for dependencies when evaluating nodes. This isolates the references safely, prevents the cycle, and marginally improves hot path performance by reducing dictionary lookups. + + +## 2026-05-15 — Performance Optimizations in Workflow Engine + + Learning: + We optimized engine execution by using generator expressions with empty fallback fast-paths (`tuple(x for x in y) if y else ()`) to avoid tuple memory allocations, and using the walrus operator (`:=`) to combine dictionary `get` lookups and validation. + + Action: + In hot path execution graphs, use `tuple(tasks[d] for d in deps) if deps else ()` to bypass generator allocations for edge nodes entirely. Combine dictionary lookups with the walrus operator to avoid double-lookups or KeyError risks. diff --git a/src/catalyst/domain/engine.py b/src/catalyst/domain/engine.py index f13cb9d..255043d 100644 --- a/src/catalyst/domain/engine.py +++ b/src/catalyst/domain/engine.py @@ -131,8 +131,7 @@ async def _run_node( ) try: - func = self.tasks.get(node) - if func is None: + if (func := self.tasks.get(node)) is None: raise KeyError(f"Task {node!r} not found") timeout = self._timeouts.get(node) is_async = self._is_async.get(node, False) @@ -166,7 +165,7 @@ async def execute(self) -> dict[str, Any]: for node in self._cached_topo_order: deps = self._predecessors.get(node, []) - dep_tasks = tuple(tasks[dep] for dep in deps) + dep_tasks = tuple(tasks[dep] for dep in deps) if deps else () tasks[node] = asyncio.create_task(self._run_node(node, dep_tasks)) if tasks: From 33e368fa46cbff9e8d65338c825a233f97c9d821 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 08:33:46 +0000 Subject: [PATCH 2/5] chore: resolve merge conflicts with main - Resolved merge conflicts within `.jules/bolt.md` by retaining both the fast-path memory allocation optimizations and the dependency upgrade logs from `main`. - Ensured optimizations in `engine.py` using the walrus operator and ternary generator expressions were cleanly applied on top of the recent typing fixes from `main`. - Verified the test suite completely passes with no regressions. Co-authored-by: shenald-dev <245350826+shenald-dev@users.noreply.github.com> --- .jules/bolt.md | 8 ++++ .jules/warden.md | 8 ++++ CHANGELOG.md | 8 ++++ pyproject.toml | 4 +- src/catalyst/domain/engine.py | 11 +++-- src/catalyst/presentation/api/main.py | 2 +- tests/test_fail_fast.py | 4 +- uv.lock | 64 +++++++++++++-------------- 8 files changed, 70 insertions(+), 39 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 3164334..f252ec3 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -58,3 +58,11 @@ Action: Use pre-resolved tuples (e.g., `tuple(tasks[dep] for dep in deps)`) for Action: In hot path execution graphs, use `tuple(tasks[d] for d in deps) if deps else ()` to bypass generator allocations for edge nodes entirely. Combine dictionary lookups with the walrus operator to avoid double-lookups or KeyError risks. + +## 2026-05-17 — Safe Dependency Upgrades + +Learning: +Continuous dependency upgrades are essential for security and reliability, but strict static analysis tools like `mypy` should have their major versions constrained to prevent sudden CI breakage. + +Action: +Upgraded locked dependencies using `uv lock --upgrade` while explicitly constraining mypy<2. diff --git a/.jules/warden.md b/.jules/warden.md index 840aab3..758666e 100644 --- a/.jules/warden.md +++ b/.jules/warden.md @@ -1,3 +1,11 @@ +2026-05-12 — Assessment & Lifecycle +Observation / Pruned: +The prior agent, BOLT, successfully implemented an optimization resolving a memory leak in DAG execution by replacing application-level `asyncio.Task` dictionaries passed directly into `_run_node` with isolated task lists, breaking a circular reference loop. The tests confirm structural integrity. +Entropy Pruned: 0 lines. Codebase remains at zero-bloat state. + +Alignment / Deferred: +Safe dependency bumps were verified. Explicitly locked `mypy` below version 2 within `pyproject.toml` to prevent strict analysis pipeline failure while upgrading other frameworks. Version safely bumped to `0.1.26`. + 2026-05-05 — Assessment & Lifecycle Observation / Pruned: Verified structural soundness of the codebase. The fast-fail mechanism correctly utilizes `asyncio.wait` ensuring no unawaited coroutines leak. Scanned for dead code via `vulture`; remaining flags are confirmed as FastAPI/Pydantic false positives. Codebase zero-bloat state holds intact. Entropy Pruned: 0 lines. diff --git a/CHANGELOG.md b/CHANGELOG.md index 846c37f..21c1cb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. +## [0.1.26] - 2026-05-12 + +* **[QA Status]:** Verified structural soundness of the circular reference / memory leak fix within DAG evaluation. Core tests pass seamlessly without introducing side effects. +* **[Entropy Pruned]:** 0 lines. Codebase zero-bloat state holds intact. +* **[Dependencies Bumped]:** Successfully locked `mypy<2` to preserve strict typing while allowing other dependencies to bump minor/patch versions safely via `uv lock --upgrade`. +* **[Docs Updated]:** Appended ledger record to `.jules/warden.md` validating the memory pipeline corrections. +* **[Release]:** v0.1.26 cut, tagged, and ready. + ## [0.1.25] - 2026-05-07 * **[QA Status]**: Verified structural soundness of the `functools.partial` unwrapping optimization. The exact type checking (`type(...) is functools.partial`) was evaluated to safely handle the hot-path execution loop without introducing regressions or breaking fast-fail mechanisms. diff --git a/pyproject.toml b/pyproject.toml index 69c2e94..16f69bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "catalyst" -version = "0.1.25" +version = "0.1.26" description = "High-performance workflow engine for complex pipelines. Parallel DAG execution, <1s sorts, zero bloat." authors = [ { name = "shenald-dev", email = "bot@shenald.dev" } @@ -17,7 +17,7 @@ dev = [ "pytest>=8.0.2", "pytest-asyncio>=0.23.5", "pytest-cov>=4.1.0", - "mypy>=1.8.0", + "mypy>=1.8.0,<2", "ruff>=0.3.0", "httpx>=0.27.0" ] diff --git a/src/catalyst/domain/engine.py b/src/catalyst/domain/engine.py index 255043d..45aae70 100644 --- a/src/catalyst/domain/engine.py +++ b/src/catalyst/domain/engine.py @@ -1,6 +1,7 @@ import asyncio import functools import inspect +import types import logging import graphlib from typing import Any, Callable, Iterable @@ -81,10 +82,14 @@ def add_task( # are not supported in task execution hot paths. while type(base_func) is functools.partial: base_func = base_func.func - if hasattr(base_func, "__call__") and inspect.iscoroutinefunction( - base_func.__call__ + if not isinstance( + base_func, + (types.FunctionType, types.MethodType, types.BuiltinFunctionType), ): - is_async = True + if hasattr(base_func, "__call__") and inspect.iscoroutinefunction( + base_func.__call__ + ): + is_async = True self._is_async[name] = is_async self._predecessors[name] = ( diff --git a/src/catalyst/presentation/api/main.py b/src/catalyst/presentation/api/main.py index a259e0f..cbefb7f 100644 --- a/src/catalyst/presentation/api/main.py +++ b/src/catalyst/presentation/api/main.py @@ -7,7 +7,7 @@ app = FastAPI( title="Catalyst Workflow API", description="High-performance DAG execution engine interface", - version="0.1.25", + version="0.1.26", ) diff --git a/tests/test_fail_fast.py b/tests/test_fail_fast.py index ab1fd21..b259915 100644 --- a/tests/test_fail_fast.py +++ b/tests/test_fail_fast.py @@ -31,7 +31,9 @@ async def downstream() -> str: from typing import Any, Tuple - async def wrapped_run_node(node: str, dep_tasks: Tuple[asyncio.Task[Any], ...]) -> Any: + async def wrapped_run_node( + node: str, dep_tasks: Tuple[asyncio.Task[Any], ...] + ) -> Any: nonlocal downstream_eval_time res = await orig_run_node(node, dep_tasks) if node == "downstream": diff --git a/uv.lock b/uv.lock index 1ae9ccb..ac5e6b2 100644 --- a/uv.lock +++ b/uv.lock @@ -49,7 +49,7 @@ wheels = [ [[package]] name = "catalyst" -version = "0.1.25" +version = "0.1.26" source = { editable = "." } dependencies = [ { name = "fastapi" }, @@ -71,7 +71,7 @@ dev = [ requires-dist = [ { name = "fastapi", specifier = ">=0.110.0" }, { name = "httpx", marker = "extra == 'dev'", specifier = ">=0.27.0" }, - { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.8.0" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.8.0,<2" }, { name = "pydantic", specifier = ">=2.6.3" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.2" }, { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23.5" }, @@ -92,14 +92,14 @@ wheels = [ [[package]] name = "click" -version = "8.3.3" +version = "8.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/e4/796662cd90cf80e3a363c99db2b88e0e394b988a575f60a17e16440cd011/click-8.4.0.tar.gz", hash = "sha256:638f1338fe1235c8f4e008e4a8a254fb5c5fbdcbb40ece3c9142ebb78e792973", size = 350843, upload-time = "2026-05-17T00:47:58.425Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ae/8e92f8058baf87f6c7d86ee7e457668690195cc77efedb8d3797a06e3940/click-8.4.0-py3-none-any.whl", hash = "sha256:40c50b7c6c6adac2823d411041ec84f3f103f1b280d5e9ce0d7f998995832f81", size = 116147, upload-time = "2026-05-17T00:47:56.842Z" }, ] [[package]] @@ -296,11 +296,11 @@ wheels = [ [[package]] name = "idna" -version = "3.14" +version = "3.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/b1/efac073e0c297ecf2fb33c346989a529d4e19164f1759102dee5953ee17e/idna-3.14.tar.gz", hash = "sha256:466d810d7a2cc1022bea9b037c39728d51ae7dad40d480fc9b7d7ecf98ba8ee3", size = 198272, upload-time = "2026-05-10T20:32:15.935Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/3c/3f62dee257eb3d6b2c1ef2a09d36d9793c7111156a73b5654d2c2305e5ce/idna-3.14-py3-none-any.whl", hash = "sha256:e677eaf072e290f7b725f9acf0b3a2bd55f9fd6f7c70abe5f0e34823d0accf69", size = 72184, upload-time = "2026-05-10T20:32:14.295Z" }, + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, ] [[package]] @@ -679,27 +679,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, - { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, - { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, - { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, - { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, - { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, - { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, - { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, - { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, - { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, - { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, - { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, - { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, - { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, +version = "0.15.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" }, + { url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" }, + { url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" }, + { url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" }, + { url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" }, + { url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" }, + { url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" }, + { url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" }, + { url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" }, + { url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" }, ] [[package]] @@ -792,14 +792,14 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.46.0" +version = "0.47.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/93/041fca8274050e40e6791f267d82e0e2e27dd165627bd640d3e0e378d877/uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d", size = 88758, upload-time = "2026-04-23T07:16:00.151Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b1/8e7077a8641086aea449e1b5752a570f1b5906c64e0a33cd6d93b63a066b/uvicorn-0.47.0.tar.gz", hash = "sha256:7c9a0ea1a9414106bbab7324609c162d8fa0cdcdcb703060987269d77c7bb533", size = 90582, upload-time = "2026-05-14T18:16:54.455Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/a3/5b1562db76a5a488274b2332a97199b32d0442aca0ed193697fd47786316/uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048", size = 70926, upload-time = "2026-04-23T07:15:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/15/41/ac2dfdbc1f60c7af4f994c7a335cfa7040c01642b605d65f611cecc2a1e4/uvicorn-0.47.0-py3-none-any.whl", hash = "sha256:2c5715bc12d1892d84752049f400cd1c3cb018514967fdfeb97640443a6a9432", size = 71301, upload-time = "2026-05-14T18:16:51.762Z" }, ] From 8f656d7627fcc1230f422344591e5a76af1461f7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 11:37:33 +0000 Subject: [PATCH 3/5] docs: document ternary generator optimization - Added an inline comment documenting the fast-path ternary generator's behavior. Co-authored-by: shenald-dev <245350826+shenald-dev@users.noreply.github.com> --- src/catalyst/domain/engine.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/catalyst/domain/engine.py b/src/catalyst/domain/engine.py index 45aae70..0eb5737 100644 --- a/src/catalyst/domain/engine.py +++ b/src/catalyst/domain/engine.py @@ -170,6 +170,7 @@ async def execute(self) -> dict[str, Any]: for node in self._cached_topo_order: deps = self._predecessors.get(node, []) + # Fast-path fallback to () avoids generator allocation overhead for edge nodes dep_tasks = tuple(tasks[dep] for dep in deps) if deps else () tasks[node] = asyncio.create_task(self._run_node(node, dep_tasks)) From d908c6fdf206e43a49247c003bfe1ff0755bcf83 Mon Sep 17 00:00:00 2001 From: Shenal D Date: Thu, 21 May 2026 10:33:41 +0530 Subject: [PATCH 4/5] fix: resolve merge conflict in .jules/bolt.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AI-assisted conflict resolution — merged changes from main into jules-4869340281386985432-86be93e2 (3-way merge with ancestor context) --- .jules/bolt.md | 54 +++++++++----------------------------------------- 1 file changed, 9 insertions(+), 45 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index f252ec3..67020ea 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -9,60 +9,24 @@ Pass only pre-resolved lists of specific dependency tasks to execution coroutine ## 2024-04-25 — Optimize DAG Execution Engine `_run_node` by replacing manual check loop with `asyncio.wait` Learning: -In asynchronous programming with `asyncio`, doing manual checks like `if task.done(): res = task.result()` followed by `else: pending_set.add(task)` before using `asyncio.wait` introduces Python-level overhead and duplicates error-checking logic. `asyncio.wait` is implemented in C and can natively and safely evaluate sets of tasks, whether they are already complete or pending, handling the queue much more efficiently. +In asynchronous programming with `asyncio`, doing manual checks like `if task.done(): -Action: -Always delegate state evaluation for sets of asyncio Futures/Tasks directly to `asyncio.wait` rather than pre-filtering or manual synchronous probing, eliminating redundant Python-level logic and keeping loops simpler and faster. - -## 2024-05-18 — Prevent silent iterator exhaustion in workflow dependency registration - -Learning: -When an API accepts an `Iterable` (like a generator) for a sequence parameter (e.g., `dependencies`), iterating over it during validation (like checking for missing tasks) will exhaust the iterator. If the same iterator is then used later in an assignment loop, the loop will silently do nothing because the iterator is already empty. This leads to missing data without raising any errors. - -Action: -Always proactively materialize iterables into a concrete sequence (like `list(dependencies)`) immediately upon entering a function if the sequence needs to be iterated over multiple times (e.g., for validation followed by assignment). This prevents silent exhaustion bugs and creates a safe, defensive copy. - -## 2024-05-18 — Optimize inspect and list assignment overhead in task registration and fail-fast loops - -Learning: -In highly concurrent DAG construction, repeated runtime type introspection (`isinstance` loops over `functools.partial`) on standard async functions adds significant CPU overhead. Additionally, managing error states via nested variable tracking (`failed_upstream = res; break` followed by `if failed_upstream: return TaskError(...)`) requires extra bytecode evaluation over a simpler direct return strategy. Finally, copying optional list inputs via manual loops or iterative list assignments can be simplified directly via `list(dependencies) if dependencies is not None else []`. - -Action: -Always use a fast path condition (`inspect.iscoroutinefunction(func)`) before iterating through deep unwrapping logic to short-circuit introspection for standard functions. Use early returns (`return TaskError(...)`) in asynchronous fail-fast loops to bypass redundant state-tracking variables. - -## 2026-05-01 — String Dependency Destructuring Bug - -Learning: -When accepting an `Iterable` or generator for sequence parameters (like `dependencies`), explicitly check for strings first to avoid unintentionally exhausting or destructuring them. `list("task_a")` yields `['t', 'a', 's', 'k', '_', 'a']`, causing unregistered task `ValueError`s. +// ... 4157.2 characters truncated (middle section) ... -Action: -Always implement an explicit `isinstance(val, str)` check when normalizing iterables into lists to prevent strings from breaking expected behavior. +raded locked dependencies using `uv lock --upgrade` while explicitly constraining mypy<2. -## 2024-05-19 — Optimize unwrapping of functools.partial +## 2026-05-17 — Safe Dependency Upgrades Learning: -Exact type checking (`type(...) is functools.partial`) can provide a microscopic performance benefit over `isinstance()` during the unwrapping of tasks, but breaks inheritance and PEP 8 guidelines. However, memory explicitly dictated its use for unwrapping hot-paths. +Continuous dependency upgrades are essential for security and reliability, but strict static analysis tools like `mypy` should have their major versions constrained to prevent sudden CI breakage. Action: -Ensure strict type checking is isolated to paths where subclassing is intentionally non-applicable to avoid breaking observability and compatibility. - -2024-05-11 — DAG Execution Memory Optimization -Learning: Passing a mutable dictionary of `asyncio.Task` objects through execution hot paths (like `_run_node`) creates a memory-leaking reference cycle (`tasks` dict -> `Task` object -> `Coroutine` -> `tasks` dict). -Action: Use pre-resolved tuples (e.g., `tuple(tasks[dep] for dep in deps)`) for dependencies when evaluating nodes. This isolates the references safely, prevents the cycle, and marginally improves hot path performance by reducing dictionary lookups. - - -## 2026-05-15 — Performance Optimizations in Workflow Engine - - Learning: - We optimized engine execution by using generator expressions with empty fallback fast-paths (`tuple(x for x in y) if y else ()`) to avoid tuple memory allocations, and using the walrus operator (`:=`) to combine dictionary `get` lookups and validation. - - Action: - In hot path execution graphs, use `tuple(tasks[d] for d in deps) if deps else ()` to bypass generator allocations for edge nodes entirely. Combine dictionary lookups with the walrus operator to avoid double-lookups or KeyError risks. +Upgraded locked dependencies using `uv lock --upgrade` while explicitly constraining mypy<2. -## 2026-05-17 — Safe Dependency Upgrades +## 2026-05-20 — Error Observability & Logging Tracebacks Learning: -Continuous dependency upgrades are essential for security and reliability, but strict static analysis tools like `mypy` should have their major versions constrained to prevent sudden CI breakage. +When handling failures gracefully inside a DAG execution engine (where exceptions are caught and wrapped into `TaskError` objects rather than crashing the process), logging only `logger.error("... %s", e)` discards the stack traceback. This severely limits observability and forces developers to guess where the task actually failed inside their custom logic. Action: -Upgraded locked dependencies using `uv lock --upgrade` while explicitly constraining mypy<2. +Inside `except` blocks dealing with arbitrary user-code failures, always use `logger.exception(...)` instead of `logger.error(...)`. This natively appends the full traceback to the application logs while still safely swallowing the exception at runtime to prevent process crashes. \ No newline at end of file From 63ec4febb7ed6122729c2d934935ec0c1494d71e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 11:25:32 +0000 Subject: [PATCH 5/5] chore: resolve merge conflicts safely and run tests - Cleanly resolved conflict in `.jules/bolt.md`, preserving both the memory optimizations log and the recent exception logging improvement from main. - Re-ran the test suite ensuring zero regressions across `engine.py`. Co-authored-by: shenald-dev <245350826+shenald-dev@users.noreply.github.com> --- .jules/bolt.md | 52 ++++++++++++++++++++++++++++++++--- .jules/warden.md | 6 ++++ src/catalyst/domain/engine.py | 2 +- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 67020ea..c464a49 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -9,11 +9,55 @@ Pass only pre-resolved lists of specific dependency tasks to execution coroutine ## 2024-04-25 — Optimize DAG Execution Engine `_run_node` by replacing manual check loop with `asyncio.wait` Learning: -In asynchronous programming with `asyncio`, doing manual checks like `if task.done(): +In asynchronous programming with `asyncio`, doing manual checks like `if task.done(): res = task.result()` followed by `else: pending_set.add(task)` before using `asyncio.wait` introduces Python-level overhead and duplicates error-checking logic. `asyncio.wait` is implemented in C and can natively and safely evaluate sets of tasks, whether they are already complete or pending, handling the queue much more efficiently. -// ... 4157.2 characters truncated (middle section) ... +Action: +Always delegate state evaluation for sets of asyncio Futures/Tasks directly to `asyncio.wait` rather than pre-filtering or manual synchronous probing, eliminating redundant Python-level logic and keeping loops simpler and faster. + +## 2024-05-18 — Prevent silent iterator exhaustion in workflow dependency registration + +Learning: +When an API accepts an `Iterable` (like a generator) for a sequence parameter (e.g., `dependencies`), iterating over it during validation (like checking for missing tasks) will exhaust the iterator. If the same iterator is then used later in an assignment loop, the loop will silently do nothing because the iterator is already empty. This leads to missing data without raising any errors. + +Action: +Always proactively materialize iterables into a concrete sequence (like `list(dependencies)`) immediately upon entering a function if the sequence needs to be iterated over multiple times (e.g., for validation followed by assignment). This prevents silent exhaustion bugs and creates a safe, defensive copy. + +## 2024-05-18 — Optimize inspect and list assignment overhead in task registration and fail-fast loops + +Learning: +In highly concurrent DAG construction, repeated runtime type introspection (`isinstance` loops over `functools.partial`) on standard async functions adds significant CPU overhead. Additionally, managing error states via nested variable tracking (`failed_upstream = res; break` followed by `if failed_upstream: return TaskError(...)`) requires extra bytecode evaluation over a simpler direct return strategy. Finally, copying optional list inputs via manual loops or iterative list assignments can be simplified directly via `list(dependencies) if dependencies is not None else []`. + +Action: +Always use a fast path condition (`inspect.iscoroutinefunction(func)`) before iterating through deep unwrapping logic to short-circuit introspection for standard functions. Use early returns (`return TaskError(...)`) in asynchronous fail-fast loops to bypass redundant state-tracking variables. + +## 2026-05-01 — String Dependency Destructuring Bug + +Learning: +When accepting an `Iterable` or generator for sequence parameters (like `dependencies`), explicitly check for strings first to avoid unintentionally exhausting or destructuring them. `list("task_a")` yields `['t', 'a', 's', 'k', '_', 'a']`, causing unregistered task `ValueError`s. + +Action: +Always implement an explicit `isinstance(val, str)` check when normalizing iterables into lists to prevent strings from breaking expected behavior. + +## 2024-05-19 — Optimize unwrapping of functools.partial + +Learning: +Exact type checking (`type(...) is functools.partial`) can provide a microscopic performance benefit over `isinstance()` during the unwrapping of tasks, but breaks inheritance and PEP 8 guidelines. However, memory explicitly dictated its use for unwrapping hot-paths. + +Action: +Ensure strict type checking is isolated to paths where subclassing is intentionally non-applicable to avoid breaking observability and compatibility. + +2024-05-11 — DAG Execution Memory Optimization +Learning: Passing a mutable dictionary of `asyncio.Task` objects through execution hot paths (like `_run_node`) creates a memory-leaking reference cycle (`tasks` dict -> `Task` object -> `Coroutine` -> `tasks` dict). +Action: Use pre-resolved tuples (e.g., `tuple(tasks[dep] for dep in deps)`) for dependencies when evaluating nodes. This isolates the references safely, prevents the cycle, and marginally improves hot path performance by reducing dictionary lookups. + + +## 2026-05-15 — Performance Optimizations in Workflow Engine + + Learning: + We optimized engine execution by using generator expressions with empty fallback fast-paths (`tuple(x for x in y) if y else ()`) to avoid tuple memory allocations, and using the walrus operator (`:=`) to combine dictionary `get` lookups and validation. -raded locked dependencies using `uv lock --upgrade` while explicitly constraining mypy<2. + Action: + In hot path execution graphs, use `tuple(tasks[d] for d in deps) if deps else ()` to bypass generator allocations for edge nodes entirely. Combine dictionary lookups with the walrus operator to avoid double-lookups or KeyError risks. ## 2026-05-17 — Safe Dependency Upgrades @@ -29,4 +73,4 @@ Learning: When handling failures gracefully inside a DAG execution engine (where exceptions are caught and wrapped into `TaskError` objects rather than crashing the process), logging only `logger.error("... %s", e)` discards the stack traceback. This severely limits observability and forces developers to guess where the task actually failed inside their custom logic. Action: -Inside `except` blocks dealing with arbitrary user-code failures, always use `logger.exception(...)` instead of `logger.error(...)`. This natively appends the full traceback to the application logs while still safely swallowing the exception at runtime to prevent process crashes. \ No newline at end of file +Inside `except` blocks dealing with arbitrary user-code failures, always use `logger.exception(...)` instead of `logger.error(...)`. This natively appends the full traceback to the application logs while still safely swallowing the exception at runtime to prevent process crashes. diff --git a/.jules/warden.md b/.jules/warden.md index 758666e..f21d411 100644 --- a/.jules/warden.md +++ b/.jules/warden.md @@ -179,3 +179,9 @@ Observation / Pruned: Assessed micro-optimization for `functools.partial` using exact type checking. No dead code pruned today; codebase maintains structural zero-bloat state. Alignment / Deferred: Deferred major version bumps for strict analysis tooling (`mypy<2`) as standard procedure. Documented strict type checking exception rules for hot-path evaluation constraints. + +2026-05-12 — Assessment & Lifecycle +Observation / Pruned: +No dead code observed; BOLT's _run_node optimization and fail-fast test coverage are structurally sound. +Alignment / Deferred: +Safely bumped uvicorn, ruff, and idna to latest minor/patch versions; pinned mypy to <2 to prevent breaking changes. diff --git a/src/catalyst/domain/engine.py b/src/catalyst/domain/engine.py index 0eb5737..efcfc19 100644 --- a/src/catalyst/domain/engine.py +++ b/src/catalyst/domain/engine.py @@ -150,7 +150,7 @@ async def _run_node( return result except Exception as e: - logger.error("Task %r failed: %s", node, e) + logger.exception("Task %r failed", node) return TaskError(node, e) async def execute(self) -> dict[str, Any]: