diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,16 @@
 
 ## Unreleased
 
+## 0.3.0.0 - 2026-03-11
+
+### Changed
+- `SUL` typeclass no longer takes `i` and `o` as class parameters; they are now
+  universally quantified in the method signatures. Instances should drop `i o`
+  from their instance heads: `instance SUL MyType IO` instead of
+  `instance SUL MyType IO Input Output`.
+- `Automaton` typeclass likewise drops `i` and `o` from its class head.
+  All constraint occurrences `(Automaton aut s i o)` become `(Automaton aut s)`.
+
 ## 0.2.0.0 - 2026-03-05
 
 ### Added
diff --git a/examples/div.hs b/examples/div.hs
--- a/examples/div.hs
+++ b/examples/div.hs
@@ -50,7 +50,7 @@
     , buffer :: [i]
     }
 
-instance SUL Program Identity i o where
+instance SUL Program Identity where
     step s i = return (theStep s i)
     reset = return . theReset
 
diff --git a/examples/io.hs b/examples/io.hs
--- a/examples/io.hs
+++ b/examples/io.hs
@@ -51,7 +51,7 @@
     , buffer :: [i]
     }
 
-instance SUL Program IO Binary Bool where
+instance SUL Program IO where
     step = theStep
     reset = theReset
 
diff --git a/examples/website.hs b/examples/website.hs
--- a/examples/website.hs
+++ b/examples/website.hs
@@ -24,11 +24,13 @@
     | NotFoundTag
     deriving (Eq, Show, Ord, Enum, Bounded)
 
-data WebsiteSUL inp out = WebsiteSUL
+data WebsiteSUL i o = WebsiteSUL
     { baseUrl :: String
     , currentUrl :: String -- current path, e.g. "/about"
+    , toPath :: i -> String
+    , fromHtml :: String -> o
+    , notFound :: o
     }
-    deriving (Eq, Show)
 
 -- Fetch + HTML processing
 --------------------------------------------------------------------------------
@@ -65,20 +67,18 @@
 -- SUL instance
 --------------------------------------------------------------------------------
 
-instance SUL WebsiteSUL IO Page PageTag where
-    reset sul = do
-        pure sul{currentUrl = "/"}
+instance SUL WebsiteSUL IO where
+    reset sul = pure sul{currentUrl = "/"}
 
     step sul input = do
         if currentUrl sul /= "/garbage"
             then do
-                let suffix = inputMap input
+                let suffix = toPath sul input
                 html <- fetch (baseUrl sul ++ suffix)
