diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,8 @@
+# Revision history for acolyte
+
+## 0.1.0.0 -- 2026-04-27
+
+* Initial release. Umbrella package for the composable, type-safe
+  acolyte web framework. Re-exports the core API DSL, server,
+  client, OpenAPI, gRPC, and test packages through Acolyte and
+  Acolyte.Prelude.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2024-2026, Josh Burgess
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from this
+   software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/acolyte.cabal b/acolyte.cabal
new file mode 100644
--- /dev/null
+++ b/acolyte.cabal
@@ -0,0 +1,56 @@
+cabal-version:   3.0
+name:            acolyte
+version:         0.1.0.0
+synopsis:        Composable, type-safe web framework for Haskell
+category:        Web
+description:
+  A web framework where the API is a type, middleware is tracked at
+  compile time, and the backend is pluggable. Re-exports all common
+  types from the component packages.
+
+license:         BSD-3-Clause
+license-file:    LICENSE
+author:          Josh Burgess
+maintainer:      Josh Burgess <joshburgess.webdev@gmail.com>
+homepage:        https://github.com/joshburgess/acolyte
+bug-reports:     https://github.com/joshburgess/acolyte/issues
+build-type:      Simple
+
+extra-doc-files:
+  CHANGELOG.md
+
+library
+  exposed-modules:
+    Acolyte
+    Acolyte.Prelude
+
+  build-depends:
+      base                       >= 4.20 && < 5
+    , acolyte-core                 >= 0.1 && < 0.2
+    , acolyte-server               >= 0.1 && < 0.2
+    , acolyte-client               >= 0.1 && < 0.2
+    , acolyte-openapi              >= 0.1 && < 0.2
+    , acolyte-grpc                 >= 0.1 && < 0.2
+    , spire                        >= 0.1 && < 0.2
+    , spire-http                   >= 0.1 && < 0.2
+    , spire-server                 >= 0.1 && < 0.2
+    , spire-grpc                   >= 0.1 && < 0.2
+    , spire-websocket              >= 0.1 && < 0.2
+    , http-core                    >= 0.1 && < 0.2
+    , http-types                   >= 0.12 && < 0.13
+
+  hs-source-dirs: src
+  default-language: GHC2024
+  default-extensions:
+    DataKinds
+    TypeFamilies
+    TypeOperators
+    OverloadedStrings
+    AllowAmbiguousTypes
+    StrictData
+
+  ghc-options: -Wall -funbox-strict-fields
+
+source-repository head
+  type:     git
+  location: https://github.com/joshburgess/acolyte.git
diff --git a/src/Acolyte.hs b/src/Acolyte.hs
new file mode 100644
--- /dev/null
+++ b/src/Acolyte.hs
@@ -0,0 +1,18 @@
+-- | @acolyte@ — composable, type-safe web framework.
+--
+-- This is the facade package. For most uses, import
+-- 'Acolyte.Prelude' which re-exports everything.
+--
+-- For more control, import individual packages:
+--
+-- * "Acolyte.Core" — type-level API types
+-- * "Acolyte.Server" — server interpretation
+-- * "Acolyte.Client" — client interpretation
+-- * "Acolyte.OpenApi" — OpenAPI spec generation
+-- * "Spire" — Service\/Layer\/Middleware composition
+-- * "Http.Core" — backend-agnostic Request\/Response
+module Acolyte
+  ( module Acolyte.Prelude
+  ) where
+
+import Acolyte.Prelude
diff --git a/src/Acolyte/Prelude.hs b/src/Acolyte/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/src/Acolyte/Prelude.hs
@@ -0,0 +1,229 @@
+-- | Convenience prelude — import everything you typically need.
+--
+-- @
+-- import Acolyte.Prelude
+-- @
+--
+-- This single import gives you:
+--
+-- * All API types and path helpers from @acolyte-core@
+-- * All extractors, response types, and server wiring from @acolyte-server@
+-- * Spire Service\/Layer\/Middleware composition
+-- * HTTP middleware (CORS, security headers, tracing, etc.)
+-- * Backend-agnostic HTTP Request\/Response types
+-- * spire-server (zero-WAI HTTP server)
+module Acolyte.Prelude
+  ( -- * Core API types (methods, paths, endpoints, effects, wrappers)
+    module Acolyte.Core
+
+    -- * Server (extractors, responses, wiring, effects)
+  , module Acolyte.Server
+
+    -- * Spire (composition)
+  , Service (..)
+  , Layer (..)
+  , Middleware
+  , middleware
+  , applyLayer
+  , (|>)
+  , (<|)
+  , composeLayer
+  , before
+  , after
+  , around
+  , mapRequest
+  , mapResponse
+  , mapResponseM
+  , contramapRequest
+  , hoistService
+
+    -- * HTTP types (backend-agnostic)
+  , Http.Core.Request (..)
+  , Http.Core.Response (..)
+  , Extensions
+  , emptyExtensions
+  , newExtensions
+  , insertExtension
+  , lookupExtension
+  , deleteExtension
+  , hasExtension
+  , splitRequest
+  , RequestParts (..)
+  , defaultRequest
+
+    -- * HTTP response helpers
+  , ok
+  , created
+  , noContent
+  , badRequest
+  , unauthorized
+  , forbidden
+  , notFound
+  , methodNotAllowed
+  , unprocessable
+  , serverError
+
+    -- * Body types
+  , Body (..)
+  , BodyChunk (..)
+  , FileRange (..)
+  , fromBytes
+  , fromLazyBytes
+  , fromBuilder
+  , fromText
+  , streamBody
+  , streamFromList
+  , fileBody
+  , emptyBody
+  , bodyToStrict
+  , foldBodyChunks
+  , isEmptyBody
+
+    -- * HTTP status codes (re-exported from http-types)
+  , Status
+  , status200
+  , status201
+  , status204
+  , status400
+  , status401
+  , status403
+  , status404
+  , status405
+  , status422
+  , status500
+    -- | Extract the numeric status code from a 'Status' value.
+  , statusCode
+
+    -- * HTTP methods (re-exported from http-types)
+  , methodGet
+  , methodPost
+  , methodPut
+  , methodDelete
+  , methodPatch
+  , methodHead
+  , methodOptions
+
+    -- * HTTP headers (re-exported from http-types)
+  , RequestHeaders
+  , ResponseHeaders
+  , Header
+  , HeaderName
+  , hContentType
+  , hAccept
+  , hAuthorization
+
+    -- * HTTP middleware layers (spire-http)
+  , SecureHeadersConfig (..)
+  , defaultSecureHeaders
+  , secureHeadersLayer
+  , RequestId (..)
+  , requestIdLayer
+  , requestIdHeader
+  , TraceEntry (..)
+  , traceLayer
+  , CorsConfig (..)
+  , defaultCors
+  , permissiveCors
+  , corsLayer
+  , timeoutLayer
+  , CompressionConfig (..)
+  , defaultCompression
+  , compressionLayer
+
+    -- * spire-server (zero-WAI HTTP server)
+  , runServer
+  , runServerBS
+  , runServerConfig
+  , runServerConfigBS
+  , runServerWithShutdown
+  , ServerConfig (..)
+  , defaultConfig
+  , adaptToBody
+
+    -- * gRPC (spire-grpc)
+  , grpcServer
+  , GrpcServiceMap
+  , grpcServiceMap
+  , GrpcHandler (..)
+  , GrpcRequest (..)
+  , GrpcResponse (..)
+  , unaryHandler
+  , serverStreamHandler
+  , clientStreamHandler
+  , bidiStreamHandler
+  , GrpcStatus (..)
+  , grpcOk
+  , grpcCancelled
+  , grpcUnknown
+  , grpcInvalidArgument
+  , grpcDeadlineExceeded
+  , grpcNotFound
+  , grpcAlreadyExists
+  , grpcPermissionDenied
+  , grpcResourceExhausted
+  , grpcFailedPrecondition
+  , grpcAborted
+  , grpcOutOfRange
+  , grpcUnimplemented
+  , grpcInternal
+  , grpcUnavailable
+  , grpcDataLoss
+  , grpcUnauthenticated
+  , grpcStatusCode
+  , statusFromCode
+  , encodeMessage
+  , encodeMessages
+  , decodeMessage
+  , decodeMessages
+  , GrpcMessage (..)
+  , multiplex
+
+    -- * gRPC reflection
+  , reflectionService
+  , withReflection
+  , ServiceInfo (..)
+  , MethodInfo (..)
+
+    -- * gRPC health check
+  , healthService
+  , withHealthCheck
+  , HealthStatus (..)
+
+    -- * gRPC compression
+  , compressMessage
+  , decompressMessage
+  , GrpcCompression (..)
+  , encodeMessageCompressed
+
+    -- * gRPC interpretation (acolyte-grpc)
+  , GrpcCodec (..)
+  , GrpcReady
+  , AllGrpcReady
+  , mkGrpcServiceMap
+  , generateProto
+  , ProtoMessageName (..)
+
+    -- * WebSocket session types (spire-websocket)
+  , Session
+  , WebSocketConn (..)
+  , send
+  , recv
+  , offer
+  , select1
+  , select2
+  , close
+  , recurse
+  , loop
+  , withSession
+  , Unfold
+  ) where
+
+import Acolyte.Core
+import Acolyte.Server
+import Spire
+import Http.Core
+import Spire.Http
+import Spire.Server
+import Spire.Grpc
+import Acolyte.Grpc (GrpcCodec(..), GrpcReady, AllGrpcReady, mkGrpcServiceMap, generateProto, ProtoMessageName(..))
+import Spire.WebSocket.Session
