diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
 # Revision history for railroad
 
+## 0.1.2.0 -- 2026-10-11
+* Add the fallback operator `(?|)`: runs the left action, and only if it fails runs the right one.
+  The two sides can be different structures (`Maybe`, `Either`, ...) with the same success type, the
+  last error is kept, and the result chains and is finished with `?` or `??`. It never throws, so
+  `Railroad.MonadError` re-exports the one definition.
+* Add `(?|<>)`, the same fallback but when every source fails the errors are combined with `<>`
+  in source order. The sources must agree on the error type (`CErr`) and the success type (`CRes`).
+* Documentation: a servant + effectful example and a section on the fallback operators in the README,
+  clearer haddocks (module headers, `CErr`, `CRes`, each operator), and `readme.md` in `extra-doc-files`.
+* Add `tested-with` (GHC 9.4.8 to 9.14.1, the versions the test suite is run on).
+* Write the `validation` bound without a trailing zero or a strict lower bound:
+  `(>= 1.1 && < 1.3) || (>= 1.3.1 && < 1.4)`, the same set of releases as in 0.1.1.5.
+
 ## 0.1.1.5 -- 2026-10-11
 * Exclude `validation-1.3.0` from the bounds: it does not build (it uses `foldable1-classes-compat`
   without declaring it). `validation >= 1.3.1` needs `base >= 4.18`, so on GHC 9.4 the solver picked
diff --git a/railroad.cabal b/railroad.cabal
--- a/railroad.cabal
+++ b/railroad.cabal
@@ -1,13 +1,14 @@
 cabal-version:      3.0
 name:               railroad
-version:            0.1.1.5
+version:            0.1.2.0
 license:            BSD-3-Clause
 license-file:       LICENSE
 author:             Frederik Kallstrup Mastratisi
 maintainer:         mastratisi@proton.me
 category:           Control, Error Handling
 build-type:         Simple
-extra-doc-files:    CHANGELOG.md
+tested-with:        GHC == 9.4.8, GHC == 9.6.7, GHC == 9.8.4, GHC == 9.10.3, GHC == 9.12.4, GHC == 9.14.1
+extra-doc-files:    CHANGELOG.md, readme.md
 synopsis:           A railway oriented mini-DSL for unified error handling.
 description:
     A railway oriented mini-DSL for handling and interpreting error states 
@@ -24,7 +25,7 @@
       base >=4.17 && < 4.23,
       effectful >= 2.5 && < 2.8,
       mtl >= 2.2 && < 2.4,
-      validation (>= 1.1 && < 1.3.0) || (> 1.3.0 && < 1.4)
+      validation (>= 1.1 && < 1.3) || (>= 1.3.1 && < 1.4)
 
 library
     import:           stuff
