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_dumpproduces a portable backup of the whole stack — including thecontent_asset_chunkspgvector retrieval index. Every vector row rides along in the standard dump format. - S3-API.
shared/storage/storage_adapter.pyswaps 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_queuelist 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, orquota_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.pyruns extract → chunk → embed → persist.content_asset_chunks_repository.pyholds the hand-written SQL againstpgvector. 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 LOCKEDstep claims,ON CONFLICT DO NOTHINGon every step insert, sha256Idempotency-Keyon 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.pyreadsentity_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_permissionscontract 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_accessvisibility gate does not apply to the external store.pgvectoron 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 LOCKEDdelivers the same idempotent-step guarantee inside the existing process. - Kafka, RabbitMQ. Dedicated brokers. Redis lists via
BLPOPwith 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.