diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Changelog for agentic-anthropic
 
+## 0.2.0.4 - 2026-10-06
+
+* Follows agentic's record field changes; no API change.
+
 ## 0.2.0.3 - 2026-10-06
 
 * Follows agentic's renames: `endpoint` is `Text`.
diff --git a/agentic-anthropic.cabal b/agentic-anthropic.cabal
--- a/agentic-anthropic.cabal
+++ b/agentic-anthropic.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               agentic-anthropic
-version:            0.2.0.3
+version:            0.2.0.4
 synopsis:           Claude as the System Two provider for agentic
 description:
   Runs agentic's draft steps on Claude through the Messages API, with strict structured outputs and tools, and can stand in as System One.
diff --git a/src/Agentic/Anthropic.hs b/src/Agentic/Anthropic.hs
--- a/src/Agentic/Anthropic.hs
+++ b/src/Agentic/Anthropic.hs
@@ -152,11 +152,11 @@
     , ("max_tokens", A.Integer (toInteger cfg.maxTokens))
     ]
       <> maybe [] (\s -> [("system", A.String s)]) cfg.system
-      <> [("tools", A.Array (map tool (tools c))) | not (null (tools c))]
-      <> [ ("messages", A.Array (task : concatMap exchange (history c)))
+      <> [("tools", A.Array (map tool c.tools)) | not (null c.tools)]
+      <> [ ("messages", A.Array (task : concatMap exchange c.history))
          , ( "output_config"
            , A.Object
-               ( ("format", A.Object [("type", A.String "json_schema"), ("schema", objectSchema (outputSchema c))])
+               ( ("format", A.Object [("type", A.String "json_schema"), ("schema", objectSchema c.outputSchema)])
                    : maybe [] (\e -> [("effort", A.String (effortName e))]) cfg.effort
                )
            )
@@ -164,15 +164,15 @@
          ]
       <> [("fallbacks", A.String "default") | cfg.fallbacks]
   where
-    task = message "user" (A.String (instructionText (instruction c) <> inputText))
-    inputText = case input c of
+    task = message "user" (A.String (c.instruction.text <> inputText))
+    inputText = case c.input of
       A.Null -> ""
       s -> "\n\nInput:\n" <> A.renderJson s
     tool spec =
       A.Object
-        [ ("name", A.String (specName spec))
-        , ("description", A.String (specDescription spec))
-        , ("input_schema", objectSchema (specInput spec))
+        [ ("name", A.String spec.name)
+        , ("description", A.String spec.description)
+        , ("input_schema", objectSchema spec.input)
         , ("strict", A.Bool True)
         ]
     exchange = \case
@@ -223,11 +223,11 @@
     -- A reply that isn't JSON goes back to the core as text; the output
     -- contract then rejects it and the model gets another go.
     final text = case J.eitherDecode (TL.encodeUtf8 (TL.fromStrict text)) of
-      Right v -> unwrap (outputSchema c) (fromAeson v)
+      Right v -> unwrap c.outputSchema (fromAeson v)
       Left _ -> A.String text
     unwrapInput name v = maybe v (`unwrap` v) (inputSchema name)
     inputSchema :: Text -> Maybe Schema
-    inputSchema name = case catMaybes [if specName s == name then Just (specInput s) else Nothing | s <- tools c] of
+    inputSchema name = case catMaybes [if s.name == name then Just s.input else Nothing | s <- c.tools] of
       s : _ -> Just s
       [] -> Nothing
 
diff --git a/test-live/Live.hs b/test-live/Live.hs
--- a/test-live/Live.hs
+++ b/test-live/Live.hs
@@ -15,9 +15,9 @@
   deriving (Generic, Show, Contract)
 
 data BetterJoke
-  = DadJoke {setup' :: Text, punchline' :: Text}
+  = DadJoke {setup :: Text, punchline :: Text}
   | OneLiner {line :: Text}
-  | KnockKnock {whosThere :: Text, punchline' :: Text}
+  | KnockKnock {whosThere :: Text, punchline :: Text}
   deriving (Generic, Show, Contract)
 
 data Square = Blank | X | O
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -20,8 +20,8 @@
     , instruction = "Suggest 3 dinosaurs"
     , input = Null
     , inputSchema = schemaOf SNull
-    , tools = [ToolSpec "search" "Search the fossil database" (codecSchema (contract @Text))]
-    , outputSchema = codecSchema (contract @[Text])
+    , tools = [ToolSpec "search" "Search the fossil database" ((contract @Text).schema)]
+    , outputSchema = (contract @[Text]).schema
     , history = []
     }
 
@@ -48,7 +48,7 @@
         )
 
   it "keeps a record's field order in its schema" $ do
-    let body = renderJson (requestBody anthropic conversation {outputSchema = codecSchema (contract @(Text, Text, Text))})
+    let body = renderJson (requestBody anthropic conversation {outputSchema = (contract @(Text, Text, Text)).schema})
         at' k = T.length (fst (T.breakOn k body))
     (at' "\"_1\"" < at' "\"_2\"", at' "\"_2\"" < at' "\"_3\"") `shouldBe` (True, True)
 
@@ -73,15 +73,15 @@
         )
 
   it "reads tool calls, unwrapping their input" $
-    fmap action (decoded (fromJust (J.decode "{\"stop_reason\":\"tool_use\",\"content\":[{\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"x\"},{\"type\":\"tool_use\",\"id\":\"t1\",\"name\":\"search\",\"input\":{\"value\":\"rex\"}}]}")))
+    fmap (.action) (decoded (fromJust (J.decode "{\"stop_reason\":\"tool_use\",\"content\":[{\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"x\"},{\"type\":\"tool_use\",\"id\":\"t1\",\"name\":\"search\",\"input\":{\"value\":\"rex\"}}]}")))
       `shouldBe` Right (CallTools [ToolCall "t1" "search" (String "rex")])
 
   it "reads a final answer, unwrapping it" $
-    fmap action (decoded (fromJust (J.decode "{\"stop_reason\":\"end_turn\",\"content\":[{\"type\":\"text\",\"text\":\"{\\\"value\\\":[\\\"Stegosaurus\\\"]}\"}]}")))
+    fmap (.action) (decoded (fromJust (J.decode "{\"stop_reason\":\"end_turn\",\"content\":[{\"type\":\"text\",\"text\":\"{\\\"value\\\":[\\\"Stegosaurus\\\"]}\"}]}")))
       `shouldBe` Right (Respond (Array [String "Stegosaurus"]))
 
   it "keeps the whole content, thinking included, as the raw turn" $
-    fmap raw (decoded (fromJust (J.decode "{\"stop_reason\":\"end_turn\",\"content\":[{\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"x\"},{\"type\":\"text\",\"text\":\"{}\"}]}")))
+    fmap (.raw) (decoded (fromJust (J.decode "{\"stop_reason\":\"end_turn\",\"content\":[{\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"x\"},{\"type\":\"text\",\"text\":\"{}\"}]}")))
       -- aeson orders object keys, which JSON ignores; the blocks themselves are unchanged.
       `shouldBe` Right (Raw (Array [Object [("signature", String "x"), ("thinking", String ""), ("type", String "thinking")], Object [("text", String "{}"), ("type", String "text")]]))
 
