diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,14 @@
+# Changelog for hs-opentelemetry-instrumentation-amazonka
+
+## 1.0.0.0 - 2026-05-29
+
+- Promoted to 1.0.0.0 for the hs-opentelemetry 1.0 release.
+
+## 0.1.0.0
+
+* Initial release
+* Hooks-based automatic tracing for all Amazonka `send` calls
+* `instrumentEnv` to add tracing hooks to an Amazonka `Env`
+* AWS SDK semantic convention attributes (rpc.system, rpc.service, rpc.method, aws.request_id)
+* HTTP response attributes (http.response.status_code)
+* Error recording on failed AWS calls
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Ian Duncan (c) 2021-2026
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * 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.
+
+    * Neither the name of Ian Duncan nor the names of other
+      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
+OWNER 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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,44 @@
+# hs-opentelemetry-instrumentation-amazonka
+
+[![hs-opentelemetry-instrumentation-amazonka](https://img.shields.io/hackage/v/hs-opentelemetry-instrumentation-amazonka?style=flat-square&logo=haskell&label=hs-opentelemetry-instrumentation-amazonka&labelColor=5D4F85)](https://hackage.haskell.org/package/hs-opentelemetry-instrumentation-amazonka)
+
+OpenTelemetry instrumentation for the [Amazonka](https://hackage.haskell.org/package/amazonka) AWS SDK.
+
+## GHC Compatibility
+
+This package requires `amazonka >= 2.0`. As of April 2026, the published
+`amazonka-2.0` on Hackage does not compile with GHC 9.10+ due to
+`DuplicateRecordFields` changes in generated service packages (`amazonka-sts`,
+`amazonka-sso`). You may need to use amazonka from its GitHub `main` branch
+or wait for the next Hackage release.
+
+## Usage
+
+```haskell
+import Amazonka
+import OpenTelemetry.Instrumentation.Amazonka (instrumentEnv)
+import OpenTelemetry.Trace (getTracerProvider, makeTracer, tracerOptions)
+
+main :: IO ()
+main = do
+  tp <- getTracerProvider
+  let tracer = makeTracer tp "my-app" tracerOptions
+  env <- newEnv discover
+  let tracedEnv = instrumentEnv tracer env
+  -- All send calls through tracedEnv will create OTel spans
+  runResourceT $ send tracedEnv someRequest
+```
+
+## Attributes
+
+Per the [OTel AWS SDK semantic conventions](https://opentelemetry.io/docs/specs/semconv/cloud-providers/aws-sdk/):
+
+| Attribute | Description |
+|-----------|-------------|
+| `rpc.system` | Always `"aws-api"` |
+| `rpc.service` | AWS service abbreviation (e.g., `"S3"`, `"DynamoDB"`) |
+| `rpc.method` | AWS operation name (e.g., `"GetObject"`, `"PutItem"`) |
+| `aws.request_id` | From response headers when available |
+| `http.response.status_code` | HTTP status code |
+| `server.address` | AWS endpoint host |
+| `cloud.region` | AWS region |
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,4 @@
+import Distribution.Simple
+
+
+main = defaultMain
diff --git a/hs-opentelemetry-instrumentation-amazonka.cabal b/hs-opentelemetry-instrumentation-amazonka.cabal
new file mode 100644
--- /dev/null
+++ b/hs-opentelemetry-instrumentation-amazonka.cabal
@@ -0,0 +1,69 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.38.3.
+--
+-- see: https://github.com/sol/hpack
+
+name:           hs-opentelemetry-instrumentation-amazonka
+version:        1.0.0.0
+synopsis:       OpenTelemetry instrumentation for the Amazonka AWS SDK
+description:    Automatic tracing for Amazonka AWS SDK calls via the hooks API. Creates spans per AWS API call with semantic convention attributes (rpc.system, rpc.service, rpc.method, aws.request_id, etc.).
+category:       OpenTelemetry, Telemetry, Monitoring, Observability, AWS
+homepage:       https://github.com/iand675/hs-opentelemetry#readme
+bug-reports:    https://github.com/iand675/hs-opentelemetry/issues
+author:         Ian Duncan, Jade Lovelace
+maintainer:     ian@iankduncan.com
+copyright:      2024 Ian Duncan, Mercury Technologies
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/iand675/hs-opentelemetry
+
+library
+  exposed-modules:
+      OpenTelemetry.Instrumentation.Amazonka
+  other-modules:
+      Paths_hs_opentelemetry_instrumentation_amazonka
+  hs-source-dirs:
+      src
+  ghc-options: -Wall
+  build-depends:
+      amazonka ==2.0.*
+    , amazonka-core ==2.0.*
+    , base >=4.7 && <5
+    , bytestring
+    , case-insensitive
+    , hs-opentelemetry-api ==1.0.*
+    , hs-opentelemetry-semantic-conventions >=1.40 && <2
+    , http-client
+    , http-types
+    , text
+    , unordered-containers
+  default-language: Haskell2010
+
+test-suite hs-opentelemetry-instrumentation-amazonka-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Paths_hs_opentelemetry_instrumentation_amazonka
+  hs-source-dirs:
+      test
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      amazonka
+    , amazonka-core
+    , base >=4.7 && <5
+    , hs-opentelemetry-api ==1.0.*
+    , hs-opentelemetry-exporter-in-memory ==1.0.*
+    , hs-opentelemetry-instrumentation-amazonka ==1.0.*
+    , hs-opentelemetry-sdk ==1.0.*
+    , hspec
+    , text
+    , unordered-containers
+  default-language: Haskell2010
diff --git a/src/OpenTelemetry/Instrumentation/Amazonka.hs b/src/OpenTelemetry/Instrumentation/Amazonka.hs
new file mode 100644
--- /dev/null
+++ b/src/OpenTelemetry/Instrumentation/Amazonka.hs
@@ -0,0 +1,224 @@
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+
+{- |
+Module      :  OpenTelemetry.Instrumentation.Amazonka
+Copyright   :  (c) Ian Duncan, 2024
+License     :  BSD-3
+Description :  OpenTelemetry instrumentation for the Amazonka AWS SDK
+Maintainer  :  Ian Duncan
+Stability   :  experimental
+Portability :  non-portable (GHC extensions)
+
+Provides automatic tracing for all AWS API calls made through the Amazonka
+SDK. Installs hooks on an Amazonka 'Env' that create OTel spans per
+@send@\/@sendEither@ call, following the
+<https://opentelemetry.io/docs/specs/semconv/cloud-providers/aws-sdk/ OTel AWS SDK semantic conventions>.
+
+@
+import Amazonka
+import OpenTelemetry.Instrumentation.Amazonka ('instrumentEnv')
+
+main :: IO ()
+main = 'OpenTelemetry.Trace.withTracerProvider' $ \\tp -> do
+  let tracer = 'OpenTelemetry.Trace.Core.makeTracer' tp "my-app" 'OpenTelemetry.Trace.Core.tracerOptions'
+  env <- newEnv discover
+  let tracedEnv = 'instrumentEnv' tracer env
+  runResourceT $ send tracedEnv someRequest
+@
+
+Each @send@ call produces a span named @\"Service.Operation\"@ (e.g.,
+@\"S3.GetObject\"@, @\"DynamoDB.PutItem\"@) with standard RPC and AWS
+attributes.
+
+@since 0.1.0.0
+-}
+module OpenTelemetry.Instrumentation.Amazonka (
+  instrumentEnv,
+  instrumentHooks,
+) where
+
+import Amazonka.Data.Text (toText)
+import Amazonka.Env (Env, Env' (..))
+import Amazonka.Env.Hooks (Finality (..), Hook, Hook_, Hooks (..))
+import qualified Amazonka.Types as AWS
+import Control.Applicative ((<|>))
+import Control.Exception (onException)
+import qualified Data.HashMap.Strict as HM
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+import Data.Typeable (Proxy (..), Typeable, tyConName, typeRep, typeRepTyCon)
+import qualified Network.HTTP.Client as HTTP
+import Network.HTTP.Types (Header)
+import qualified Network.HTTP.Types.Status as HTTP
+import OpenTelemetry.Attributes (toAttribute)
+import OpenTelemetry.Attributes.Key (unkey)
+import OpenTelemetry.Context (insertSpan, lookupSpan)
+import OpenTelemetry.Context.ThreadLocal (getAndAdjustContext, getContext)
+import qualified OpenTelemetry.SemanticConventions as SC
+import OpenTelemetry.Trace.Core (
+  Span,
+  SpanArguments (..),
+  SpanKind (Client),
+  SpanStatus (Error),
+  Tracer,
+  addAttributes,
+  createSpanWithoutCallStack,
+  defaultSpanArguments,
+  endSpan,
+  setStatus,
+ )
+import Prelude hiding (error)
+
+
+{- | Add OpenTelemetry tracing hooks to an Amazonka 'Env'.
+
+All subsequent @send@ and @sendEither@ calls through the returned 'Env'
+will automatically create spans with AWS SDK semantic convention attributes.
+
+The tracing hooks compose with any existing hooks on the 'Env' — they do
+not replace them.
+
+@since 0.1.0.0
+-}
+instrumentEnv :: Tracer -> Env -> Env
+instrumentEnv tracer env =
+  env {hooks = instrumentHooks tracer (hooks env)}
+
+
+{- | Install tracing hooks on an Amazonka 'Hooks' value.
+
+Lower-level than 'instrumentEnv' — use this if you need fine-grained
+control over hook composition.
+
+@since 0.1.0.0
+-}
+instrumentHooks :: Tracer -> Hooks -> Hooks
+instrumentHooks tracer baseHooks =
+  baseHooks
+    { configuredRequest = tracingConfiguredRequest tracer (configuredRequest baseHooks)
+    , clientResponse = tracingClientResponse (clientResponse baseHooks)
+    , response = tracingResponse (response baseHooks)
+    , error = tracingError (error baseHooks)
+    }
+
+
+tracingConfiguredRequest
+  :: forall a
+   . (AWS.AWSRequest a, Typeable a)
+  => Tracer
+  -> Hook (AWS.Request a)
+  -> Hook (AWS.Request a)
+tracingConfiguredRequest tracer baseHook env req = do
+  let svc = (AWS.service req :: AWS.Service)
+      svcAbbrev = toText svc.abbrev
+      opName = T.pack $ tyConName $ typeRepTyCon $ typeRep (Proxy @a)
+      spanName = svcAbbrev <> "." <> opName
+      region = AWS.fromRegion (Amazonka.Env.region env)
+      endpoint_ = AWS.endpoint (AWS.service req) (Amazonka.Env.region env)
+      host = TE.decodeUtf8 (AWS.host endpoint_)
+
+  ctx <- getContext
+  span <-
+    createSpanWithoutCallStack tracer ctx spanName $
+      defaultSpanArguments
+        { kind = Client
+        , attributes =
+            HM.fromList
+              [ (unkey SC.rpc_system, toAttribute ("aws-api" :: T.Text))
+              , (unkey SC.rpc_service, toAttribute svcAbbrev)
+              , (unkey SC.rpc_method, toAttribute opName)
+              , (unkey SC.cloud_region, toAttribute region)
+              , (unkey SC.server_address, toAttribute host)
+              ]
+        }
+
+  _ <- getAndAdjustContext (insertSpan span)
+  baseHook env req `onException` endSpan span Nothing
+
+
+tracingClientResponse
+  :: forall a
+   . (AWS.AWSRequest a, Typeable a)
+  => Hook_ (AWS.Request a, AWS.ClientResponse ())
+  -> Hook_ (AWS.Request a, AWS.ClientResponse ())
+tracingClientResponse baseHook env arg@(_req, resp) = do
+  ctx <- getContext
+  case lookupSpan ctx of
+    Just span -> do
+      let sc = HTTP.statusCode (HTTP.responseStatus resp)
+          mReqId = requestIdFromHeaders (HTTP.responseHeaders resp)
+          attrs =
+            HM.fromList $
+              (unkey SC.http_response_statusCode, toAttribute sc)
+                : case mReqId of
+                  Just reqId -> [(unkey SC.aws_requestId, toAttribute reqId)]
+                  Nothing -> []
+      addAttributes span attrs
+    Nothing -> pure ()
+  baseHook env arg
+
+
+tracingResponse
+  :: forall a
+   . (AWS.AWSRequest a, Typeable a)
+  => Hook_ (AWS.Request a, AWS.ClientResponse (AWS.AWSResponse a))
+  -> Hook_ (AWS.Request a, AWS.ClientResponse (AWS.AWSResponse a))
+tracingResponse baseHook env arg = do
+  result <- baseHook env arg
+  ctx <- getContext
+  case lookupSpan ctx of
+    Just span -> endSpan span Nothing
+    Nothing -> pure ()
+  pure result
+
+
+tracingError
+  :: forall a
+   . (AWS.AWSRequest a, Typeable a)
+  => Hook_ (Finality, AWS.Request a, AWS.Error)
+  -> Hook_ (Finality, AWS.Request a, AWS.Error)
+tracingError baseHook env arg@(finality, _req, err) = do
+  case finality of
+    Final -> do
+      ctx <- getContext
+      case lookupSpan ctx of
+        Just span -> do
+          let errDesc = describeError err
+              attrs =
+                HM.fromList $
+                  (unkey SC.error_type, toAttribute errDesc)
+                    : case extractServiceRequestId err of
+                      Just reqId -> [(unkey SC.aws_requestId, toAttribute reqId)]
+                      Nothing -> []
+          addAttributes span attrs
+          setStatus span (Error errDesc)
+          endSpan span Nothing
+        Nothing -> pure ()
+    NotFinal -> pure ()
+  baseHook env arg
+
+
+requestIdFromHeaders :: [Header] -> Maybe T.Text
+requestIdFromHeaders headers =
+  fmap TE.decodeUtf8 (lookup "x-amzn-requestid" headers)
+    <|> fmap TE.decodeUtf8 (lookup "x-amzn-request-id" headers)
+    <|> fmap TE.decodeUtf8 (lookup "x-amz-request-id" headers)
+
+
+extractServiceRequestId :: AWS.Error -> Maybe T.Text
+extractServiceRequestId (AWS.ServiceError svcErr) =
+  AWS.fromRequestId <$> AWS.requestId svcErr
+extractServiceRequestId _ = Nothing
+
+
+describeError :: AWS.Error -> T.Text
+describeError (AWS.TransportError _) = "transport_error"
+describeError (AWS.SerializeError serr) =
+  T.pack (serr :: AWS.SerializeError).message
+describeError (AWS.ServiceError svcErr) =
+  let AWS.ErrorCode code = AWS.code svcErr
+  in code
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,2 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+
