Every load-bearing boundary in Actionary is a public specification. Every domain decision is code the team owns.

A thin adapter sits at each open-standard boundary. The domain layer sits inside — hand-written against the entity model and the permission gates it enforces.

Cockburn’s Hexagonal Architecture (2005). Wiggins’s Twelve-Factor App (2011). Chapter 32 of Robert C. Martin’s Clean Architecture (2017) — “Frameworks Are Details.” The pattern is applied consistently, without exceptions. Detail behind every citation lives on the design principles page.

The two claims are one principle

Adapters at the boundary are portable because portability is the whole point of an adapter. Business logic is code because a domain decision belongs in the code that owns the domain. A framework that pretends to know a domain it does not is precisely the coupling Chapter 32 warns against.

What is open at the boundary

Every load-bearing service Actionary depends on is a public specification with multiple compatible implementations.

  • PostgreSQL 16. pg_dump produces a portable backup of the whole stack — including the content_asset_chunks pgvector retrieval index. Every vector row rides along in the standard dump format.
  • S3-API. shared/storage/storage_adapter.py swaps between AWS S3, Cloudflare R2, MinIO, and Backblaze B2 with one env var. Signed URLs and presigned uploads are the standard interface at every endpoint.
  • Redis wire protocol. The upload_indexer_queue list runs on any Redis-protocol-compatible store — Dragonfly, KeyDB, Valkey, Elasticache, Upstash.
  • OpenTelemetry. Traces, metrics, and logs emit through vendor-neutral OTLP. Datadog, Honeycomb, New Relic, Grafana Tempo, AWS X-Ray, Google Cloud Trace — every one is a downstream collector target.
  • Model Context Protocol. The agent surface is exposed through Anthropic’s open specification. Claude Desktop, Cursor, Zed, Sourcegraph Cody, and Continue.dev all speak MCP; external clients register through DCR and receive the same tool catalogue the embedded agent sees.
  • OAuth 2.0 + DCR + PKCE. The SSO and MCP-client trust flows use the IETF standard. Any DCR-compliant client can register. Any OIDC IdP can be the identity source.
  • HTTP + OpenAPI. The public REST API is described by the schema at /.well-known/openapi.json. Every SDK generation, every API explorer, every SIEM ingestion pipeline reads a standard.
  • Any LLM provider. Anthropic, OpenAI, Bedrock, Azure OpenAI, and Gemini all ship as full streaming adapters. Tenant settings pick primary and fallback; per-step failover switches on provider_unavailable, rate_limit, or quota_exhausted.

What we own at the core

Downstream of the adapters is code Actionary maintains — tightly coupled to the entity model and the permission gates it enforces.

  • RAG pipeline. upload_indexer_service.py runs extract → chunk → embed → persist. content_asset_chunks_repository.py holds the hand-written SQL against pgvector. The chunker, the retriever, and the ranker are each a few hundred lines against the domain shape.
  • Workflow engine. Durable execution on Postgres via workflow_runs + FOR UPDATE SKIP LOCKED step claims, ON CONFLICT DO NOTHING on every step insert, sha256 Idempotency-Key on every outbound call. One process runs the engine; the deploy topology stays flat.
  • Agent orchestration. Propose-then-execute on the MCP tool surface, destructive-confirm staging in the agent loop. Coupled tightly to the tool-idempotency guarantees the substrate provides.
  • Entity metadata layer. entity_service.py reads entity_config; the SPA, the admin API, and MCP all render from the same payload. One schema, zero drift.
  • Permission enforcement. Postgres RLS keyed on app.current_tenant_id, plus a _permissions contract shipped on every list response. The gate is code inside the repository, evaluated on every read.

Roads not taken

Each pattern below already lives inside the substrate.

  • LangChain, LlamaIndex. Orchestration middleware. Their patterns are specific to a general shape. Actionary’s chunker plus retriever is a few hundred lines against the domain, so an imported abstraction re-specialised to that same domain adds import cost without adding capability.
  • Pinecone, Weaviate. Hosted vector databases. Sending data to them means it leaves Postgres — backup surface splits in two, audit surface splits in two, the record_access visibility gate does not apply to the external store. pgvector on the same Postgres keeps every gate a single-surface concern.
  • Temporal, Airflow. Durable-execution frameworks. A second stateful service in the deploy topology, with workflow state synced back for auditability. workflow_runs + FOR UPDATE SKIP LOCKED delivers the same idempotent-step guarantee inside the existing process.
  • Kafka, RabbitMQ. Dedicated brokers. Redis lists via BLPOP with an orphan sweep against a Postgres state table cover Actionary’s shape.

A dedicated broker becomes the right choice when throughput or cross-region delivery demands it. At Actionary’s shape, the substrate reuse wins.

Portability by construction

Four guarantees an architecture review can take at face value.

  • pg_dump-clean data. The retrieval index, the workflow state, the audit log, and the entity metadata all round-trip through one dump file.
  • S3-adapter-swappable object storage. One env var moves every upload, every asset, every presigned download to any compatible endpoint.
  • OTEL-compatible metrics. Every trace, every metric, every log line emits through the CNCF standard. The observability tier stays vendor-neutral end to end.
  • MCP for the agent tool surface. The tools the embedded agent uses are the same tools any DCR-compliant external client receives — one catalogue, one authorisation model, one audit trail.

Every boundary is one configuration change from a different provider. Every core decision is code in a repository the team owns.

The takeaway

The mature enterprise-SaaS pattern builds ON open standards at the infrastructure boundary and builds the domain layer as code the team owns and evolves. Stripe, Basecamp, Anthropic, Supabase, and GitHub all run this stance. Frameworks at the boundary keep the stack portable. Frameworks at the core become the coupling that outlives every decision that put them there. Technical leaders recognise the shape on day one; the platform architecture reads the same argument at every layer beneath it.