Repository navigation
docs(nodes): document mutable state as intentional MVP design [OMN-704] - #191
Conversation
Add explicit documentation that mutable state in nodes (NodeCompute cache,
NodeReducer FSM state, NodeOrchestrator workflow state) is an intentional
design decision for the MVP phase.
Changes:
- Expand Thread Safety sections in NodeCompute, NodeReducer, NodeOrchestrator
to explain MVP design rationale and list mutable state components
- Create docs/architecture/MUTABLE_STATE_STRATEGY.md with:
- Comprehensive explanation of MVP mutable state trade-offs
- Current mutable state breakdown by node type
- 4-phase production improvement roadmap (Protocol Injection → External
State Stores → Lease-Based Coordination → Event-Sourced State)
- Thread safety guidelines and migration path
This addresses the code quality audit finding that nodes should explicitly
document their thread-safety limitations and production improvement plans.
WalkthroughDocumentation updates establish MVP design decisions for mutable state in ONEX nodes. A new architecture document outlines thread-safety implications, production roadmap with planned migration phases, and migration strategies. Three node classes' docstrings are updated to reflect intentional mutable state with current limitations and guidance. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
Code Review - PR #191: Document Mutable State as Intentional MVP DesignSummaryThis PR successfully documents the intentional use of mutable state in ONEX nodes as an explicit MVP design decision. The documentation is comprehensive, well-structured, and provides clear guidance for both current limitations and future improvements. ✅ Strengths1. Excellent Documentation Quality
2. Node Docstring Updates
3. Production-Ready Roadmap
4. Thread Safety Guidance
📝 Suggestions for ImprovementMinor Issues1. Documentation Index Missing EntryThe new Suggested addition to | [**Mutable State Strategy**](architecture/MUTABLE_STATE_STRATEGY.md) | MVP design decision and production roadmap ⭐ **Important** | ✅ Complete |Place it near the Thread Safety documentation reference. 2. CLAUDE.md Should Reference New DocThe project's Suggested addition to **See**:
- [docs/guides/THREADING.md](docs/guides/THREADING.md) for thread-local instance patterns
- [docs/architecture/MUTABLE_STATE_STRATEGY.md](docs/architecture/MUTABLE_STATE_STRATEGY.md) for MVP design rationale and production roadmap3. Minor Typo in Migration PathIn Current: | Phase 2 | `docs/guides/MIGRATING_TO_EXTERNAL_STATE.md` |Suggested (for consistency): | Phase 2 | docs/guides/MIGRATING_TO_EXTERNAL_STATE.md (planned) |Code Quality Notes✅ No Code Logic ChangesThis is a documentation-only PR, which minimizes risk. The only code changes are docstring updates. ✅ Docstring Format CorrectAll updated docstrings follow the project's reStructuredText conventions with proper indentation and formatting. ✅ Thread Safety Sections ExpandedThe expanded Thread Safety sections in node docstrings are significantly more informative than the previous terse warnings. Before (NodeReducer): Thread Safety:
NodeReducer instances are NOT thread-safe due to mutable FSM state:
- Current state tracking
- State transition history
- Context accumulation
Each thread should have its own NodeReducer instance, or implement
explicit synchronization. See docs/guides/THREADING.md for patterns.After: Thread Safety:
**MVP Design Decision**: NodeReducer uses mutable FSM state intentionally for
the MVP phase to enable stateful workflow processing with minimal complexity.
This is a documented trade-off.
**Mutable State Components**:
- fsm_contract: Loaded FSM subcontract reference
- FSM execution state (via MixinFSMExecution):
- Current state tracking
- State transition history
- Context accumulation
**Current Limitations**:
NodeReducer instances are NOT thread-safe. Concurrent access will corrupt
FSM state.
**Production Path**: Future versions will support stateless FSM execution
with external state stores. See docs/architecture/MUTABLE_STATE_STRATEGY.md
for the production improvement roadmap.
**Mitigation**: Each thread should have its own NodeReducer instance.
See docs/guides/THREADING.md for thread-local instance patterns.The new version is much more informative and educational. 🔒 Security Considerations✅ No security concerns - This is documentation-only. 🧪 Test Coverage✅ Appropriate for documentation PR - No test changes needed since this is pure documentation. The PR description indicates all pre-commit hooks passed, which validates:
🚀 Performance Considerations✅ No performance impact - Documentation and docstring changes only. 📋 Acceptance Criteria CheckAll acceptance criteria from OMN-704 met:
🎯 Final RecommendationAPPROVE ✅ with minor documentation index updates suggested above. Why Approve?
Post-Merge ActionsAfter merging, consider:
Great work documenting this design decision! This will significantly help developers understand the threading model and plan for production deployments. |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
docs/architecture/MUTABLE_STATE_STRATEGY.md (3)
124-131: Thread-local pattern example could cause stale state issues.The
get_reducerfunction caches the NodeReducer instance but doesn't account for scenarios where thefsm_contractmight change. If called with a differentfsm_contractafter initial creation, the cached instance with the old contract is returned.Consider documenting this limitation or adjusting the pattern:
def get_reducer(container, fsm_contract): """Get or create thread-local NodeReducer instance.""" - if not hasattr(thread_local, 'reducer'): + if not hasattr(thread_local, 'reducer') or thread_local.reducer.fsm_contract != fsm_contract: node = NodeReducer(container) node.fsm_contract = fsm_contract thread_local.reducer = node return thread_local.reducerAlternatively, add a note that this pattern assumes a single FSM contract per thread.
161-168: Same stale state concern applies to orchestrator pattern.The
get_orchestratorfunction has the same potential issue asget_reducer- if called with a differentworkflow_definition, the cached instance is returned with the old definition.For consistency, consider the same fix or add documentation noting this limitation.
229-247: Add blank lines before tables for markdown compliance.Per markdownlint MD058, tables should be surrounded by blank lines for consistent rendering across markdown parsers.
**Thread-Safe Components**: + | Component | Notes | |-----------|-------| ... **NOT Thread-Safe Components**: + | Component | Risk Level | Recommended Mitigation | |-----------|------------|------------------------|
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
docs/architecture/MUTABLE_STATE_STRATEGY.md(1 hunks)src/omnibase_core/nodes/node_compute.py(1 hunks)src/omnibase_core/nodes/node_orchestrator.py(1 hunks)src/omnibase_core/nodes/node_reducer.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Always use Poetry for Python package management and task execution - usepoetry runfor all Python commands (pytest, mypy, black, isort, etc.), never use pip or direct python execution
Callsuper().__init__(container)in all node class constructors - this is MANDATORY and eliminates 80+ lines of boilerplate initialization code
Always raise structured errors using ModelOnexError with appropriate EnumCoreErrorCode, never use generic Exception
Usecontainer.get_service("ProtocolName")for dependency injection - always use protocol names, never concrete class names or isinstance checks
Use @standard_error_handling decorator on methods to eliminate 6+ lines of try/catch boilerplate
Do NOT share node instances across threads - use thread-local instances or separate instances per thread for NodeCompute, NodeEffect, NodeReducer, NodeOrchestrator
Use FileRegistry for loading RuntimeHostContract YAML files - handles fail-fast error reporting with specific error codes (FILE_NOT_FOUND, CONFIGURATION_PARSE_ERROR, CONTRACT_VALIDATION_ERROR, etc.)
Use the correct container type: ModelONEXContainer for dependency injection in node constructors, ModelContainer[T] only for wrapping single values with metadata
Use EnumNodeKind for architectural classification (EFFECT, COMPUTE, REDUCER, ORCHESTRATOR) and EnumNodeType for specific implementation type (TRANSFORMER, AGGREGATOR, GATEWAY, etc.)
Format code usingpoetry run blackand sort imports usingpoetry run isortbefore committing
Lint code usingpoetry run ruff checkto detect issues and apply auto-fixes withpoetry run ruff check --fix
Files:
src/omnibase_core/nodes/node_orchestrator.pysrc/omnibase_core/nodes/node_compute.pysrc/omnibase_core/nodes/node_reducer.py
src/omnibase_core/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/omnibase_core/**/*.py: Achieve and maintain 100% mypy strict mode compliance with 0 errors across all source files
Achieve and maintain pyright basic mode compliance with 0 errors (targets Python 3.12)
For immutable value objects that are nested in other Pydantic models or used in parallel test execution, addfrom_attributes=Trueto ConfigDict
Use protocol-based dependency injection with container.get_service() - no isinstance checks with protocols (rely on duck typing instead)
Files:
src/omnibase_core/nodes/node_orchestrator.pysrc/omnibase_core/nodes/node_compute.pysrc/omnibase_core/nodes/node_reducer.py
src/omnibase_core/nodes/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Import nodes from
omnibase_core.nodesusing the unified import path - useNodeCompute,NodeReducer,NodeOrchestrator,NodeEffect(NOT with 'Declarative' suffix)
Files:
src/omnibase_core/nodes/node_orchestrator.pysrc/omnibase_core/nodes/node_compute.pysrc/omnibase_core/nodes/node_reducer.py
🧠 Learnings (3)
📚 Learning: 2025-12-13T20:59:19.537Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T20:59:19.537Z
Learning: Use the 4-Node ONEX Architecture pattern (EFFECT → COMPUTE → REDUCER → ORCHESTRATOR) with unidirectional data flow and no backwards dependencies
Applied to files:
docs/architecture/MUTABLE_STATE_STRATEGY.md
📚 Learning: 2025-12-13T20:59:19.537Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T20:59:19.537Z
Learning: Applies to **/*.py : Do NOT share node instances across threads - use thread-local instances or separate instances per thread for NodeCompute, NodeEffect, NodeReducer, NodeOrchestrator
Applied to files:
src/omnibase_core/nodes/node_orchestrator.pysrc/omnibase_core/nodes/node_compute.py
📚 Learning: 2025-12-13T20:59:19.537Z
Learnt from: CR
Repo: OmniNode-ai/omnibase_core PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T20:59:19.537Z
Learning: ModelIntent pattern should be used in REDUCER nodes for pure FSM transitions without direct side effects
Applied to files:
src/omnibase_core/nodes/node_reducer.py
🪛 LanguageTool
docs/architecture/MUTABLE_STATE_STRATEGY.md
[style] ~387-~387: This phrase is redundant. Consider writing “point” or “time”.
Context: ...e changes - State reconstruction at any point in time - Debugging complex distributed scenari...
(MOMENT_IN_TIME)
🪛 markdownlint-cli2 (0.18.1)
docs/architecture/MUTABLE_STATE_STRATEGY.md
230-230: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
239-239: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
- GitHub Check: Tests (Split 18/20)
- GitHub Check: Tests (Split 9/20)
- GitHub Check: Tests (Split 13/20)
- GitHub Check: Tests (Split 19/20)
- GitHub Check: Tests (Split 17/20)
- GitHub Check: Tests (Split 20/20)
- GitHub Check: Tests (Split 14/20)
- GitHub Check: Tests (Split 10/20)
- GitHub Check: Tests (Split 12/20)
- GitHub Check: Tests (Split 5/20)
- GitHub Check: Tests (Split 4/20)
- GitHub Check: Tests (Split 15/20)
- GitHub Check: Tests (Split 3/20)
- GitHub Check: Tests (Split 2/20)
- GitHub Check: claude-review
🔇 Additional comments (5)
docs/architecture/MUTABLE_STATE_STRATEGY.md (2)
84-94: Protocol injection example is well-structured.The example correctly demonstrates registering a thread-safe cache via container and using it with NodeCompute. This aligns with the OMN-700 pattern and coding guidelines for using
container.register_service().
471-493: All referenced documentation files exist in the repository. No broken links found.src/omnibase_core/nodes/node_reducer.py (1)
48-69: Thread Safety documentation is comprehensive and accurate.The updated docstring clearly documents:
- MVP design rationale
- Specific mutable state components (fsm_contract, FSM execution state)
- Current limitations (not thread-safe)
- Production path (external state stores)
- Mitigation strategy (thread-local instances)
This aligns with the coding guidelines and the architecture document. Based on learnings, the thread-local instance recommendation correctly addresses the "Do NOT share node instances across threads" guideline.
src/omnibase_core/nodes/node_compute.py (1)
82-99: Thread Safety documentation correctly captures dual-mode behavior.The updated docstring accurately describes:
- MVP design rationale for mutable state
- Specific mutable components (_cache, computation_registry, computation_metrics)
- Thread-safety guarantee in pure mode (no mutable state accessed)
- Caveat that injected implementations determine thread safety
- Production path via protocol injection (OMN-700)
The distinction between pure mode (thread-safe) and injected mode (depends on implementation) is valuable for users choosing their deployment model. Based on learnings, this aligns with the coding guidelines.
src/omnibase_core/nodes/node_orchestrator.py (1)
36-57: Thread Safety documentation is thorough and forward-looking.The updated docstring comprehensively documents:
- MVP design rationale for mutable workflow state
- Specific mutable components (workflow_definition, active executions, step completion, context)
- Current limitations (not thread-safe)
- Production path mentioning lease-based coordination (aligns with Phase 3 in roadmap)
- Mitigation strategy (thread-local instances)
The reference to "lease-based coordination" correctly anticipates Phase 3 of the production roadmap. Based on learnings, the thread-local recommendation aligns with the coding guidelines.
Summary
Changes
Node Documentation Updates
node_compute.py): Expanded Thread Safety section with MVP design decision, mutable state components (_cache,computation_registry,computation_metrics), and production path via protocol injection (OMN-700)node_reducer.py): Expanded Thread Safety section with MVP design decision, mutable FSM state components, and future external state store plansnode_orchestrator.py): Expanded Thread Safety section with MVP design decision, mutable workflow state components, and lease-based coordination roadmapNew Architecture Document
docs/architecture/MUTABLE_STATE_STRATEGY.md(498 lines): Comprehensive documentation including:Acceptance Criteria (OMN-704)
Test Plan
poetry run mypypasses for all modified node filesRelated Issues
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.