-                let out = outputMap html
+                let out = fromHtml sul html
                     sul' = sul{currentUrl = suffix}
                 pure (sul', out)
-            else do
-                pure (sul, NotFoundTag)
+            else pure (sul, notFound sul)
 
 --------------------------------------------------------------------------------
 -- Experiment setup
@@ -101,8 +101,11 @@
         _currentUrl = "/"
     let website =
             WebsiteSUL
-                { baseUrl = _baseUrl -- this is constant
+                { baseUrl = _baseUrl
                 , currentUrl = _currentUrl
+                , toPath = inputMap
+                , fromHtml = outputMap
+                , notFound = NotFoundTag
                 } ::
                 WebsiteSUL Page PageTag
     (model, _) <- runExperimentT exper website
diff --git a/haal.cabal b/haal.cabal
--- a/haal.cabal
+++ b/haal.cabal
@@ -1,11 +1,11 @@
 cabal-version: 2.2
 
--- This file has been generated from package.yaml by hpack version 0.37.0.
+-- This file has been generated from package.yaml by hpack version 0.38.1.
 --
 -- see: https://github.com/sol/hpack
 
 name:           haal
-version:        0.2.0.0
+version:        0.3.0.0
 synopsis:       A Haskell library for Active Automata Learning.
 description:    Please see the README on GitHub at <https://github.com/steve-anunknown/haal#readme>
 category:       Model Learning
diff --git a/src/Haal/Automaton/MealyAutomaton.hs b/src/Haal/Automaton/MealyAutomaton.hs
--- a/src/Haal/Automaton/MealyAutomaton.hs
+++ b/src/Haal/Automaton/MealyAutomaton.hs
@@ -73,7 +73,7 @@
 mealyReset :: MealyAutomaton s i o -> MealyAutomaton s i o
 mealyReset m = m{mealyCurrentS = mealyInitialS m}
 
-instance SUL (MealyAutomaton s) Identity i o where
+instance SUL (MealyAutomaton s) Identity where
     step sul i = return (mealyStep sul i)
     reset = return . mealyReset
 
@@ -92,7 +92,7 @@
     domainS = Set.toList $ mealyStates m
     domainI = Set.toList $ inputs m
 
-instance Automaton MealyAutomaton s i o where
+instance Automaton MealyAutomaton s where
     transitions = mealyTransitions
     states = mealyStates
     current = mealyCurrentS
diff --git a/src/Haal/Automaton/MooreAutomaton.hs b/src/Haal/Automaton/MooreAutomaton.hs
--- a/src/Haal/Automaton/MooreAutomaton.hs
+++ b/src/Haal/Automaton/MooreAutomaton.hs
@@ -49,7 +49,7 @@
 mooreReset :: MooreAutomaton s i o -> MooreAutomaton s i o
 mooreReset m = m{mooreCurrentS = mooreInitialS m}
 
-instance SUL (MooreAutomaton s) Identity i o where
+instance SUL (MooreAutomaton s) Identity where
     step sul i = return (mooreStep sul i)
     reset = return . mooreReset
 
@@ -68,7 +68,7 @@
     domainS = Set.toList $ mooreStates m
     domainI = Set.toList $ inputs m
 
-instance Automaton MooreAutomaton s i o where
+instance Automaton MooreAutomaton s where
     transitions = mooreTransitions
     current = mooreCurrentS
     states = mooreStates
diff --git a/src/Haal/BlackBox.hs b/src/Haal/BlackBox.hs
--- a/src/Haal/BlackBox.hs
+++ b/src/Haal/BlackBox.hs
@@ -44,7 +44,7 @@
 It also requires a monad m, that may be 'Identity' in case of a pure SUL, or 'IO'
 in case of an external program that performs IO.
 -}
-class (Monad m) => SUL sul m i o where
+class (Monad m) => SUL sul m where
     step :: sul i o -> i -> m (sul i o, o)
     reset :: sul i o -> m (sul i o)
 
@@ -56,7 +56,7 @@
 type FiniteOrd i = (Ord i, Finite i)
 
 -- | Generalization of 'step' that operates on a list of inputs.
-walk :: (SUL sul m i o) => sul i o -> [i] -> m (sul i o, [o])
+walk :: (SUL sul m) => sul i o -> [i] -> m (sul i o, [o])
 walk sul [] = pure (sul, [])
 walk sul (x : xs) = do
     (sul', o) <- step sul x
@@ -75,7 +75,7 @@
 support for automata operations. Automatons are models, not programs,
 so they are pure and operate in the Identity monad.
 -}
-class (SUL (aut s) Identity i o) => Automaton aut s i o where
+class (SUL (aut s) Identity) => Automaton aut s where
     transitions ::
         (FiniteOrd i, FiniteOrd s) =>
         aut s i o ->
@@ -85,23 +85,23 @@
     update :: aut s i o -> s -> aut s i o
 
 -- | Pure instance of 'step'.
-stepPure :: (SUL sul Identity i o) => sul i o -> i -> (sul i o, o)
+stepPure :: (SUL sul Identity) => sul i o -> i -> (sul i o, o)
 stepPure sul i = runIdentity (step sul i)
 
 -- | Pure instance of 'walk'.
-walkPure :: (SUL sul Identity i o) => sul i o -> [i] -> (sul i o, [o])
+walkPure :: (SUL sul Identity) => sul i o -> [i] -> (sul i o, [o])
 walkPure sul i = runIdentity (walk sul i)
 
 -- | Pure instance of 'reset'.
-resetPure :: (SUL sul Identity i o) => sul i o -> sul i o
+resetPure :: (SUL sul Identity) => sul i o -> sul i o
 resetPure sul = runIdentity (reset sul)
 
 -- | Return the initial state of an automaton.
-initial :: (Automaton aut s i o) => aut s i o -> s
+initial :: (Automaton aut s) => aut s i o -> s
 initial = current . resetPure
 
 -- | Return the set of reachable states of an automaton.
-reachable :: forall s i o aut. (Automaton aut s i o, Ord s, FiniteOrd i) => aut s i o -> Set.Set s
+reachable :: forall s i o aut. (Automaton aut s, Ord s, FiniteOrd i) => aut s i o -> Set.Set s
 reachable aut = bfs [initial aut] $ Set.singleton (initial aut)
   where
     alphabet = inputs aut
@@ -118,7 +118,7 @@
 -- | Returns a map containing the shortest sequence to access each reachable state from the initial state.
 accessSequences ::
     forall s i o aut.
-    (Automaton aut s i o, FiniteOrd i, Ord s) =>
+    (Automaton aut s, FiniteOrd i, Ord s) =>
     aut s i o ->
     Map.Map s [i]
 accessSequences aut = bfs [(initialSt, [])] (Set.singleton initialSt) (Map.singleton initialSt [])
@@ -147,7 +147,7 @@
 the given automaton.
 -}
 distinguish ::
