Accepted (Supersedes ADR-0001)
While ADR-0001 proposed a hybrid C#/Python architecture, the project has been successfully implemented using Python exclusively. This decision reflects the actual implementation and provides clarity on the technology stack in use.
We will use a Python-only architecture for the entire codeflow-engine project with the following stack:
- Python 3.12+ for all application code
- FastAPI for REST API and server components
- Flask-SocketIO for real-time WebSocket communication
- PostgreSQL with SQLAlchemy and Alembic for data persistence
- Redis for caching and queue management
- Poetry for dependency management
- Docker for containerization and deployment
- Python 3.12+ with type hints for type safety
- Pydantic v2 for data validation and settings management
- Structlog for structured JSON logging
- OpenAI GPT models (GPT-4, GPT-3.5)
- Anthropic Claude models
- Mistral AI and Groq for alternative providers
- AutoGen for multi-agent orchestration
- PyGithub for GitHub API
- GitPython for Git operations
- aiohttp for async HTTP requests
- websockets for real-time communication
- PostgreSQL (via psycopg2-binary)
- SQLAlchemy for ORM
- Alembic for migrations
- Redis for caching
- OpenTelemetry SDK for distributed tracing
- Prometheus metrics (via prometheus-client)
- Sentry for error tracking
- DataDog for monitoring
A thorough evaluation was conducted comparing multiple architectural approaches and technology stacks.
Technology: Python 3.12+ with FastAPI, async/await, Pydantic
Pros:
- Rich AI/ML ecosystem (OpenAI, Anthropic, LangChain, AutoGen)
- Rapid development and iteration
- Single language reduces complexity
- Strong async capabilities (asyncio, aiohttp)
- Excellent type safety with Pydantic v2 and type hints
- Mature deployment tooling (Docker, K8s)
- Lower learning curve for data science teams
Cons:
- Lower raw CPU performance vs compiled languages
- Global Interpreter Lock (GIL) limits CPU-bound parallelism
- Higher memory footprint
- Slower cold start times
Separation of Concerns:
- Modular package structure (
codeflow_engine.*) - Plugin system for extensibility
- Service layer pattern for business logic
- Repository pattern for data access
- Clear API boundaries with FastAPI routers
Technology: Rust + Python hybrid via PyO3/maturin
Pros:
- Exceptional performance (comparable to C++)
- Memory safety without garbage collection
- Zero-cost abstractions
- Growing ecosystem (Tokio, Actix, Rocket)
- Excellent concurrency model
- Python integration via PyO3
Cons:
- Steeper learning curve (ownership/borrowing)
- Smaller AI/ML ecosystem
- Longer compile times
- More complex debugging across language boundary
- Limited team expertise in Rust
- Overhead of FFI (Foreign Function Interface)
Separation of Concerns:
- Could isolate performance-critical paths (e.g., data processing, parsing)
- Python for orchestration, Rust for computation
- Clear FFI boundaries
Decision: Not chosen for initial implementation due to added complexity and lack of immediate performance requirements. Remains viable for future optimization.
Technology: C# for services + Python for AI/ML
Pros:
- Excellent performance (JIT + AOT compilation)
- Strong type system
- Mature ecosystem (.NET, ASP.NET Core)
- Good async/await support
- Native cloud support (Azure)
- Cross-platform (.NET 8+)
Cons:
- Weaker AI/ML ecosystem vs Python
- Requires gRPC or REST for inter-service communication
- Increased deployment complexity
- Split team expertise required
- More complex build pipelines
- Limited direct access to Python AI libraries
Separation of Concerns:
- C# for core engine, APIs, infrastructure
- Python for AI/ML processing
- gRPC for communication (ADR-0002)
- Clear service boundaries
Decision: Proposed in ADR-0001 but not implemented. The communication overhead and complexity outweighed benefits given Python's adequate performance for our workload.
Technology: Go for services + Python for AI/ML
Pros:
- Fast compilation and execution
- Excellent concurrency (goroutines)
- Simple deployment (single binary)
- Good performance
- Strong standard library
- Lower memory usage than Python
Cons:
- Limited AI/ML ecosystem
- Less mature web framework ecosystem vs Python/C#
- Weaker type system vs Rust/C#
- No native async/await (goroutines are different model)
- Would still need Python for AI features
Separation of Concerns:
- Go for high-throughput APIs
- Python for AI processing
- REST/gRPC communication
Decision: Not chosen. Go doesn't provide sufficient advantage over Python for our use case, and still requires Python for AI capabilities.
Technology: Python (AI) + Rust (performance) + C#/Go (services)
Pros:
- Best tool for each job
- Maximum performance potential
- Clear separation by language boundaries
Cons:
- Extreme complexity
- Multiple deployment pipelines
- Cross-language debugging difficulties
- Team fragmentation
- Increased operational overhead
- Version management nightmare
Decision: Rejected as over-engineering for current scale. May reconsider at massive scale (10x+ current load).
-
AI/ML Ecosystem Dominance: Python is the undisputed leader for AI/ML with direct access to:
- LLM providers (OpenAI, Anthropic, Mistral)
- Multi-agent frameworks (AutoGen, LangChain, CrewAI)
- Data processing (NumPy, Pandas)
- ML libraries (scikit-learn, transformers)
-
Development Velocity: Single language stack accelerates:
- Feature development (no cross-language coordination)
- Debugging (single toolchain)
- Testing (unified test framework)
- Onboarding (one language to learn)
-
Sufficient Performance: For our workload characteristics:
- I/O-bound operations (API calls, database queries)
- Async Python handles I/O efficiently
- CPU-intensive work is minimal (done by LLM APIs)
- Redis caching reduces computation needs
-
Separation of Concerns via Python: Python supports clean architecture through:
- Domain Layer: Core business logic in
engine/codeflow_engine/engine.py - Application Layer: Use cases in
engine/codeflow_engine/actions/ - Infrastructure Layer: Integrations in
engine/codeflow_engine/integrations/ - Presentation Layer: APIs in
engine/codeflow_engine/server.py - Plugin System: Extensibility via
codeflow_engine.actionsnamespace - Type Safety: Pydantic models enforce contracts
- Domain Layer: Core business logic in
-
Operational Simplicity: Single runtime reduces:
- Container image size
- Deployment complexity
- Monitoring overhead
- Security surface area
We acknowledge Python-only has limitations. Future scenarios that might require multi-language:
- Performance Bottlenecks: If profiling reveals CPU-bound hotspots consuming >30% resources
- Real-time Requirements: If latency requirements drop below 50ms p99
- Memory Constraints: If memory usage becomes problematic at scale
- Concurrency Needs: If need for true parallel CPU work (not I/O) emerges
In these cases, hybrid approaches (Python + Rust/C#) remain viable migration paths.
- Simplified Architecture: Single codebase, single deployment pipeline
- Faster Iteration: No gRPC communication layer needed
- Better Type Safety: Python 3.12+ with Pydantic v2 provides strong typing
- Rich AI Ecosystem: Direct access to all Python AI/ML libraries
- Easier Testing: Single language testing framework
- Lower Maintenance: Fewer moving parts, simpler debugging
- CPU Performance: Lower raw CPU performance compared to C#
- Memory Usage: Python's memory footprint is higher
- GIL Limitations: Global Interpreter Lock affects multi-threaded CPU work
- Startup Time: Slower cold starts compared to compiled languages
- Use async I/O extensively to avoid GIL bottlenecks
- Leverage Redis for caching to reduce CPU load
- Scale horizontally with multiple worker processes
- Use PyPy or Cython for performance-critical sections if needed
While using a single language, the architecture maintains clear separation of concerns through:
engine/codeflow_engine/
├── engine.py # Domain Layer (core business logic)
├── actions/ # Application Layer (use cases, workflows)
├── integrations/ # Infrastructure Layer (external services)
├── ai/ # AI/ML Layer (LLM providers, agents)
├── database/ # Data Layer (models, repositories)
├── config/ # Configuration Layer (settings, validation)
├── security/ # Security Layer (auth, validation)
└── server.py # Presentation Layer (API endpoints)
-
Bounded Contexts: Clear module boundaries
actions.*- Workflow automation contextintegrations.*- External service contextai.*- AI/ML processing contextsecurity.*- Security and auth context
-
Dependency Inversion: Abstractions over implementations
# Abstract base in domain layer class LLMProvider(ABC): @abstractmethod async def generate(self, prompt: str) -> str: ... # Concrete implementations in infrastructure layer class OpenAIProvider(LLMProvider): ... class AnthropicProvider(LLMProvider): ...
-
Plugin Architecture: Extensibility without core changes
[tool.poetry.plugins."codeflow.actions"] "platform_detector" = "codeflow_engine.actions.platform_detector:PlatformDetector"
-
Repository Pattern: Data access abstraction
class WorkflowRepository: async def save(self, workflow: Workflow) -> None: ... async def find_by_id(self, id: str) -> Workflow: ...
-
Service Layer: Business logic orchestration
class WorkflowService: def __init__(self, repo: WorkflowRepository, llm: LLMProvider): self._repo = repo self._llm = llm
- Each module can be tested independently
- Clear interfaces (Pydantic models) between layers
- Minimal coupling through dependency injection
- Type safety enforced at module boundaries
While monolithic, the architecture supports future microservices extraction:
- Each bounded context could become a service
- FastAPI routers already provide API boundaries
- Database schema separates concerns (workflow, auth, audit)
- Event-driven patterns in place for async processing
This demonstrates that separation of concerns is achieved through software design patterns, not just language choice.
| Proposed (ADR-0001) | Actual Implementation | Rationale |
|---|---|---|
| C# + Python hybrid | Python-only | Simpler, faster development |
| gRPC communication | Direct Python calls | No cross-language overhead |
| .NET 6+ for core | FastAPI + Flask | Python web frameworks sufficient |
| Separate C# service | Monolithic Python app | Easier deployment and debugging |
If performance becomes a critical issue, migration options include:
- Profile First: Use cProfile, py-spy to identify actual bottlenecks
- Optimize Hot Paths:
- Cython for CPU-intensive Python code
- PyPy for JIT compilation benefits
- Numba for numerical computations
- Strategic Caching: Add Redis caching to frequently accessed data
- Database Optimization: Query optimization, indexing, connection pooling
- Async Improvements: Ensure proper async/await usage throughout
Expected Gains: 2-5x performance improvement with minimal architectural changes
- Identify Candidates: Profile to find CPU-bound bottlenecks
- Extract Module: Create isolated Rust crate for specific function
- PyO3 Integration: Build Python bindings using PyO3/maturin
- Incremental Migration: Replace Python module with Rust extension
- Examples of Good Candidates:
- Large file parsing (YAML, JSON processing)
- Data transformation pipelines
- Text processing/tokenization
- Compression/decompression
- Cryptographic operations
Example Structure:
engine/codeflow_engine/
├── core/ # Python code
├── extensions/
│ └── fast_parser/ # Rust extension
│ ├── Cargo.toml
│ ├── src/lib.rs # Rust code
│ └── pyproject.toml # maturin config
Expected Gains: 10-50x on CPU-bound operations, maintains Python ecosystem benefits
Why Rust over C#/Go:
- Zero-cost abstractions: Performance matches C/C++
- Memory safety: No garbage collection, no null pointers
- Python integration: PyO3 provides excellent ergonomics
- Growing adoption: Used by Polars, Pydantic v2 core, Ruff linter
- Future-proof: Mozilla, AWS, Microsoft investing heavily
- Extract Service: Identify service boundary (e.g., workflow engine)
- Define API Contract: gRPC or REST interface
- Implement in C#: ASP.NET Core service
- Dual Deployment: Run alongside Python services
- Gradual Migration: Move components incrementally
Best For: When you need entire service rewrite, not just performance optimization
Expected Gains: 5-10x performance, but adds operational complexity
- Service Extraction: Break monolith into 3-5 core services
- Language Selection: Choose best tool per service
- Python: AI/ML processing, orchestration
- Rust: High-throughput data processing
- C#/Go: Business logic APIs
- Communication: gRPC for inter-service, REST for external
- Independent Deployment: Each service scales independently
Best For: 10x+ scale, clear team specialization
Expected Gains: Horizontal scalability, technology flexibility, but significant complexity
Is performance actually a problem? (Profile first!)
├── No → Stay with Python-only
└── Yes → Is it CPU-bound or I/O-bound?
├── I/O-bound → Optimize async, add caching, horizontal scaling
└── CPU-bound → Profile specific functions
├── <5% of codebase hot → Use Rust via PyO3
├── Entire module/service → Consider C# microservice
└── Multiple services → Evaluate polyglot architecture
For context, here's how a Rust extension might work:
Python interface (engine/codeflow_engine/parsers/fast.py):
from .extensions.fast_parser import parse_yaml_fast
def parse_workflow(content: str) -> dict:
"""Parse workflow YAML with Rust performance."""
return parse_yaml_fast(content)Rust implementation (extensions/fast_parser/src/lib.rs):
use pyo3::prelude::*;
use serde_yaml;
#[pyfunction]
fn parse_yaml_fast(content: &str) -> PyResult<PyObject> {
let parsed: serde_yaml::Value = serde_yaml::from_str(content)?;
// Convert to Python dict
Ok(parsed.into_py(py))
}
#[pymodule]
fn fast_parser(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(parse_yaml_fast, m)?)?;
Ok(())
}This approach maintains Python's developer experience while adding Rust's performance where needed.
- ADR-0002: gRPC Communication - Not implemented, kept for reference
- ADR-0005: Configuration Management - Implemented with Pydantic
- ADR-0011: Data Persistence Strategy - Implemented with PostgreSQL
- ADR-0020: Package Naming Convention - codeflow_engine package name
- Python 3.12 Release Notes: https://docs.python.org/3/whatsnew/3.12.html
- FastAPI Documentation: https://fastapi.tiangolo.com/
- Pydantic v2 Documentation: https://docs.pydantic.dev/
- Rust Book: https://doc.rust-lang.org/book/
- PyO3 User Guide: https://pyo3.rs/
- Maturin (Rust-Python build tool): https://github.com/PyO3/maturin
- C# .NET Documentation: https://learn.microsoft.com/en-us/dotnet/
- Go Documentation: https://go.dev/doc/
- Domain-Driven Design: https://www.domainlanguage.com/ddd/
- Clean Architecture: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
- Microservices Patterns: https://microservices.io/patterns/
- Pydantic v2 Core (Rust): https://github.com/pydantic/pydantic-core
- Ruff Linter (Rust): https://github.com/astral-sh/ruff
- Polars DataFrame (Rust): https://github.com/pola-rs/polars