Skip to content

feat(graph-engine): make layer runtime state non-null and bound early#6

Open
tomerqodo wants to merge 9 commits intocoderabbit_only-issues-20260113-coderabbit_base_featgraph-engine_make_layer_runtime_state_non-null_and_bound_early_pr177from
coderabbit_only-issues-20260113-coderabbit_head_featgraph-engine_make_layer_runtime_state_non-null_and_bound_early_pr177
Open

feat(graph-engine): make layer runtime state non-null and bound early#6
tomerqodo wants to merge 9 commits intocoderabbit_only-issues-20260113-coderabbit_base_featgraph-engine_make_layer_runtime_state_non-null_and_bound_early_pr177from
coderabbit_only-issues-20260113-coderabbit_head_featgraph-engine_make_layer_runtime_state_non-null_and_bound_early_pr177

Conversation

@tomerqodo
Copy link

@tomerqodo tomerqodo commented Jan 16, 2026

Benchmark PR from qodo-benchmark#177

Summary by CodeRabbit

  • Documentation

    • Enhanced layer initialization documentation explaining runtime state binding and availability in layer hooks.
  • Bug Fixes

    • Improved error handling with explicit exceptions for uninitialized layer access, replacing generic attribute errors.
  • Tests

    • Added comprehensive layer initialization test coverage.

✏️ Tip: You can customize this high-level summary in your review settings.