-    ( Automaton aut s i o
+    ( Automaton aut s
     , FiniteOrd i
     , Ord s
     , Eq o
@@ -186,7 +186,7 @@
  - any other state of the automaton.
 -}
 localCharacterizingSet ::
-    ( Automaton aut s i o
+    ( Automaton aut s
     , FiniteOrd i
     , FiniteOrd s
     , Eq o
@@ -202,7 +202,7 @@
 of the automaton.
 -}
 globalCharacterizingSet ::
-    ( Automaton aut s i o
+    ( Automaton aut s
     , FiniteOrd i
     , FiniteOrd s
     , Eq o
diff --git a/src/Haal/EquivalenceOracle/WMethod.hs b/src/Haal/EquivalenceOracle/WMethod.hs
--- a/src/Haal/EquivalenceOracle/WMethod.hs
+++ b/src/Haal/EquivalenceOracle/WMethod.hs
@@ -39,7 +39,7 @@
 
 -- | The 'wmethodSuiteSize' function computes the size of the test suite for the W-method.
 wmethodSuiteSize ::
-    ( Automaton aut s i o
+    ( Automaton aut s
     , FiniteOrd i
     , FiniteOrd s
     , Eq o
@@ -57,7 +57,7 @@
 
 -- | The 'wmethodSuite' function generates the test suite for the W-method and a new oracle.
 wmethodSuite ::
-    ( Automaton aut s i o
+    ( Automaton aut s
     , FiniteOrd i
     , FiniteOrd s
     , Eq o
@@ -103,7 +103,7 @@
 -- | The 'randomWMethodSuite' function generates the test suite for the random W-method and a new oracle.
 randomWMethodSuite ::
     forall i o s aut.
-    ( Automaton aut s i o
+    ( Automaton aut s
     , FiniteOrd i
     , FiniteOrd s
     , Eq o
diff --git a/src/Haal/EquivalenceOracle/WpMethod.hs b/src/Haal/EquivalenceOracle/WpMethod.hs
--- a/src/Haal/EquivalenceOracle/WpMethod.hs
+++ b/src/Haal/EquivalenceOracle/WpMethod.hs
@@ -36,7 +36,7 @@
 
 -- | The 'wpmethodSuiteSize' returns the number of test cases in the test suite of WpMethod.
 wpmethodSuiteSize ::
-    ( Automaton aut s i o
+    ( Automaton aut s
     , FiniteOrd i
     , FiniteOrd s
     , Eq o
@@ -82,7 +82,7 @@
 -- | Returns the test suite for the WpMethod.
 wpmethodSuite ::
     forall aut i o s.
-    ( Automaton aut s i o
+    ( Automaton aut s
     , FiniteOrd i
     , FiniteOrd s
     , Eq o
@@ -156,7 +156,7 @@
 -- | Return the 'RandomWpMethod' test suite.
 randomWpMethodSuite ::
     forall aut i o s.
-    ( Automaton aut s i o
+    ( Automaton aut s
     , FiniteOrd i
     , FiniteOrd s
     , Eq o
diff --git a/src/Haal/Experiment.hs b/src/Haal/Experiment.hs
--- a/src/Haal/Experiment.hs
+++ b/src/Haal/Experiment.hs
@@ -35,7 +35,7 @@
 -}
 class EquivalenceOracle or where
     testSuite ::
-        ( Automaton aut s i o
+        ( Automaton aut s
         , FiniteOrd i
         , FiniteOrd s
         , Eq o
@@ -51,14 +51,14 @@
 -}
 class Learner l aut s | l -> aut s where
     initialize ::
-        ( SUL sul m i o
+        ( SUL sul m
         , FiniteOrd i
         , Finite o
         ) =>
         l i o ->
         ExperimentT (sul i o) m (l i o)
     refine ::
-        ( SUL sul m i o
+        ( SUL sul m
         , FiniteOrd i
         , Finite o
         ) =>
@@ -66,8 +66,8 @@
         [i] ->
         ExperimentT (sul i o) m (l i o)
     learn ::
-        ( SUL sul m i o
-        , Automaton aut s i o
+        ( SUL sul m
+        , Automaton aut s
         , FiniteOrd i
         , FiniteOrd s
         , FiniteEq o
@@ -120,8 +120,8 @@
 and then requires a system under learning (SUL) to run the experiment.
 -}
 experiment ::
-    ( SUL sul m i o
-    , Automaton aut s i o
+    ( SUL sul m
+    , Automaton aut s
     , Learner learner aut s
     , EquivalenceOracle oracle
     , FiniteOrd i
@@ -149,8 +149,8 @@
 
 -- | The 'execute' function executes the test suite of an oracle, given a SUL and an automaton.
 execute ::
-    ( SUL sul m i o
-    , Automaton aut s i o
+    ( SUL sul m
+    , Automaton aut s
     , Ord i
     , Eq o
     ) =>
@@ -171,8 +171,8 @@
 simultaneously, checking if the outputs are the same.
 -}
 pairwiseWalk ::
-    ( SUL sul m i o
-    , Automaton aut s i o
+    ( SUL sul m
+    , Automaton aut s
     , Ord i
     , Eq o
     ) =>
@@ -191,8 +191,8 @@
 and SUL.
 -}
 findCex ::
-    ( SUL sul m i o
-    , Automaton aut s i o
+    ( SUL sul m
+    , Automaton aut s
     , EquivalenceOracle or
     , FiniteOrd i
     , FiniteOrd s
diff --git a/src/Haal/Learning/LMstar.hs b/src/Haal/Learning/LMstar.hs
--- a/src/Haal/Learning/LMstar.hs
+++ b/src/Haal/Learning/LMstar.hs
@@ -59,7 +59,7 @@
 -}
 initializeOT ::
     forall i o sul m.
-    (FiniteOrd i, SUL sul m i o) =>
+    (FiniteOrd i, SUL sul m) =>
     ExperimentT (sul i o) m (ObservationTable i o)
 initializeOT = do
     sul <- ask
@@ -106,7 +106,7 @@
 -- | The 'lmstar' function implements one iteration of the LM* algorithm.
 lmstar ::
     forall sul i o m.
-    (SUL sul m i o, FiniteOrd i, Eq o, Monad m) =>
+    (SUL sul m, FiniteOrd i, Eq o, Monad m) =>
     LMstar i o ->
     ExperimentT (sul i o) m (LMstar i o, MealyAutomaton StateID i o)
 lmstar (LMstar ot) = case otIsClosed ot of
@@ -158,7 +158,7 @@
 -- | The 'otRefineAngluin' function refines the observation table based on a counterexample, according to Angluin's algorithm.
 otRefineAngluin ::
     forall sul i o m.
-    (FiniteOrd i, SUL sul m i o) =>
+    (FiniteOrd i, SUL sul m) =>
     ObservationTable i o ->
     [i] ->
     ExperimentT (sul i o) m (ObservationTable i o)
@@ -223,7 +223,7 @@
 -- | The 'makeConsistent' function makes the observation table consistent by adding missing prefixes.
 makeConsistent ::
     forall i o sul m.
-    (FiniteOrd i, SUL sul m i o) =>
+    (FiniteOrd i, SUL sul m) =>
     ObservationTable i o ->
     ([i], [i]) ->
     ExperimentT (sul i o) m (ObservationTable i o)
@@ -257,7 +257,7 @@
 -- | The 'makeClosed' function makes the observation table closed by adding missing suffixes.
 makeClosed ::
     forall sul i o m.
-    (FiniteOrd i, SUL sul m i o) =>
+    (FiniteOrd i, SUL sul m) =>
     ObservationTable i o ->
     [i] ->
     ExperimentT (sul i o) m (ObservationTable i o)
@@ -297,7 +297,7 @@
 -}
 otRefinePlus ::
     forall sul i o m.
-    (FiniteOrd i, SUL sul m i o) =>
+    (FiniteOrd i, SUL sul m) =>
     ObservationTable i o ->
     [i] ->
     ExperimentT (sul i o) m (ObservationTable i o)
@@ -323,7 +323,7 @@
     return (ObservationTable{prefixSetS = sm, suffixSetE = em', mappingT = tm', prefixSetSI = sm_I})
 
 updateMap ::
-    (Ord i, SUL sul m i o, Monad m) =>
+    (Ord i, SUL sul m, Monad m) =>
     Map.Map ([i], [i]) o ->
     Set.Set ([i], [i]) ->
     sul i o ->
