DSPEx Phase 2 Implementation Plan: Foundation-Aware Architecture
Executive Summary
After analyzing Foundation v0.1.1 on Hex, the comprehensive integration documentation, and the detailed DSPy source mapping, this document provides a Foundation-aware implementation strategy for DSPEx Phase 2. While Foundation v0.1.1 shows promise as an infrastructure library, I recommend proceeding without Foundation as a dependency for the immediate Phase 2 implementation, with a clear migration path for future integration.
Foundation Analysis Summary
Foundation v0.1.1 Assessment
Strengths:
- Comprehensive infrastructure primitives (fuse, hammer, telemetry, poolboy)
- Well-documented event system and error handling
- Service registry and configuration management
- Circuit breaker and rate limiting capabilities
Concerns:
- Extremely early stage (only v0.1.0 and v0.1.1 releases)
- Production readiness unclear for core DSPEx functionality
- Potential over-engineering for Phase 2 scope
- Dependency risk on a new, unproven library
Integration Documentation Analysis
The Foundation integration documentation in docs/001_initial/104_claude_synthesizeGemini_foundationIntegration.md
demonstrates sophisticated patterns but assumes a mature Foundation library with capabilities that may exceed v0.1.1’s current state.
Key Integration Patterns Identified:
- Service registry-based client discovery
- Foundation error context wrapping
- Telemetry integration with correlation IDs
- Configuration-driven circuit breakers
- Event-driven architecture with Foundation events
Recommended Implementation Strategy
Phase 2A: Native Elixir Implementation (Weeks 1-2)
Rationale: Build on proven Elixir libraries rather than early-stage Foundation
Core Dependencies:
# mix.exs - Production-Ready Dependencies
{:req, "~> 0.5"}, # HTTP client (mature)
{:fuse, "~> 2.5"}, # Circuit breaker (Foundation uses this)
{:cachex, "~> 3.6"}, # Caching (high performance)
{:jason, "~> 1.4"}, # JSON processing
{:telemetry, "~> 1.2"}, # Native telemetry
{:poolboy, "~> 1.5"}, # Connection pooling
Implementation Architecture:
# Core Client GenServer (Foundation-Ready)
defmodule DSPEx.Client do
use GenServer
require Logger
# Foundation-compatible correlation ID support
def start_link(opts) do
name = Keyword.fetch!(opts, :name)
GenServer.start_link(__MODULE__, opts, name: name)
end
def request(client_name, messages, opts \\ []) do
correlation_id = Keyword.get(opts, :correlation_id) || generate_correlation_id()
# Native telemetry (Foundation-compatible events)
:telemetry.span([:dspex, :client, :request], %{client: client_name}, fn ->
result = GenServer.call(client_name, {:request, messages, opts}, 30_000)
{result, %{correlation_id: correlation_id}}
end)
end
def init(opts) do
config = Keyword.fetch!(opts, :config)
name = Keyword.fetch!(opts, :name)
# Circuit breaker setup (Foundation-compatible)
fuse_name = :"fuse_#{name}"
:fuse.install(fuse_name, {:standard, 5, 10_000})
# Cache setup (Foundation-compatible)
cache_name = :"cache_#{name}"
Cachex.start_link(name: cache_name, limit: 1000)
state = %{
name: name,
config: config,
fuse_name: fuse_name,
cache_name: cache_name
}
{:ok, state}
end
def handle_call({:request, messages, opts}, _from, state) do
correlation_id = Keyword.get(opts, :correlation_id)
# Foundation-style error context (manual implementation)
try do
cache_key = build_cache_key(messages, state.config)
result = Cachex.get_or_set(state.cache_name, cache_key, fn ->
:fuse.ask(state.fuse_name, fn ->
http_post(messages, state.config, correlation_id)
end)
end)
{:reply, result, state}
catch
kind, reason ->
# Foundation-compatible error structure
error = %{
type: :client_error,
reason: reason,
correlation_id: correlation_id,
context: %{client: state.name, kind: kind}
}
{:reply, {:error, error}, state}
end
end
defp http_post(messages, config, correlation_id) do
body = %{model: config.model, messages: messages}
headers = [
{"authorization", "Bearer #{config.api_key}"},
{"content-type", "application/json"},
{"x-correlation-id", correlation_id || ""}
]
case Req.post(config.base_url, json: body, headers: headers) do
{:ok, %{status: 200, body: response_body}} ->
{:ok, response_body}
{:ok, %{status: status, body: body}} ->
raise "API request failed with status #{status}: #{inspect(body)}"
{:error, reason} ->
raise "HTTP request failed: #{inspect(reason)}"
end
end
# Foundation-compatible utilities
defp generate_correlation_id do
:crypto.strong_rand_bytes(8) |> Base.encode16(case: :lower)
end
defp build_cache_key(messages, config) do
term = {messages, config.model}
:crypto.hash(:sha256, :erlang.term_to_binary(term))
end
end
Phase 2B: Foundation-Ready Abstractions (Week 3)
Service Layer Patterns (Foundation-Compatible):
# Abstract service layer for future Foundation integration
defmodule DSPEx.Services.Base do
@moduledoc """
Base service patterns compatible with Foundation service registry.
"""
defmacro __using__(opts) do
quote do
use GenServer
require Logger
# Foundation-compatible service registration
def start_link(init_args) do
name = Keyword.get(init_args, :name) || __MODULE__
GenServer.start_link(__MODULE__, init_args, name: via_tuple(name))
end
# Service discovery (manual implementation, Foundation-ready)
def via_tuple(name) do
# Native implementation that can be swapped for Foundation.ServiceRegistry
{:global, {__MODULE__, name}}
end
def lookup(service_name) do
case :global.whereis_name({__MODULE__, service_name}) do
:undefined -> {:error, :not_found}
pid -> {:ok, pid}
end
end
# Foundation-compatible telemetry
def emit_telemetry(event, measurements, metadata \\ %{}) do
:telemetry.execute([:dspex | event], measurements, metadata)
end
unquote(block)
end
end
end
Configuration Management (Foundation-Compatible):
# Configuration module ready for Foundation integration
defmodule DSPEx.Config do
@moduledoc """
Configuration management compatible with Foundation.Config patterns.
"""
# Native implementation that mirrors Foundation.Config API
def get(path, default \\ nil) do
case get_config_value(path) do
nil -> default
value -> value
end
end
def put(path, value) do
update_config_value(path, value)
end
# Foundation-compatible schema validation
def validate(config, schema) do
# Implement basic validation that can be upgraded to Foundation.Config.validate
case validate_config_structure(config, schema) do
:ok -> {:ok, config}
{:error, errors} -> {:error, errors}
end
end
# Private implementations that can be swapped for Foundation
defp get_config_value(path) do
path
|> Enum.reduce(Application.get_all_env(:dspex), fn key, acc ->
case acc do
map when is_map(map) -> Map.get(map, key)
list when is_list(list) -> Keyword.get(list, key)
_ -> nil
end
end)
end
defp update_config_value(path, value) do
# Implementation for nested config updates
# Can be replaced with Foundation.Config.put/2
Application.put_env(:dspex, hd(path), value)
end
defp validate_config_structure(config, schema) do
# Basic validation logic that matches Foundation patterns
:ok
end
end
Phase 2C: Migration-Ready Error Handling (Week 4)
Error System (Foundation Pattern-Compatible):
defmodule DSPEx.Error do
@moduledoc """
Error handling system compatible with Foundation.Error patterns.
"""
defstruct [:type, :message, :context, :correlation_id, :timestamp]
@type t :: %__MODULE__{
type: atom(),
message: String.t(),
context: map(),
correlation_id: String.t() | nil,
timestamp: DateTime.t()
}
# Foundation-compatible error creation
def new(type, message, opts \\ []) do
%__MODULE__{
type: type,
message: message,
context: Keyword.get(opts, :context, %{}),
correlation_id: Keyword.get(opts, :correlation_id),
timestamp: DateTime.utc_now()
}
end
# Foundation-compatible error context
def with_context(correlation_id, metadata, fun) do
# Manual context implementation that can be replaced with Foundation.ErrorContext
try do
fun.()
catch
kind, reason ->
error = new(:execution_error, "Operation failed",
context: %{
kind: kind,
reason: reason,
metadata: metadata
},
correlation_id: correlation_id
)
{:error, error}
end
end
end
# Context macro for Foundation-style error handling
defmodule DSPEx.ErrorContext do
defmacro with_error_context(correlation_id, metadata, do: block) do
quote do
DSPEx.Error.with_context(unquote(correlation_id), unquote(metadata), fn ->
unquote(block)
end)
end
end
end
Foundation Integration Strategy (Future Phase)
When to Integrate Foundation
Trigger Conditions:
- Foundation reaches v0.2.0+ with proven stability
- DSPEx Phase 2 is successfully completed and tested
- Performance benchmarks confirm Foundation adds value
- Community adoption validates Foundation’s production readiness
Migration Path
Step 1: Configuration Migration
# Replace DSPEx.Config with Foundation.Config
# config/config.exs
config :dspex,
foundation_integration: true
# lib/dspex/config.ex
if Application.get_env(:dspex, :foundation_integration) do
defdelegate get(path, default), to: Foundation.Config
defdelegate put(path, value), to: Foundation.Config
defdelegate validate(config, schema), to: Foundation.Config
else
# Existing DSPEx.Config implementation
end
Step 2: Service Registry Migration
# Replace via_tuple implementation
def via_tuple(name) do
if foundation_enabled?() do
Foundation.ServiceRegistry.via_tuple(:dspex, name)
else
{:global, {__MODULE__, name}}
end
end
Step 3: Error Handling Migration
# Replace DSPEx.Error with Foundation.Error
def with_context(correlation_id, metadata, fun) do
if foundation_enabled?() do
context = Foundation.ErrorContext.new(__MODULE__, :operation,
correlation_id: correlation_id,
metadata: metadata
)
Foundation.ErrorContext.with_context(context, fun)
else
# Existing DSPEx.Error implementation
end
end
Testing Strategy
Phase 2 Testing (Foundation-Compatible)
defmodule DSPEx.Test.Support do
@moduledoc """
Test support utilities designed for Foundation compatibility.
"""
def setup_test_environment do
# Setup that works with or without Foundation
correlation_id = generate_test_correlation_id()
# Configure test clients
{:ok, _} = DSPEx.Client.start_link(
name: :test_client,
config: %{
api_key: "test-key",
model: "test-model",
base_url: "http://localhost:#{test_port()}"
}
)
%{correlation_id: correlation_id}
end
def mock_foundation_events(correlation_id) do
# Mock that can be replaced with Foundation.Events when available
Agent.start_link(fn -> [] end, name: {:global, :test_events})
on_exit(fn ->
Agent.stop({:global, :test_events})
end)
end
defp generate_test_correlation_id do
"test_#{:crypto.strong_rand_bytes(4) |> Base.encode16(case: :lower)}"
end
defp test_port, do: 8080
end
Foundation Integration Tests (Future)
# Test that validates Foundation integration when available
defmodule DSPEx.Foundation.IntegrationTest do
use ExUnit.Case
@moduletag :foundation_integration
setup do
if foundation_available?() do
# Setup Foundation test environment
DSPEx.Foundation.setup_test_environment()
else
# Skip Foundation integration tests
:skip
end
end
test "Foundation service registry integration", %{namespace: namespace} do
# Test that would only run when Foundation is available
assert {:ok, _pid} = Foundation.ServiceRegistry.lookup(namespace, :test_service)
end
defp foundation_available? do
Code.ensure_loaded?(Foundation)
end
end
Implementation Timeline
Phase 2 (Immediate - 4 weeks)
Week 1: Core DSPEx.Client with native Elixir libraries
- GenServer-based client with circuit breaker (fuse)
- Caching layer (cachex) with deterministic keys
- HTTP abstraction (req) with retry logic
- Native telemetry events (Foundation-compatible format)
Week 2: Service abstractions and configuration
- Service base patterns (Foundation-ready)
- Configuration management (Foundation-compatible API)
- Error handling system (Foundation pattern matching)
- Test infrastructure with mock support
Week 3: Adapter and execution layers
- DSPEx.Adapter for provider abstraction
- DSPEx.Program and DSPEx.Predict execution engine
- Integration with client layer
- Comprehensive error propagation
Week 4: Evaluation and testing
- DSPEx.Evaluate with concurrent execution
- Performance benchmarking
- Integration testing across all layers
- Documentation and examples
Phase 3 (Foundation Integration - Future)
When Foundation v0.2.0+ is available:
- Gradual migration of configuration system
- Service registry replacement
- Enhanced error context and telemetry
- Event-driven architecture patterns
- Advanced circuit breaker configurations
Success Metrics
Phase 2 Completion Criteria
Functional:
- ✅ All client operations working with real LLM APIs
- ✅ Circuit breaker activation/recovery under failure conditions
- ✅ Cache hit rates >90% for repeated requests
- ✅ End-to-end signature → client → response workflow
Performance:
- ✅ Sub-100ms cache hits
- ✅ <5s circuit breaker recovery times
- ✅ 100+ concurrent requests per client
- ✅ Memory usage stable under extended operation
Quality:
- ✅ Test coverage >95% for all new modules
- ✅ Zero dialyzer warnings
- ✅ All ExUnit tests passing consistently
- ✅ Foundation-compatible patterns validated
Foundation Integration Criteria (Future)
Prerequisites:
- Foundation v0.2.0+ released with proven stability
- Community adoption and production usage examples
- Performance benefits demonstrated over native implementation
- Migration path tested and documented
Risk Assessment
Phase 2 Risks (Low-Medium)
Circuit Breaker Tuning
- Risk: Suboptimal failure thresholds
- Mitigation: Configurable parameters, extensive testing
Performance Under Load
- Risk: GenServer bottlenecks
- Mitigation: Load testing, pooling strategies, async patterns
Integration Complexity
- Risk: Adapter layer coupling
- Mitigation: Well-defined interfaces, comprehensive mocking
Foundation Integration Risks (Medium-High)
Early Adoption Risk
- Risk: Foundation v0.1.1 instability
- Mitigation: Avoided by not using Foundation in Phase 2
Over-Engineering
- Risk: Foundation complexity exceeds DSPEx needs
- Mitigation: Gradual migration, feature-by-feature evaluation
Migration Complexity
- Risk: Breaking changes during Foundation upgrade
- Mitigation: Compatible patterns from Phase 2, comprehensive test coverage
Conclusion
This Foundation-aware implementation strategy provides the best of both worlds:
- Immediate Progress: Phase 2 proceeds with proven Elixir libraries
- Future-Proofed: Architecture patterns compatible with Foundation integration
- Risk Mitigation: Avoids dependency on early-stage Foundation v0.1.1
- Clear Migration Path: Well-defined upgrade strategy when Foundation matures
The approach leverages Foundation’s design patterns without the dependency risk, creating a robust DSPEx implementation that can seamlessly integrate Foundation when it reaches production maturity.
<function_calls>