diff --git a/readme.md b/readme.md
new file mode 100644
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,247 @@
+# railroad
+
+**A terse Haskell railroad error handling DSL abstracting over error functors by catamorphism via `Either`.**
+
+Instead of
+
+```haskell
+rows <- runQuery (userById uid) >>= either (const (throwError err503)) pure
+user <- case rows of
+  []  -> throwError err404
+  [u] -> pure u
+  _   -> throwError err500
+```
+
+write
+
+```haskell
+user <- runQuery (userById uid) ? err503 ?! cardinalityErr err404 (const err500)
+```
+
+Here `runQuery` returns `Eff es (Either DbError [row])`. Each operator
+peels one layer (the database failure, then "exactly one row") and says
+what to throw if it fails, so the happy path stays one line. A longer
+[servant example](#example-a-servant-backend) is below.
+
+The purpose of `railroad` is to error handle tersely, keeping the
+happy-path clean of control-flow and functor unpacking.  It does this
+with 10 combinators, that abstract over binary co-products (`Bool`,
+`Maybe`, `Either`, `Validation`) and Traversables or Folds over them.
+
+```haskell
+import Railroad.MonadError
+import Control.Monad.Except
+
+example :: Either String Int
+example = runExcept $ do
+ -- False ? "error" would make the block short circuit to Left "error"
+ 
+ x <- pure (Just 2) ? "Value missing"
+ 
+ -- Right (Just 1) ~> Just 1 ~> 1
+ y <- pure (Right $ Just 1) ? "Outer fail" ? "Inner fail"
+ 
+ -- [Just 4] ~> [4] ~> 4
+ z <- pure [Just 4] ? "List failed" ?! const "Not a single element"
+ 
+ q <- pure Nothing ?~ 1 
+ 
+ pure (x + y + z + q) -- ~> Right 8 
+```
+
+You just need to work in a monad which supports a `MonadError`-like
+context. Either by being an instance of `MonadError` or in an Eff
+stack with `Error`.
+
+[![Haskell](https://img.shields.io/badge/language-Haskell-%23612a7e.svg)](https://www.haskell.org/)
+[![License](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](LICENSE)
+
+---
+
+## Install
+
+### Nix
+
+Here is an expression you can add to `ghcWithPackages`:
+```nix
+railroad = pkgs.haskellPackages.callCabal2nix "railroad" (pkgs.fetchFromGitHub 
+  { owner  = "mastratisi"
+  ; repo   = "railroad"
+  ; rev    = "master"
+  ; sha256 = pkgs.lib.fakeSha256; # Nix will tell you the real hash on first build   
+  }) {};
+```
+
+### Hackage
+The package is under the name `railroad`. Link: https://hackage.haskell.org/package/railroad
+
+
+
+## Example: a servant backend
+
+Handlers in a servant + `effectful` backend (adapted from a real one)
+are chains of queries and checks where each failure is an HTTP status.
+The queries return `Eff es (Either DbError [row])`: a database failure
+wrapping the rows. With `railroad`:
+
+```haskell
+getUser :: Error ServerError :> es => UserId -> Eff es UserDTO
+getUser uid = do
+  user <- runQuery (userById uid) ? err503 ?! cardinalityErr err404 (const err500)
+  pure (toUserDTO user)
+
+register :: Error ServerError :> es => Email -> Eff es Token
+register email = do
+  runQuery (userByEmail email) ? err503 ?∅ const err409   -- must not exist yet
+  makeJWT email ? err500
+
+updateAsset :: Error ServerError :> es => UserId -> Asset -> Eff es ()
+updateAsset uid asset = do
+  _ <- runQuery (permitted uid asset) ? err500 ? err403   -- db error, then "not permitted"
+  ...
+```
+
+Each operator peels one layer, left to right: `? err503` unwraps the
+`Either`, then `?!` demands exactly one row (none is a 404, several a
+500). The same handlers in plain Haskell:
+
+```haskell
+getUser' uid = do
+  rows <- runQuery (userById uid) >>= either (const (throwError err503)) pure
+  user <- case rows of
+    []  -> throwError err404
+    [u] -> pure u
+    _   -> throwError err500
+  pure (toUserDTO user)
+
+register' email = do
+  rows <- runQuery (userByEmail email) >>= either (const (throwError err503)) pure
+  unless (null rows) (throwError err409)
+  makeJWT email >>= either (const (throwError err500)) pure
+
+updateAsset' uid asset = do
+  allowed <- runQuery (permitted uid asset) >>= either (const (throwError err500)) pure
+  unless (and allowed) (throwError err403)
+  ...
+```
+
+The failure paths no longer take up lines, and the order of the layers is
+the order you read them in. Validating a request with a `Validation`
+reports every problem at once: `pure (map validateFile files) ?? badRequest`
+turns all the invalid files into one 400. `Railroad.MonadError` has the
+same operators for plain `MonadError`, including servant's `Handler`.
+
+## Understanding the Operators
+
+| Operator | Purpose                                       | Example                       |
+|----------|-----------------------------------------------|-------------------------------|
+| `??`     | Derail on error with custom error mapping     | `action ?? toMyError`         |
+| `?`      | Derail with constant error                    | `action ? MyError`            |
+| `?>`     | Derail on predicate                           | `(action ?> isGood) toErr`    |
+| `??~`    | Recover with a mapped default (error → value) | `action ??~ toDefaultVal`     |
+| `?~`     | Recover with a const default value            | `action ?~ defaultVal`        |
+| `?+`     | Derail on empty collection                    | `items ?+ NoResults`          |
+| `?!`     | Derail if not exactly one element             | `items ?! fromCardinalityErr` |
+| `?∅`     | Derail on non-empty collections (alias `?@`)  | `items ?∅ DuplicateFound`     |
+| `?\|`    | Fall back to another action on error         | `cache k ?\| db k ? NotFound` |
+| `?\|<>`  | Fall back, combining the errors with `<>`     | `a ?\|<> b ?? toErr`         |
+
+
+For the semantics of the operators there are 3 relevant questions:
+What counts as an error?  What happens in the error case?  What
+happens in the success case?
+
+
+### What counts as an error?
+Here we can split the operators into two families. Those that
+interpret errors on the structure of Functors and Traversable of
+Functor. Those that interpret error on the cardinality of Foldables.
+
+For the former, they all (`?`, `??`, `?~`, `??~`) interpret what
+an error is the same way:
+
+ | Functor          | Error Constructor    | Error Info (`CErr`) | Result Info (`CRes`) |
+ |------------------|----------------------|---------------------|----------------------|
+ | `Bool`           | `False`              | `()`                | `()`                 |
+ | `Maybe a`        | `Nothing`            | `()`                | `a`                  |
+ | `Either e a`     | `Left`               | `e`                 | `a`                  |
+ | `Validation e a` | `Failure`            | `e`                 | `a`                  |
+ | `t f`            | Any element is error | `CErr f`            | `t (CRes f)`         |
+
+
+For the latter each operator (`?+`, `?!`, `?∅`) interpret what
+cardinalities of a Foldable counts as errors differently. To wit:
+
+| Operator    | Success Condition   | Error Info               | Result Info |
+|:------------|:--------------------|:-------------------------|:------------|
+| `?+`        | Non-empty           | `()`                     | `t a`       |
+| `?!`        | Exactly one element | `CardinalityError (t a)` | `a`         |
+| `?∅` / `?@` | Is empty            | `t a`                    | `()`        |
+
+Where
+```haskell
+-- | Structurally a @Maybe ta@ for cardinality failures
+data CardinalityError ta = IsEmpty | TooMany ta
+
+-- | Catamorphism for CardinalityError
+cardinalityErr :: e -> (ta -> e) -> CardinalityError ta -> e
+```
+
+### What happens in the error case?
+When an error is detected, the railroad "derails":
+
+1. The provided error-mapping function is applied to the **Error
+   Info** (`CErr` or `CardinalityError`).
+2. The result of the error mapping is thrown
+3. This causes early termination of the monadic sequence.
+
+**Recovery Operators (`?~`, `??~`):** Unlike the other operators,
+these do **not** derail the computation. Instead, they provide a
+"switch" to bring the logic back onto the happy path by providing a
+default value through a recovery function.
+
+
+**Traversal case:** For `Bool`, `Maybe`, `Either` the error
+accumulation is short-circuiting and stops at the first error. For 
+`t (Validation e a)` the semigroup constraint on `e` is used to
+accumulate all errors together. For more information on accumulating
+errors, see the
+[validation](https://hackage.haskell.org/package/validation/docs/Data-Validation.html)
+package on Hackage.
+### What happens in the success case?
+In the success case the result is unwrapped or validated (`?>`, `?+`)
+and the monad continues its execution. Result Info in the above tables
+shows the resulting type inside the monad.
+
+### Fallback operators
+`?|` and `?|<>` try one source after another and stop at the first
+success. They are for sources that may be different structures (for
+example a cache returning `Maybe` and a database returning `Either`)
+with the same success type (`CRes`). The result is bifurcatable, so a
+final `?` or `??` turns a total failure into a throw:
+
+```haskell
+cache k ?| db k ?| legacy k ? NotFound
+```
+
+`?|` keeps the last error. `?|<>` also needs the sources to share the
+error type (`CErr`) and combines all the errors with `<>`, in source
+order, when every source fails. Map differing error types to a common one
+first, for example with `note`-like functions:
+
+```haskell
+(cache k <&> note [CacheMiss]) ?|<> (db k <&> first pure) ?? NotFound
+```
+
+Neither throws by itself, and the right-hand side only runs if the left
+one failed. Unlike `Validation`, which runs all independent checks and
+collects every failure, a fallback chain succeeds on the first good answer.
+
+## More
+See [`railroad.md`](railroad.md) for a deeper tour, or just read the
+code at [`Railroad.hs`](src/Railroad.hs). It's a short file.
+
+## Contact
+Feel free to DM me on https://x.com/mastratisi97 . I find it
+interesting to know if other people also have found this module
+useful.
diff --git a/src/Railroad.hs b/src/Railroad.hs
--- a/src/Railroad.hs
+++ b/src/Railroad.hs
@@ -4,6 +4,10 @@
 {-# LANGUAGE TypeFamilies         #-}
 {-# LANGUAGE UndecidableInstances #-}
 
+-- |
+-- Railway-oriented operators: turn a failure in a 'Bifurcate' structure (@Bool@, @Maybe@,
+-- @Either@, @Validation@, traversables of them) or a bad cardinality into a throw in the
+-- @Error@ effect, keeping the happy path clean. For @MonadError@ use "Railroad.MonadError".
 module Railroad where
 
 import           Data.Bool               (bool)
@@ -14,12 +18,16 @@
 import           Effectful.Error.Dynamic (Error, throwError_)
 
 
+-- | What the failure case carries: @()@ for 'Bool' and 'Maybe', @e@ for 'Either' and
+-- 'Validation', that of the element for a traversable.
 type family CErr f :: Type where
   CErr Bool               = ()
   CErr (Maybe a)          = ()
   CErr (Either e a)       = e
   CErr (Validation e a)   = e
   CErr (t a)              = CErr a
+-- | What the success case carries: @()@ for 'Bool', @a@ for 'Maybe', 'Either' and
+-- 'Validation', @t (CRes a)@ for a traversable @t a@.
 type family CRes f :: Type where
   CRes Bool               = ()
   CRes (Maybe a)          = a
@@ -60,45 +68,74 @@
     => Bifurcate (t (Validation e a)) where
   bifurcate = bifurcate . sequenceA
 
--- | Collapses a structure into its inner type or an effectful error
+-- | Unwraps the success case, or throws the failure mapped by the function.
 collapse :: (Error e :> es, Bifurcate f) => (CErr f -> e) -> f -> Eff es (CRes f)
 collapse toErr = either (throwError_ . toErr) pure . bifurcate
 
 
--- | Collapses a structure using an error mapper.
+-- | Unwraps the success case, or throws the error info mapped by the function.
 (??) :: forall a es e. (Error e :> es, Bifurcate a) => Eff es a -> (CErr a -> e) -> Eff es (CRes a)
 action ?? toErr = action >>= collapse toErr
 
--- | Collapses a structure using a constant error.
+-- | Unwraps the success case, or throws a constant error. Chains to peel nested layers:
+-- @m ? e1 ? e2@.
 (?) :: forall es e a. (Error e :> es, Bifurcate a) => Eff es a -> e -> Eff es (CRes a)
 action ? err = action ?? const err
 
--- | Collapses any value based on a predicate
+-- | Passes the value on if it satisfies the predicate, else throws the error built from
+-- it: @(m ?> p) toErr@.
 (?>) :: forall es e a. (Error e :> es) => Eff es a -> (a -> Bool) -> (a -> e) -> Eff es a
 (?>) action predicate toErr = do
   val <- action
   if predicate val then pure val else throwError_ $ toErr val
 
--- | Collapses a structure and recovers to a value dependent on the error
+-- | Unwraps the success case, or recovers with a value computed from the error info.
 (??~) :: forall es a. Bifurcate a => Eff es a -> (CErr a -> CRes a) -> Eff es (CRes a)
 action ??~ defaultFunc = action >>= either (pure . defaultFunc) pure . bifurcate
 
--- | Collapses a structure, and recovers to a constant default value in the error case
+-- | Unwraps the success case, or recovers with a default value.
 (?~) :: forall es a. Bifurcate a => Eff es a -> CRes a -> Eff es (CRes a)
 action ?~ defaultVal = action ??~ (const defaultVal)
 
 
--- | @IsEmpty@ or @TooMany ta@ — structurally a @Maybe ta@ for cardinality failures
+-- | Fallback: runs the right action only if the left fails. The sides may be different
+-- structures with the same success type; the last error is kept. Finish with '?' or '??'.
+--
+-- > cache k ?| db k ?| legacy k ? NotFound
+(?|) :: forall m a b. (Monad m, Bifurcate a, Bifurcate b, CRes a ~ CRes b)
+     => m a -> m b -> m (Either (CErr b) (CRes a))
+ma ?| mb = do
+  a <- ma
+  case bifurcate a of
+    Right x -> pure (Right x)
+    Left _  -> bifurcate <$> mb
+
+-- | Like '?|', but if every source fails their errors are combined with '<>', in order.
+-- The sides must also share the error type; map it to a common one first.
+(?|<>) :: forall m a b. ( Monad m, Bifurcate a, Bifurcate b, CRes a ~ CRes b
+                        , CErr a ~ CErr b, Semigroup (CErr a) )
+       => m a -> m b -> m (Either (CErr a) (CRes a))
+ma ?|<> mb = do
+  a <- ma
+  case bifurcate a of
+    Right x  -> pure (Right x)
+    Left ea  -> do
+      b <- mb
+      pure $ case bifurcate b of
+        Right y  -> Right y
+        Left eb  -> Left (ea <> eb)
+
+-- | Why a collection was not a single element: empty, or too many (carrying it).
 data CardinalityError ta = IsEmpty | TooMany ta
 
--- | Catamorphism for CardinalityError
+-- | Fold for 'CardinalityError': one result per case.
 cardinalityErr :: e -> (ta -> e) -> CardinalityError ta -> e
 cardinalityErr onEmpty onTooMany = \case
   IsEmpty -> onEmpty
   TooMany xs -> onTooMany xs
 
 
--- |  Non-empty is success state. Returns the collection as-is.
+-- | Succeeds if non-empty, returning the collection; else throws the constant error.
 (?+) :: forall es e t a. (Error e :> es, Foldable t)
      => Eff es (t a) -> e -> Eff es (t a)
 (?+) action err = do
@@ -106,7 +143,8 @@
   if null xs then throwError_ err else pure xs
 
 
--- | Single element is success state.  Returns the single element
+-- | Succeeds if there is exactly one element, returning it; else throws the error built
+-- from the 'CardinalityError'.
 (?!) :: forall es e t a. (Error e :> es, Foldable t)
      => Eff es (t a) -> (CardinalityError (t a) -> e) -> Eff es a
 (?!) action toErr = do
@@ -117,17 +155,17 @@
     _   -> throwError_ $ toErr $ TooMany xs
 
 
--- | Empty is success state. Returns Unit.
+-- | Succeeds if empty, returning @()@; else throws the error built from the collection.
 (?∅) :: forall es e t a. (Error e :> es, Foldable t)
      => Eff es (t a) -> ((t a) -> e) -> Eff es ()
 (?∅) action toErr = do
   xs <- action
   if null xs then pure () else throwError_ (toErr xs)
 
--- | Non-unicode alias for `(?∅)`
+-- | ASCII alias for '?∅'.
 (?@) :: forall es e t a. (Error e :> es, Foldable t)
      => Eff es (t a) -> ((t a) -> e) -> Eff es ()
 (?@) = (?∅)
 
-infixl  0 ??, ?, ??~, ?~, ?!, ?+, ?∅, ?@
+infixl  0 ??, ?, ??~, ?~, ?!, ?+, ?∅, ?@, ?|, ?|<>
 infixl 1 ?>
diff --git a/src/Railroad/MonadError.hs b/src/Railroad/MonadError.hs
--- a/src/Railroad/MonadError.hs
+++ b/src/Railroad/MonadError.hs
@@ -1,9 +1,6 @@
 -- |
--- Re-implementation of Railroad operators ('??', '?', '?>', etc.)
--- using 'MonadError' / 'throwError' instead of the Effectful 'Error' effect.
---
--- Use this module in classic @mtl@ / @transformers@ / @ExceptT@ code.
--- For Effectful prefer the versions from "Railroad".
+-- The operators of "Railroad" for any 'MonadError' (@mtl@, @transformers@, @ExceptT@,
+-- servant's @Handler@) instead of the @Error@ effect. In an @effectful@ stack use "Railroad".
 {-# LANGUAGE AllowAmbiguousTypes #-}
 {-# LANGUAGE FlexibleContexts    #-}
 
@@ -19,44 +16,47 @@
 import           Control.Monad.Except (MonadError (..))
 import           Data.Foldable        (toList)
 
--- | Collapses a structure into its inner type or a `MonadError` error.
+-- | Unwraps the success case, or throws the failure mapped by the function.
 collapse :: (MonadError e m, Bifurcate f)
          => (CErr f -> e) -> f -> m (CRes f)
 collapse toErr = either (throwError . toErr) pure . bifurcate
 
--- | Collapses a structure using an error mapper.
+-- | Unwraps the success case, or throws the error info mapped by the function.
 (??) :: forall a m e. (MonadError e m, Bifurcate a)
      => m a -> (CErr a -> e) -> m (CRes a)
 action ?? toErr = action >>= collapse toErr
 
--- | Collapses a structure using a constant error.
+-- | Unwraps the success case, or throws a constant error. Chains to peel nested layers:
+-- @m ? e1 ? e2@.
 (?) :: forall m e a. (MonadError e m, Bifurcate a)
     => m a -> e -> m (CRes a)
 action ? err = action ?? const err
 
--- | Collapses any value based on a predicate
+-- | Passes the value on if it satisfies the predicate, else throws the error built from
+-- it: @(m ?> p) toErr@.
 (?>) :: forall m e a. (MonadError e m)
      => m a -> (a -> Bool) -> (a -> e) -> m a
 (?>) action predicate toErr = do
   val <- action
   if predicate val then pure val else throwError $ toErr val
 
--- | Collapses a structure and recovers to a value dependent on the error
+-- | Unwraps the success case, or recovers with a value computed from the error info.
 (??~) :: forall m a. (Bifurcate a, Monad m) => m a -> (CErr a -> CRes a) -> m (CRes a)
 action ??~ defaultFunc = action >>= either (pure . defaultFunc) pure . bifurcate
 
--- | Collapses a structure, and recovers to a constant default value in the error case
+-- | Unwraps the success case, or recovers with a default value.
 (?~) :: forall m a. (Bifurcate a, Monad m) => m a -> CRes a -> m (CRes a)
 action ?~ defaultVal = action ??~ (const defaultVal)
 
--- |  Non-empty is success state. Returns the collection as-is.
+-- | Succeeds if non-empty, returning the collection; else throws the constant error.
 (?+) :: forall m e t a. (MonadError e m, Foldable t)
      => m (t a) -> e -> m (t a)
 (?+) action err = do
   xs <- action
   if null xs then throwError err else pure xs
 
--- | Single element is success state.  Returns the single element
+-- | Succeeds if there is exactly one element, returning it; else throws the error built
+-- from the 'CardinalityError'.
 (?!) :: forall m e t a. (MonadError e m, Foldable t)
      => m (t a) -> (CardinalityError (t a) -> e) -> m a
 (?!) action toErr = do
@@ -66,14 +66,14 @@
     [x] -> pure x
     _   -> throwError $ toErr $ TooMany xs
 
--- | Empty is success state. Returns Unit.
+-- | Succeeds if empty, returning @()@; else throws the error built from the collection.
 (?∅) :: forall m e t a. (MonadError e m, Foldable t)
      => m (t a) -> (t a -> e) -> m ()
 (?∅) action toErr = do
   xs <- action
   if null xs then pure () else throwError (toErr xs)
 
--- | Non-unicode alias for `(?∅)`
+-- | ASCII alias for '?∅'.
 (?@) :: forall m e t a. (MonadError e m, Foldable t)
      => m (t a) -> (t a -> e) -> m ()
 (?@) = (?∅)
diff --git a/test/Railroad/MonadErrorSpec.hs b/test/Railroad/MonadErrorSpec.hs
--- a/test/Railroad/MonadErrorSpec.hs
+++ b/test/Railroad/MonadErrorSpec.hs
@@ -4,6 +4,8 @@
 module Railroad.MonadErrorSpec where
 
 import           Control.Monad.Except
+import           Control.Monad.State   (State, modify, runState)
+import           Data.Validation       (Validation (..))
 import           Data.Functor.Identity
 import           Railroad.MonadError
 import           Test.Hspec
@@ -12,6 +14,13 @@
 runMonadError :: ExceptT String Identity a -> Either String a
 runMonadError = runIdentity . runExceptT
 
+-- Like 'runMonadError', and counts how many 'tick's ran.
+runCount :: ExceptT String (State Int) a -> (Either String a, Int)
+runCount m = runState (runExceptT m) 0
+
+tick :: ExceptT String (State Int) ()
+tick = modify (+ 1)
+
 spec :: Spec
 spec = do
   describe "MonadError version of Operators" $ do
@@ -57,3 +66,39 @@
           runMonadError (pure [] ?∅ const "not empty") `shouldBe` Right ()
         it "fails on non-empty" $ do
           runMonadError (pure [1 :: Int] ?∅ const "not empty") `shouldBe` Left "not empty"
+
+    describe "Fallback Operator (?|)" $ do
+      it "does not run the right action when the left succeeds" $ do
+        runCount (pure (Just 'a') ?| (tick >> pure (Nothing :: Maybe Char)) ? "none")
+          `shouldBe` (Right 'a', 0)
+      it "runs the right action when the left fails" $ do
+        runCount (pure (Nothing :: Maybe Int) ?| (tick >> pure (Just 7)) ? "none")
+          `shouldBe` (Right 7, 1)
+      it "keeps the last error when everything fails" $ do
+        runCount (pure (Left "first" :: Either String Int) ?| pure (Left "second") ?? id)
+          `shouldBe` (Left "second", 0)
+        runCount (pure (Nothing :: Maybe Int) ?| pure Nothing ? "none")
+          `shouldBe` (Left "none", 0)
+      it "chains mixed structures, stopping at the first success" $ do
+        let chain = pure (Nothing :: Maybe Int) ?| pure (Left "db" :: Either String Int)
+                      ?| (tick >> pure (Just 3)) ?| (tick >> tick >> pure (Just 4)) ? "gone"
+        runCount chain `shouldBe` (Right 3, 1)
+
+    describe "Accumulating Fallback Operator (?|<>)" $ do
+      it "does not run the right action when the left succeeds" $ do
+        runCount (pure (Just 'a') ?|<> (tick >> pure (Nothing :: Maybe Char)) ? "none")
+          `shouldBe` (Right 'a', 0)
+      it "combines the errors in source order when everything fails" $ do
+        runCount (pure (Left "A" :: Either String Int) ?|<> pure (Left "B") ?|<> pure (Left "C") ?? id)
+          `shouldBe` (Left "ABC", 0)
+      it "mixes structures with the same error and success types" $ do
+        runCount (pure (Left "A" :: Either String Int) ?|<> pure (Failure "B" :: Validation String Int) ?? id)
+          `shouldBe` (Left "AB", 0)
+        runCount (pure (Left "A" :: Either String Int) ?|<> (tick >> pure (Success 5 :: Validation String Int)) ?? id)
+          `shouldBe` (Right 5, 1)
+      it "drops earlier errors once a later source succeeds" $ do
+        runCount (pure (Left "A" :: Either String Int) ?|<> pure (Left "B") ?|<> pure (Right 3) ?? id)
+          `shouldBe` (Right 3, 0)
+      it "needs nothing special for Maybe sources" $ do
+        runCount (pure (Nothing :: Maybe Int) ?|<> pure Nothing ? "none")
+          `shouldBe` (Left "none", 0)
diff --git a/test/RailroadSpec.hs b/test/RailroadSpec.hs
--- a/test/RailroadSpec.hs
+++ b/test/RailroadSpec.hs
@@ -6,6 +6,7 @@
 import           Data.Validation
 import           Effectful
 import           Effectful.Error.Dynamic
+import           Effectful.State.Static.Local
 import           Railroad
 import           Test.Hspec
 
@@ -13,6 +14,13 @@
 runRail :: Eff '[Error String] a -> Either String a
 runRail = runPureEff . runErrorNoCallStack
 
+-- Like 'runRail', and counts how many 'tick's ran.
+runCount :: Eff '[Error String, State Int] a -> (Either String a, Int)
+runCount = runPureEff . runState 0 . runErrorNoCallStack
+
+tick :: State Int :> es => Eff es ()
+tick = modify (+ (1 :: Int))
+
 spec :: Spec
 spec = do
   describe "Bifurcate Instances" $ do
@@ -99,3 +107,46 @@
         runRail (pure [] ?∅ const "not empty") `shouldBe` Right ()
       it "fails on non-empty" $ do
         runRail (pure [1 :: Int] ?∅ const "not empty") `shouldBe` Left "not empty"
+
+  describe "Fallback Operator (?|)" $ do
+    it "does not run the right action when the left succeeds" $ do
+      runCount (pure (Just 'a') ?| (tick >> pure (Nothing :: Maybe Char)) ? "none")
+        `shouldBe` (Right 'a', 0)
+
+    it "runs the right action when the left fails" $ do
+      runCount (pure (Nothing :: Maybe Int) ?| (tick >> pure (Just 7)) ? "none")
+        `shouldBe` (Right 7, 1)
+
+    it "keeps the last error when everything fails" $ do
+      runCount (pure (Left "first" :: Either String Int) ?| pure (Left "second") ?? id)
+        `shouldBe` (Left "second", 0)
+      runCount (pure (Nothing :: Maybe Int) ?| pure Nothing ? "none")
+        `shouldBe` (Left "none", 0)
+
+    it "chains mixed structures, stopping at the first success" $ do
+      let chain = pure (Nothing :: Maybe Int) ?| pure (Left "db" :: Either String Int)
+                    ?| (tick >> pure (Just 3)) ?| (tick >> tick >> pure (Just 4)) ? "gone"
+      runCount chain `shouldBe` (Right 3, 1)
+
+  describe "Accumulating Fallback Operator (?|<>)" $ do
+    it "does not run the right action when the left succeeds" $ do
+      runCount (pure (Just 'a') ?|<> (tick >> pure (Nothing :: Maybe Char)) ? "none")
+        `shouldBe` (Right 'a', 0)
+
+    it "combines the errors in source order when everything fails" $ do
+      runCount (pure (Left "A" :: Either String Int) ?|<> pure (Left "B") ?|<> pure (Left "C") ?? id)
+        `shouldBe` (Left "ABC", 0)
+
+    it "mixes structures with the same error and success types" $ do
+      runCount (pure (Left "A" :: Either String Int) ?|<> pure (Failure "B" :: Validation String Int) ?? id)
+        `shouldBe` (Left "AB", 0)
+      runCount (pure (Left "A" :: Either String Int) ?|<> (tick >> pure (Success 5 :: Validation String Int)) ?? id)
+        `shouldBe` (Right 5, 1)
+
+    it "drops earlier errors once a later source succeeds" $ do
+      runCount (pure (Left "A" :: Either String Int) ?|<> pure (Left "B") ?|<> pure (Right 3) ?? id)
+        `shouldBe` (Right 3, 0)
+
+    it "needs nothing special for Maybe sources" $ do
+      runCount (pure (Nothing :: Maybe Int) ?|<> pure Nothing ? "none")
+        `shouldBe` (Left "none", 0)