laipz8200 and others added 9 commits January 5, 2026 13:43
…non-null state (vibe-kanban 85cdff8f) Parent: 7c1fead6-6f01-4778-9089-3782e9bcc6b1 (Make self.graph\_runtime\_state in GraphEngineLayer not None) Goal: Now that GraphEngineLayer.graph\_runtime\_state is guaranteed non-null, remove conditional checks and align docs/tests. Tasks: - Remove None-guards in built-in layers that currently check graph\_runtime\_state: - api/core/workflow/graph\_engine/layers/debug\_logging.py (remove \`if self.graph\_runtime\_state\` blocks; assume available after init). - api/core/workflow/graph\_engine/layers/persistence.py (remove \`if runtime\_state is None\` branches in \_populate\_completion\_statistics and \_system\_variables; treat runtime\_state as always present). - Update documentation to describe the non-null contract and early binding: - api/core/workflow/graph\_engine/layers/README.md (mention runtime state always available after engine.layer()). - api/core/workflow/README.md if it describes layer usage (optional, only if it mentions None checks or initialization caveats). Acceptance: - No \`if self.graph\_runtime\_state\` checks remain in layers. - Docs reflect that graph\_runtime\_state is always present after registration and before run. - Type checking remains clean (no Optional). Run (targeted if needed): uv run --project api -m pytest api/tests/unit\_tests/core/workflow/graph\_engine/layers
…uninitialized wrapper (vibe-kanban 7858b561) Parent: 7c1fead6-6f01-4778-9089-3782e9bcc6b1 (Make self.graph\_runtime\_state in GraphEngineLayer not None) Goal: Ensure GraphEngineLayer.graph\_runtime\_state is never None while preserving the initialization lifecycle. Provide a typed uninitialized implementation that raises a domain error when accessed too early, and bind a real read-only wrapper as soon as the layer is registered. Implementation details: - Add a domain error class, e.g., GraphEngineLayerNotInitializedError, in api/core/workflow/graph\_engine/layers/base.py or a new api/core/workflow/graph\_engine/layers/errors.py (keep DDD boundaries in mind). - Implement UninitializedReadOnlyGraphRuntimeState that satisfies the ReadOnlyGraphRuntimeState Protocol and raises GraphEngineLayerNotInitializedError for all properties/methods (system\_variable, variable\_pool, start\_at, total\_tokens, llm\_usage, outputs, node\_run\_steps, ready\_queue\_size, exceptions\_count, get\_output, dumps). - Update GraphEngineLayer: - Annotate graph\_runtime\_state as ReadOnlyGraphRuntimeState (no | None). - Default it to UninitializedReadOnlyGraphRuntimeState() in \_\_init\_\_. - Keep command\_channel as-is (optional) unless you decide to give it an uninitialized sentinel. - Update initialize() docstring to note it may be called more than once (early binding + engine run) and should be idempotent. - Update GraphEngine.layer(): - Bind context immediately by calling layer.initialize(ReadOnlyGraphRuntimeStateWrapper(self.\_graph\_runtime\_state), self.\_command\_channel). - Keep on\_graph\_start only in \_initialize\_layers; do not call it here. - If you add a helper like \_bind\_layer\_context(layer) to avoid duplication, use it both here and in \_initialize\_layers. Acceptance: - graph\_runtime\_state is never None at runtime or in types. - Accessing graph\_runtime\_state before binding raises GraphEngineLayerNotInitializedError with a clear message. - After engine.layer(layer), graph\_runtime\_state is readable without None checks. Files likely touched: - api/core/workflow/graph\_engine/layers/base.py - api/core/workflow/graph\_engine/graph\_engine.py - api/core/workflow/runtime/read\_only\_wrappers.py (if you locate UninitializedReadOnlyGraphRuntimeState there)
…zed access (vibe-kanban 0717c38f) Parent: 7c1fead6-6f01-4778-9089-3782e9bcc6b1 (Make self.graph\_runtime\_state in GraphEngineLayer not None) Background: GraphEngineLayer.graph\_runtime\_state must never be None. We will introduce an UninitializedReadOnlyGraphRuntimeState that raises GraphEngineLayerNotInitializedError until GraphEngine binds a real ReadOnlyGraphRuntimeStateWrapper. GraphEngine.layer() will bind early so users can read state after registration without None checks. Work: - Add unit tests for the new non-null contract and uninitialized access behavior. - Prefer a new focused test file: api/tests/unit\_tests/core/workflow/graph\_engine/layers/test\_layer\_initialization.py (or extend api/tests/unit\_tests/core/workflow/graph\_engine/test\_graph\_engine.py if you want to keep related tests together). Test cases (Arrange-Act-Assert): 1) test\_layer\_runtime\_state\_raises\_when\_uninitialized - Create a minimal TestLayer(GraphEngineLayer) with no overrides. - Instantiate it without engine binding. - Assert accessing any property (e.g., layer.graph\_runtime\_state.outputs or layer.graph\_runtime\_state.dumps()) raises GraphEngineLayerNotInitializedError. 2) test\_layer\_runtime\_state\_available\_after\_engine\_layer - Use WorkflowRunner + fixture (e.g., simple\_passthrough\_workflow) to create graph and GraphRuntimeState. - Create GraphEngine with InMemoryChannel. - Register layer via engine.layer(layer). - Assert accessing layer.graph\_runtime\_state.\* does not raise and returns expected defaults (e.g., outputs == {} initially, ready\_queue\_size is int >= 0). Notes: - Import GraphEngineLayerNotInitializedError from the module you add it to. - Ensure types are explicit and no Any. Run (targeted): uv run --project api -m pytest api/tests/unit\_tests/core/workflow/graph\_engine/layers/test\_layer\_initialization.py
- fix(app): remove boolean guard on TriggerPostLayer graph_runtime_state access Tests not run (not requested).
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…l with property guard and update layer/tests Tests not run (not requested).
@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 2026

Walkthrough

The changes refactor GraphEngineLayer initialization to guarantee runtime state is bound before layer hooks execute. A new exception GraphEngineLayerNotInitializedError replaces implicit None checks, the graph engine explicitly binds layer context via _bind_layer_context(), and layer implementations remove defensive null guards on graph_runtime_state.

Changes

Cohort / File(s) Summary
Layer base class refactoring
api/core/workflow/graph_engine/layers/base.py
Introduces GraphEngineLayerNotInitializedError exception; renames internal storage from graph_runtime_state to _graph_runtime_state; adds public property that raises exception if uninitialized; makes initialize() idempotent.
Graph engine binding
api/core/workflow/graph_engine/graph_engine.py
Adds private _bind_layer_context(layer) helper to initialize layers with read-only runtime state and command channel; invokes binding when layers are added via layer(); removes try/except around layer initialization.
Layer implementation updates
api/core/app/layers/pause_state_persist_layer.py, api/core/app/layers/trigger_post_layer.py, api/core/workflow/graph_engine/layers/debug_logging.py, api/core/workflow/graph_engine/layers/persistence.py
Removes null checks and guards for graph_runtime_state in hook methods; adds base class constructor calls; assumes runtime state is always available after engine binding.
Documentation updates
api/core/workflow/README.md, api/core/workflow/graph_engine/layers/README.md
Clarifies that engine.layer() binds read-only runtime state before execution, making graph_runtime_state available to all layer hooks.
Test updates
api/tests/test_containers_integration_tests/core/app/layers/test_pause_state_persist_layer.py, api/tests/unit_tests/core/app/layers/test_pause_state_persist_layer.py
Updates expectations from AttributeError to GraphEngineLayerNotInitializedError; adds import for new exception.
New layer initialization tests
api/tests/unit_tests/core/workflow/graph_engine/layers/test_layer_initialization.py
Adds LayerForTest subclass and two tests verifying that accessing uninitialized graph_runtime_state raises GraphEngineLayerNotInitializedError and that initialized layers have accessible outputs and ready_queue_size.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 With bound runtime state, no checks we need,
The engine plants the initialization seed,
Layers bloom knowing state's always there,
Guards removed—a promise fair,
Exceptions sing when not prepared! 🌱

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is minimal and only references a benchmark PR link. It lacks required sections like Summary, Motivation, and detailed explanation per the template. Expand the description to include a clear summary of changes, motivation for this refactor, context about why runtime state binding matters, and verification that the checklist items are properly addressed.
Docstring Coverage ⚠️ Warning Docstring coverage is 46.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main objective: making layer runtime state non-null and bound early, which is precisely what the changeset accomplishes across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
api/core/workflow/graph_engine/graph_engine.py (1)

308-317: Consider avoiding redundant wrapper creation.

Since layer.initialize() is idempotent and layers are already initialized via _bind_layer_context() when added via layer(), the ReadOnlyGraphRuntimeStateWrapper created on line 311 is unused for pre-registered layers. Consider checking if initialization is needed before creating the wrapper, or rely solely on _bind_layer_context().

♻️ Optional refactor to avoid redundant wrapper creation
 def _initialize_layers(self) -> None: """Initialize layers with context.""" self._event_manager.set_layers(self._layers) - read_only_state = ReadOnlyGraphRuntimeStateWrapper(self._graph_runtime_state) for layer in self._layers: - layer.initialize(read_only_state, self._command_channel) + # Layers added via layer() are already bound; this handles any edge cases + self._bind_layer_context(layer) try: layer.on_graph_start() except Exception as e: logger.warning("Layer %s failed on_graph_start: %s", layer.__class__.__name__, e)
📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3e19c4 and 9244e52.

📒 Files selected for processing (11)
  • api/core/app/layers/pause_state_persist_layer.py
  • api/core/app/layers/trigger_post_layer.py
  • api/core/workflow/README.md
  • api/core/workflow/graph_engine/graph_engine.py
  • api/core/workflow/graph_engine/layers/README.md
  • api/core/workflow/graph_engine/layers/base.py
  • api/core/workflow/graph_engine/layers/debug_logging.py
  • api/core/workflow/graph_engine/layers/persistence.py
  • api/tests/test_containers_integration_tests/core/app/layers/test_pause_state_persist_layer.py
  • api/tests/unit_tests/core/app/layers/test_pause_state_persist_layer.py
  • api/tests/unit_tests/core/workflow/graph_engine/layers/test_layer_initialization.py
💤 Files with no reviewable changes (1)
  • api/core/workflow/graph_engine/layers/persistence.py
🧰 Additional context used
🧬 Code graph analysis (5)
api/tests/unit_tests/core/app/layers/test_pause_state_persist_layer.py (1)
api/core/workflow/graph_engine/layers/base.py (2)
  • GraphEngineLayerNotInitializedError (16-21)
  • graph_runtime_state (43-46)
api/tests/unit_tests/core/workflow/graph_engine/layers/test_layer_initialization.py (2)
api/core/workflow/graph_engine/graph_engine.py (2)
  • GraphEngine (53-365)
  • layer (221-225)
api/core/workflow/graph_engine/layers/base.py (6)
  • GraphEngineLayer (24-127)
  • GraphEngineLayerNotInitializedError (16-21)
  • on_graph_start (65-72)
  • on_event (75-88)
  • on_graph_end (91-101)
  • graph_runtime_state (43-46)
api/core/workflow/graph_engine/layers/debug_logging.py (2)
api/core/workflow/graph_engine/graph_engine.py (1)
  • graph_runtime_state (363-365)
api/core/workflow/graph_engine/layers/base.py (1)
  • graph_runtime_state (43-46)
api/core/workflow/graph_engine/layers/base.py (1)
api/core/workflow/runtime/graph_runtime_state_protocol.py (1)
  • ReadOnlyGraphRuntimeState (25-83)
api/core/workflow/graph_engine/graph_engine.py (2)
api/core/workflow/graph_engine/layers/base.py (1)
  • initialize (48-62)
api/core/workflow/runtime/read_only_wrappers.py (1)
  • ReadOnlyGraphRuntimeStateWrapper (39-87)
🔇 Additional comments (18)
api/core/workflow/README.md (1)

67-68: Docs align with early runtime-state binding.

Clear statement of the non-null runtime-state contract for layers.

api/core/workflow/graph_engine/layers/README.md (1)

11-11: Layer docs now match the initialization contract.

Good clarification that runtime state is bound on registration and available in hooks.

Also applies to: 37-38

api/core/app/layers/trigger_post_layer.py (1)

29-40: Base layer initialization is now enforced.

Calling super().__init__() correctly wires the base runtime-state/command-channel setup.

api/core/workflow/graph_engine/layers/base.py (2)

16-46: Explicit uninitialized error is a strong guardrail.

Fail-fast access on unbound runtime state makes lifecycle issues obvious.


52-62: Idempotent initialize behavior looks solid.

The early-return keeps layer binding stable across repeated calls.

api/core/app/layers/pause_state_persist_layer.py (1)

55-70: Base constructor call aligns with the new layer contract.

Ensures base runtime-state/command-channel fields are initialized.

api/tests/test_containers_integration_tests/core/app/layers/test_pause_state_persist_layer.py (2)

38-38: LGTM!

Import correctly added to support the new exception-based initialization check.


569-579: LGTM!

Test correctly updated to expect GraphEngineLayerNotInitializedError instead of AttributeError, aligning with the new exception-based initialization signaling.

api/core/workflow/graph_engine/layers/debug_logging.py (2)

112-114: LGTM!

The removal of the None check is correct since on_graph_start is called after layer.initialize() in _initialize_layers(), guaranteeing that graph_runtime_state is bound and accessible.


245-246: LGTM!

The simplified condition correctly relies on the non-null contract for graph_runtime_state while still checking the include_outputs flag and output availability.

api/tests/unit_tests/core/app/layers/test_pause_state_persist_layer.py (3)

18-18: LGTM!

Import correctly added for the new exception type.


213-215: LGTM!

Test correctly updated to verify that accessing graph_runtime_state before initialization raises GraphEngineLayerNotInitializedError, while command_channel remains accessible (as None).


300-311: LGTM!

Test correctly validates that on_event raises GraphEngineLayerNotInitializedError when the layer is not initialized, consistent with the new exception-based signaling pattern.

api/core/workflow/graph_engine/graph_engine.py (2)

215-219: LGTM!

The new _bind_layer_context method correctly wraps the runtime state in a read-only wrapper and initializes the layer, enabling early binding when layers are registered.


221-225: LGTM!

Early binding via _bind_layer_context ensures layers have access to the read-only runtime state immediately after registration, fulfilling the PR's non-null contract.

api/tests/unit_tests/core/workflow/graph_engine/layers/test_layer_initialization.py (3)

16-24: LGTM!

LayerForTest provides a minimal concrete implementation of GraphEngineLayer with no-op overrides, suitable for testing the initialization lifecycle.


27-31: LGTM!

Clean test validating that accessing graph_runtime_state on an uninitialized layer raises GraphEngineLayerNotInitializedError.


34-56: LGTM!

Comprehensive test validating that graph_runtime_state is accessible immediately after engine.layer(layer) is called, without needing to run the engine. This confirms the early binding behavior.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants