Ports and Adapters
Route SOIT external systems through stable kernel ports and replaceable adapters.
Ports are stable interfaces owned by the kernel (app/kernel/ports/). Adapters (app/adapters/) implement those interfaces for concrete providers and infrastructure. Together they are how SOIT makes governance unavoidable: modules can only reach external systems through ports, and ports are where policy is enforced.
Port responsibilities
Ports are the policy enforcement point for external systems. Every boundary crossing gets consistent handling for:
- Timeouts and retry policy (retries for idempotent operations only).
- Audit logging tied to
request_id,run_id, andstep_id. - Rate limits per tenant and workspace.
- Secret reference resolution through the secrets port — plaintext never enters business code.
- Egress checks (deny-by-default).
- Run and step context propagation, so external calls appear in traces.
- Provider error normalization.
Shared gateway behavior (timeout/retry/run-id/error helpers) lives in app/kernel/ports/common/policy.py, so every port applies the same policy the same way.
Adapter responsibilities
Adapters contain provider SDK calls, protocol details, and infrastructure integration — and nothing else. They implement kernel port interfaces and hold no business rules. Current adapter families:
| Adapter family | Connects to |
|---|---|
llm | OpenAI, OpenAI-compatible endpoints, DeepSeek, Anthropic (see the provider matrix) |
tools | HTTP tools, MCP servers, governed external calls |
vector | Milvus vector collections |
storage | MinIO / S3-compatible object storage |
secrets | Vault / KMS secret resolution |
plugins | Plugin package loading and runtime registration |
Wiring
Concrete adapters are bound to ports in app/wiring/ — the only place provider registration is allowed (tests enforce this). Swapping a provider means registering a different adapter; modules and kernel code do not change.
The rule
Modules never call provider SDKs, raw HTTP clients, or external services directly — they call ports only. This is enforced by import-linter, so a bypass is a build failure rather than a code-review debate.
Related pages
- Architecture Overview — where ports sit in the hexagon.
- Secrets and Security — the policies ports enforce.