| name | hex-arch-check |
| description | Enforce hexagonal architecture (ports & adapters) in this project. Use when
writing new features, refactoring code, reviewing PRs, or when infrastructure
concerns (LangGraph, LangMem, PostgreSQL, Qdrant, LLM providers) leak into
domain or use case layers. Also trigger when the user says "hex arch",
"ports and adapters", "domain purity", or "architecture review".
|
| user-invocable | false |
Hexagonal Architecture Enforcement
This project follows strict hexagonal architecture (ports & adapters). Every new
feature or refactor must respect the layer boundaries.
Layer Rules
Domain (src/rag_concurso/domain/)
- Zero framework dependencies. No imports from
langchain, langgraph, langmem,
fastapi, psycopg, qdrant_client, or any other infrastructure library.
- Contains: entities (value objects), port interfaces (inbound + outbound), use cases.
- Ports are abstract interfaces (
Protocol or ABC) that the domain defines and
adapters implement.
- Use cases orchestrate domain logic by calling outbound ports. They never call
infrastructure directly.
Adapters (src/rag_concurso/adapters/)
- Inbound (
adapters/inbound/): FastAPI routes, schemas. Translate HTTP into
domain use case calls.
- Outbound (
adapters/outbound/): Implement domain ports. These are the only
files that import infrastructure libraries (psycopg, qdrant_client, langchain, etc.).
Infrastructure (src/rag_concurso/infrastructure/)
- Wiring, configuration, graph builders, provider factory.
- May import from adapters and domain.
- Should NOT contain business logic — only orchestration and composition.
When Adding a New Feature
- Start with the domain. Define any new entities and port interfaces first.
- Implement use cases. Write business logic against port interfaces, not concrete classes.
- Write adapters. Implement the ports with the specific technology (Postgres, LangMem, etc.).
- Wire in infrastructure. Connect adapters to use cases in
app.py / provider_factory.py.
Smell Detection
Flag these as architecture violations:
- Domain or use case importing from
langgraph, langmem, langchain, fastapi,
psycopg, qdrant_client, or any concrete adapter.
- Use case calling
store.asearch(), store.aput(), or any store/repo method directly
instead of through a port interface.
- Business logic (scoring, extraction, formatting) living in route handlers or
infrastructure graph builders.
- Entities that depend on ORM or framework types.
How to Fix a Violation
If infrastructure leaks into the domain/use case layer:
- Define a port interface in
domain/ports/outbound/ for the capability needed.
- Move the concrete implementation to an adapter in
adapters/outbound/.
- Have the use case depend on the port, not the adapter.
- Wire the adapter to the port in
infrastructure/app.py or provider_factory.py.
Gotchas
AsyncPostgresStore from LangGraph is an infrastructure detail — use cases should
not call it directly. Wrap it behind a domain port.
ReflectionExecutor from LangMem is infrastructure — the use case should depend on
an abstract interface (e.g., IMemoryExtractor), not the concrete executor.
- Graph node factories in
infrastructure/graphs/ can use adapters directly (they are
infrastructure), but the data they pass around should be domain entities or plain dicts.
- When in doubt about where code belongs, ask: "Could I swap the implementation
(e.g., replace LangMem with a custom solution) without touching the domain layer?"
If no, there's a violation.