← Back to 007 ccbuild

02 cc

Documentation for 02_cc from the Ds ex repository.

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

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)

  1. Circuit Breaker Tuning

    • Risk: Suboptimal failure thresholds
    • Mitigation: Configurable parameters, extensive testing
  2. Performance Under Load

    • Risk: GenServer bottlenecks
    • Mitigation: Load testing, pooling strategies, async patterns
  3. Integration Complexity

    • Risk: Adapter layer coupling
    • Mitigation: Well-defined interfaces, comprehensive mocking

Foundation Integration Risks (Medium-High)

  1. Early Adoption Risk

    • Risk: Foundation v0.1.1 instability
    • Mitigation: Avoided by not using Foundation in Phase 2
  2. Over-Engineering

    • Risk: Foundation complexity exceeds DSPEx needs
    • Mitigation: Gradual migration, feature-by-feature evaluation
  3. 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:

  1. Immediate Progress: Phase 2 proceeds with proven Elixir libraries
  2. Future-Proofed: Architecture patterns compatible with Foundation integration
  3. Risk Mitigation: Avoids dependency on early-stage Foundation v0.1.1
  4. 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> [{“id”: “1”, “content”: “Look up Foundation v0.1.1 on Hex and analyze its capabilities”, “status”: “completed”, “priority”: “high”}, {“id”: “2”, “content”: “Read docs/001_initial/104_claude_synthesizeGemini_foundationIntegration.md”, “status”: “completed”, “priority”: “high”}, {“id”: “3”, “content”: “Read docs/001_initial/103_claude_useOurFoundation.md”, “status”: “completed”, “priority”: “high”}, {“id”: “4”, “content”: “Read docs/001_initial/24_gemini_fileByFileDSPyAnalysis.md”, “status”: “completed”, “priority”: “high”}, {“id”: “5”, “content”: “Create docs/007_ccbuild/02_cc.md with Foundation-aware recommendations”, “status”: “completed”, “priority”: “high”}]