diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Changelog for agentic-openai
 
+## 0.2.1.0 - 2026-11-06
+
+* Follow Agentic's `Structure`/`Outline` API and fallible model compilation.
+  Provider wire schemas and protocols are unchanged.
+
 ## 0.2.0.5 - 2026-10-06
 
 * No changes; released alongside agentic 0.2.0.5.
diff --git a/agentic-openai.cabal b/agentic-openai.cabal
--- a/agentic-openai.cabal
+++ b/agentic-openai.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               agentic-openai
-version:            0.2.0.5
+version:            0.2.1.0
 synopsis:           OpenAI as the System Two provider for agentic
 description:
   Runs agentic's draft steps on OpenAI models through the Responses API, with strict structured outputs and tools, and can stand in as System One.
@@ -38,8 +38,8 @@
   hs-source-dirs:   src
   exposed-modules:  Agentic.OpenAI
   build-depends:
-    , agentic          ==0.2.*
-    , agentic-aeson    ==0.2.*
+    , agentic          >=0.2.1 && <0.3
+    , agentic-aeson    >=0.2.1 && <0.3
     , aeson            >=2.1 && <2.3
     , base             >=4.18 && <5
     , bytestring       >=0.11 && <0.13
@@ -53,9 +53,9 @@
   hs-source-dirs:   test
   main-is:          Spec.hs
   build-depends:
-    , agentic          ==0.2.*
-    , agentic-aeson    ==0.2.*
-    , agentic-openai   ==0.2.*
+    , agentic          >=0.2.1 && <0.3
+    , agentic-aeson    >=0.2.1 && <0.3
+    , agentic-openai   >=0.2.1 && <0.3
     , aeson            >=2.1 && <2.3
     , base             >=4.18 && <5
     , hspec            >=2.10 && <3
@@ -67,9 +67,9 @@
   hs-source-dirs:   test-live
   main-is:          Live.hs
   build-depends:
-    , agentic          ==0.2.*
-    , agentic-openai   ==0.2.*
-    , agentic-io       ==0.2.*
+    , agentic          >=0.2.1 && <0.3
+    , agentic-openai   >=0.2.1 && <0.3
+    , agentic-io       >=0.2.1 && <0.3
     , base             >=4.18 && <5
     , hspec            >=2.10 && <3
     , text             >=2.0 && <2.2
diff --git a/test-live/Live.hs b/test-live/Live.hs
--- a/test-live/Live.hs
+++ b/test-live/Live.hs
@@ -29,13 +29,13 @@
       "How much the audience groans"
       [option Mild "A polite smile", option Solid "An audible groan", option Unbearable "People get up and leave"]
 
-deriving via Enumeration Groan instance Contract Groan
+instance Contract Groan where contract = enumeration
 
 newtype Rating = Rating Int
   deriving (Show, Eq)
 
 instance Contract Rating where
-  contract = mapCodec Rating (\(Rating n) -> n) (between 1 10 contract)
+  contract = mapStructure Rating (\(Rating n) -> n) (between 1 10 contract)
 
 main :: IO ()
 main = do
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,8 +1,10 @@
 module Main (main) where
 
 import Agentic
+import Agentic.Scripted (modelContract)
 import Agentic.Runtime (Conversation (..))
-import Agentic.Schema (Shape (..))
+import Agentic.Schema (Schema(..), Field(..), Shape (..))
+import Agentic.JsonSchema (objectSchema)
 import Agentic.Aeson (toAeson)
 import Agentic.OpenAI
 import qualified Data.Aeson as J
@@ -22,6 +24,9 @@
 data Board = Board {top :: Row, middle :: Row, bottom :: Row}
   deriving (Generic, Show, Contract)
 
+data Box a = Box { value :: a } deriving (Generic, Contract)
+data Repeated = Repeated { one :: Box Int, two :: Box Int } deriving (Generic, Contract)
+
 conversation :: Conversation
 conversation =
   Conversation
@@ -29,8 +34,8 @@
     , instruction = "Suggest 3 dinosaurs"
     , input = Null
     , inputSchema = schemaOf SNull
-    , tools = [ToolSpec "search" "Search the fossil database" ((contract @Text).schema)]
-    , outputSchema = (contract @[Text]).schema
+    , tools = [ToolSpec "search" "Search the fossil database" ((modelContract @Text).schema)]
+    , outputSchema = (modelContract @[Text]).schema
     , history = []
     }
 
@@ -57,11 +62,32 @@
         )
 
   it "names the schema after its type, and shares repeated types through $defs" $ do
-    let format = at "format" (at "text" (body conversation {outputSchema = (contract @Board).schema}))
+    let format = at "format" (at "text" (body conversation {outputSchema = (modelContract @Board).schema}))
         schema = at "schema" format
     at "name" format `shouldBe` J.String "Board"
     at "top" (at "properties" schema) `shouldBe` fromJust (J.decode "{\"$ref\":\"#/$defs/Row\"}")
     KeyMap.keys (case at "$defs" schema of J.Object o -> o; _ -> mempty) `shouldMatchList` ["Row", "Square"]
+
+  it "uses safe shared-definition keys for applied type names" $ do
+    let schema = toAeson (objectSchema (modelContract @Repeated).schema)
+    at "$ref" (at "one" (at "properties" schema)) `shouldBe` J.String "#/$defs/Box_Int"
+    at "$ref" (at "two" (at "properties" schema)) `shouldBe` J.String "#/$defs/Box_Int"
+    at "type" (at "Box_Int" (at "$defs" schema)) `shouldBe` J.String "object"
+
+  it "keeps colliding sanitized names and pointer punctuation distinct" $ do
+    let a = Schema (Just "Box Int") Nothing [] SInteger
+        b = Schema (Just "Box_Int") Nothing [] (SString Nothing)
+        c = Schema (Just "A/B~C") Nothing [] SBool
+        root = schemaOf (SObject [Field n ty True | (n, ty) <- [("a1",a),("a2",a),("b1",b),("b2",b),("c1",c),("c2",c)]])
+        schema = toAeson (objectSchema root)
+        defs = at "$defs" schema
+        props = at "properties" schema
+    at "$ref" (at "a1" props) `shouldBe` J.String "#/$defs/Box_Int"
+    at "$ref" (at "b1" props) `shouldBe` J.String "#/$defs/Box_Int_2"
+    at "$ref" (at "c1" props) `shouldBe` J.String "#/$defs/A_B_C"
+    at "type" (at "Box_Int" defs) `shouldBe` J.String "integer"
+    at "type" (at "Box_Int_2" defs) `shouldBe` J.String "string"
+    at "type" (at "A_B_C" defs) `shouldBe` J.String "boolean"
 
   it "stores nothing, and asks for reasoning it can send back" $ do
     at "store" (body conversation) `shouldBe` J.Bool False
