ADR-006: Strangler Fig Migration¶
Status¶
Accepted
Context¶
DCAF has an existing codebase with:
- Working agents (
cmd_agent.py,aws_agent.py,k8s_agent.py) - Established
AgentProtocolinterface - Production usage via
agent_server.py - Existing schemas and tools
We want to introduce a new Core abstraction layer without:
- Breaking existing functionality
- Requiring a big-bang migration
- Disrupting current users
- Losing the ability to rollback
Decision¶
We apply the Strangler Fig Pattern:
- Build the new Core system alongside the existing system
- Route new functionality through Core
- Gradually migrate existing agents
- Eventually remove the old system
Implementation Strategy¶
dcaf/
├── agents/ # EXISTING - Keep working
│ ├── cmd_agent.py
│ ├── aws_agent.py
│ └── k8s_agent.py
├── agent_server.py # EXISTING - Still works with AgentProtocol
├── schemas/ # EXISTING - Reused by Core
├── tools.py # EXISTING - Reused by Core
│
└── core/ # NEW - Built in parallel
├── domain/
├── application/
└── adapters/
Migration Phases¶
Phase 1: Parallel Construction
- Build Core in dcaf/core/ without touching existing code
- Existing agents continue to work unchanged
- Core can import from existing schemas/ and tools.py
Phase 2: Facade Adapter
- Create a BedrockDirectAdapter that wraps existing BedrockLLM
- Allows Core use cases to work with existing infrastructure
- Proves Core architecture without new framework dependencies
Phase 3: New Agents on Core
- New agents (e.g., using Agno) are built on Core
- Old and new agents coexist in agent_server.py
- Both implement AgentProtocol for compatibility
Phase 4: Gradual Migration - Migrate existing agents one at a time - Each migration is a separate, reversible change - Old implementations kept until migration is verified
Phase 5: Cleanup
- Remove old agent implementations
- Core becomes the primary implementation
- Old AgentProtocol may remain for backwards compatibility
Compatibility Bridge¶
# dcaf/core/adapters/inbound/agent_protocol_bridge.py
class CoreAgentBridge:
"""Wraps a Core use case to implement legacy AgentProtocol."""
def __init__(self, execute_agent_service: AgentService):
self._service = execute_agent_service
def invoke(self, messages: Dict) -> AgentMessage:
# Convert legacy format to Core request
request = self._convert_to_request(messages)
response = self._service.execute(request)
return self._convert_to_agent_message(response)
Consequences¶
Positive Consequences¶
- Zero downtime during migration
- Easy rollback if issues discovered
- Can migrate incrementally by agent
- New features can use Core immediately
- Team can learn Core patterns on new work
Negative Consequences¶
- Temporary code duplication
- Two mental models during transition
- Need to maintain compatibility layer
- Longer overall timeline than big-bang
Implementation Status¶
Current Phase: 3 - Unified Surface (Complete)
As of 2025-02, the unified surface is implemented:
- ✅
dcaf.core.create_app()exposes both V1 and V2 endpoints - ✅ V2 endpoints (
/api/chat,/api/chat-stream,/api/chat-ws) use V2 code path - ✅ V1 endpoints (
/api/sendMessage,/api/sendMessageStream) use V1 code path - ✅ V1 handler functions extracted for reuse by unified app
- ✅ Tests verify code path separation (
tests/core/test_request_fields.py)
Key V2 Features (not available in V1):
| Feature | Description |
|---|---|
_request_fields |
Top-level request fields forwarded to agent |
meta_data.request_context |
Request fields echoed back in response |
| WebSocket support | /api/chat-ws bidirectional streaming |
Next Steps (Phase 4-5):
- Migrate existing agents to Core one at a time
- Eventually deprecate
dcaf.agent_server.create_chat_app()for new projects
Related ADRs¶
- ADR-001: Clean Architecture
- ADR-003: Adapter Pattern for Frameworks
- ADR-007: Lowercase Chat Endpoints