diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Changelog for agentic-aeson
 
+## 0.2.0.4 - 2026-10-06
+
+* Follows agentic's record field changes; no API change.
+
 ## 0.2.0.3 - 2026-10-06
 
 * No changes; released alongside agentic 0.2.0.3.
diff --git a/agentic-aeson.cabal b/agentic-aeson.cabal
--- a/agentic-aeson.cabal
+++ b/agentic-aeson.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               agentic-aeson
-version:            0.2.0.3
+version:            0.2.0.4
 synopsis:           Conversions between agentic's values and aeson, and strict JSON Schema
 description:
   Shared by the agentic provider packages: converts the core's Value to and from aeson, and lowers contracts to the strict JSON Schema that providers' structured outputs accept, keeping field order and sharing repeated types through $defs.
@@ -23,7 +23,7 @@
 
 library
   default-language: GHC2021
-  default-extensions: LambdaCase OverloadedStrings
+  default-extensions: DuplicateRecordFields LambdaCase NoFieldSelectors OverloadedRecordDot OverloadedStrings
   ghc-options:      -Wall
   hs-source-dirs:   src
   exposed-modules:
diff --git a/src/Agentic/JsonSchema.hs b/src/Agentic/JsonSchema.hs
--- a/src/Agentic/JsonSchema.hs
+++ b/src/Agentic/JsonSchema.hs
@@ -30,23 +30,23 @@
 -- | Like 'jsonSchema', but any schema named in @shared@ becomes a @$ref@ into
 -- @$defs@.
 schemaWith :: [Text] -> Schema -> Value
-schemaWith shared s = case title s of
+schemaWith shared s = case s.title of
   Just t | t `elem` shared -> Object [("$ref", String ("#/$defs/" <> t))]
   _ -> definition shared s
 
 -- | A schema written out, though the schemas inside it may still be references.
 definition :: [Text] -> Schema -> Value
-definition shared s = withDescription (body (shape s))
+definition shared s = withDescription (body s.shape)
   where
     sub = schemaWith shared
     withDescription = \case
       Object kvs | Just d <- description -> Object (kvs <> [("description", String d)])
       v -> v
-    description = case catMaybes [doc s] <> map (\c -> "Must be " <> c <> ".") (checks s) of
+    description = case catMaybes [s.doc] <> map (\c -> "Must be " <> c <> ".") s.checks of
       [] -> Nothing
       ds -> Just (T.intercalate " " ds)
     body = \case
-      SObject fs -> object (map (\f -> (fieldName f, sub (fieldSchema f))) fs)
+      SObject fs -> object (map (\f -> (f.name, sub f.schema)) fs)
       SSum vs -> Object [("anyOf", Array (map variant vs))]
       SEnum ls
         | all ((== Nothing) . snd) ls -> Object [typed "string", ("enum", Array (map (String . fst) ls))]
@@ -59,9 +59,9 @@
       SBool -> Object [typed "boolean"]
       SNull -> Object [typed "null"]
     variant v =
-      let tagged = ("tag", Object [typed "string", ("const", String (variantTag v))])
-          o = object (tagged : map (\f -> (fieldName f, sub (fieldSchema f))) (variantFields v))
-       in case (o, variantDoc v) of
+      let tagged = ("tag", Object [typed "string", ("const", String v.tag)])
+          o = object (tagged : map (\f -> (f.name, sub f.schema)) v.fields)
+       in case (o, v.doc) of
             (Object kvs, Just d) -> Object (kvs <> [("description", String d)])
             _ -> o
     constant l d = Object ([typed "string", ("const", String l)] <> maybe [] (\t -> [("description", String t)]) d)
@@ -87,7 +87,7 @@
 
 -- | Does this schema need wrapping to be a top-level object?
 wrap :: Schema -> Bool
-wrap s = case shape s of
+wrap s = case s.shape of
   SObject _ -> False
   _ -> True
 
@@ -107,24 +107,24 @@
       | otherwise = definition shared s
     shared = sharedNames s
     defs = [(t, definition shared d) | t <- shared, Just d <- [lookup t named']]
-    named' = [(t, d) | d <- nested s, Just t <- [title d]]
+    named' = [(t, d) | d <- nested s, Just t <- [d.title]]
 
 -- | Names of the types inside a schema (not the schema itself) that appear more
 -- than once and are identical everywhere they appear, in order of appearance.
 sharedNames :: Schema -> [Text]
-sharedNames s = [t | t <- nub names, uses t >= 2, length (nub [d | d <- inside, title d == Just t]) == 1]
+sharedNames s = [t | t <- nub names, uses t >= 2, length (nub [d | d <- inside, d.title == Just t]) == 1]
   where
     inside = nested s
-    names = catMaybes (map title inside)
-    uses t = length (filter ((== Just t) . title) inside)
+    names = catMaybes (map (.title) inside)
+    uses t = length (filter ((== Just t) . (.title)) inside)
 
 -- | Every schema nested inside this one, outermost first.
 nested :: Schema -> [Schema]
-nested s = concatMap (\c -> c : nested c) (children (shape s))
+nested s = concatMap (\c -> c : nested c) (children s.shape)
   where
     children = \case
-      SObject fs -> map fieldSchema fs
-      SSum vs -> concatMap (map fieldSchema . variantFields) vs
+      SObject fs -> map (.schema) fs
+      SSum vs -> concatMap (map (.schema) . (.fields)) vs
       SArray c -> [c]
       SNullable c -> [c]
       _ -> []
@@ -132,7 +132,7 @@
 -- | A name for the schema, for providers that ask for one: the type's name with
 -- anything but letters, digits, @_@ and @-@ dropped, or @output@.
 schemaName :: Schema -> Text
-schemaName s = case T.intercalate "_" (filter (not . T.null) (T.split (not . valid) (maybe "" id (title s)))) of
+schemaName s = case T.intercalate "_" (filter (not . T.null) (T.split (not . valid) (maybe "" id s.title))) of
   "" -> "output"
   n -> T.take 64 n
   where
