SOIT Docs
Architecture

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, and step_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 familyConnects to
llmOpenAI, OpenAI-compatible endpoints, DeepSeek, Anthropic (see the provider matrix)
toolsHTTP tools, MCP servers, governed external calls
vectorMilvus vector collections
storageMinIO / S3-compatible object storage
secretsVault / KMS secret resolution
pluginsPlugin 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.

On this page