diff --git a/attoparsec-run.cabal b/attoparsec-run.cabal
new file mode 100644
--- /dev/null
+++ b/attoparsec-run.cabal
@@ -0,0 +1,45 @@
+cabal-version: 3.0
+
+name: attoparsec-run
+version: 0.0.0.0
+synopsis: Conveniently run Attoparsec parsers
+category: Parsing
+
+description: This package fixes a number of problems with the API that
+    Attoparsec provides for running parsers. The difficulties stem from
+    the that that Attoparsec's @IResult@ type encompasses three situations:
+    When parsing has succeeded, when parsing has failed, and when parsing
+    is awaiting further input. This is insufficient to describe situations
+    in which we know we are dealing with a subset of these three cases.
+    We address this by introducing two smaller types: @FinalResult@ and
+    @ParseError@.
+
+license: Apache-2.0
+license-file: license.txt
+
+author: Chris Martin
+maintainer: Chris Martin, Julie Moronuki
+
+homepage: https://github.com/typeclasses/attoparsec-run
+bug-Reports: https://github.com/typeclasses/attoparsec-run/issues
+
+extra-source-files: *.md
+
+source-repository head
+    type: git
+    location: git://github.com/typeclasses/attoparsec-run.git
+
+library
+    default-language: Haskell2010
+    ghc-options: -Wall
+    hs-source-dirs: library
+    build-depends:
+      , attoparsec ^>= 0.14.4
+      , base ^>= 4.14 || ^>= 4.15 || ^>= 4.16 || ^>= 4.17
+      , bytestring ^>= 0.10.12 || ^>= 0.11
+      , text ^>= 1.2.4 || ^>= 2.0
+    exposed-modules:
+        Data.Attoparsec.Run
+
+        Data.Attoparsec.ByteString.Run
+        Data.Attoparsec.Text.Run
diff --git a/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,3 @@
+### 0.0.0.0 (2023-01-09)
+
+Initial release
diff --git a/library/Data/Attoparsec/ByteString/Run.hs b/library/Data/Attoparsec/ByteString/Run.hs
new file mode 100644
--- /dev/null
+++ b/library/Data/Attoparsec/ByteString/Run.hs
@@ -0,0 +1,26 @@
+module Data.Attoparsec.ByteString.Run
+  (
+    parseStream, parseAndRestore,
+    module Data.Attoparsec.Run,
+  )
+  where
+
+import Data.Attoparsec.Run
+
+import Control.Monad (unless)
+import Data.Attoparsec.ByteString (parseWith, Parser)
+import Data.ByteString (ByteString)
+import Data.ByteString (null)
+import Prelude (($), (<$>), pure, mempty, Monad, Either)
+
+parseStream :: Monad m =>
+    BufferedInput m ByteString -> Parser a -> m (FinalResult ByteString a)
+parseStream (BufferedInput initial get) p =
+    finalizeResult <$> parseWith get p initial
+
+parseAndRestore :: Monad m =>
+    RestorableInput m ByteString -> Parser a -> m (Either ParseError a)
+parseAndRestore (RestorableInput get restore) p = do
+    FinalResult remainder value <- parseStream (BufferedInput mempty get) p
+    unless (null remainder) $ restore remainder
+    pure value
diff --git a/library/Data/Attoparsec/Run.hs b/library/Data/Attoparsec/Run.hs
new file mode 100644
--- /dev/null
+++ b/library/Data/Attoparsec/Run.hs
@@ -0,0 +1,41 @@
+module Data.Attoparsec.Run where
+
+import Data.Attoparsec.Types
+
+import Data.List (intercalate)
+import Prelude (Either (..), Eq, Ord, Show, String, error, otherwise, null, (++))
+
+data FinalResult i a = FinalResult
+    i -- ^ Remaining unparsed input
+    (Either ParseError a) -- ^ Either an error or a successfully parsed value
+    deriving (Eq, Ord, Show)
+
+finalizeResult ::
+    IResult i a -- ^ Must be either 'Done' or 'Fail', not 'Partial'
+    -> FinalResult i a
+finalizeResult r = case r of
+    Done remainder v ->
+        FinalResult remainder (Right v)
+    Fail remainder context message ->
+        FinalResult remainder (Left (ParseError context message))
+    Partial{} ->
+        error "parseWith should not return Partial"
+
+data ParseError = ParseError
+    [String] -- ^ A list of contexts in which the error occurred
+    String -- ^ The message describing the error, if any
+    deriving (Eq, Ord, Show)
+
+-- | Format a parse error in a matter suitable for displaying in log output
+showParseError :: ParseError -> String
+showParseError (ParseError context message)
+    | null context = message
+    | otherwise = intercalate " > " context ++ ": " ++ message
+
+data BufferedInput m i = BufferedInput
+    i -- ^ Initial input
+    (m i) -- ^ Should return an empty string once the end of input is reached
+
+data RestorableInput m i = RestorableInput
+    (m i) -- ^ Return an empty string once the end of input is reached
+    (i -> m ()) -- ^ Return a non-empty chunk of input to the input stream
diff --git a/library/Data/Attoparsec/Text/Run.hs b/library/Data/Attoparsec/Text/Run.hs
new file mode 100644
--- /dev/null
+++ b/library/Data/Attoparsec/Text/Run.hs
@@ -0,0 +1,26 @@
+module Data.Attoparsec.Text.Run
+  (
+    parseStream, parseAndRestore,
+    module Data.Attoparsec.Run,
+  )
+  where
+
+import Data.Attoparsec.Run
+
+import Control.Monad (unless)
+import Data.Attoparsec.Text (parseWith, Parser)
+import Data.Text (Text)
+import Data.Text (null)
+import Prelude (($), (<$>), pure, mempty, Monad, Either)
+
+parseStream :: Monad m =>
+    BufferedInput m Text -> Parser a -> m (FinalResult Text a)
+parseStream (BufferedInput initial get) p =
+    finalizeResult <$> parseWith get p initial
+
+parseAndRestore :: Monad m =>
+    RestorableInput m Text -> Parser a -> m (Either ParseError a)
+parseAndRestore (RestorableInput get restore) p = do
+    FinalResult remainder value <- parseStream (BufferedInput mempty get) p
+    unless (null remainder) $ restore remainder
+    pure value
diff --git a/license.txt b/license.txt
new file mode 100644
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,13 @@
+Copyright 2023 Mission Valley Software LLC
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/readme.md b/readme.md
new file mode 100644
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,73 @@
+This package fixes a number of problems with the API that Attoparsec provides
+for running parsers.
+
+The difficulties stem from the that that Attoparsec's `IResult` type encompasses
+three situations: When parsing has succeeded, when parsing has failed, and when
+parsing is awaiting further input. This is insufficient to describe situations
+in which we know we are dealing with a subset of these three cases. We address
+this by introducing two smaller types: `FinalResult` and `ParseError`.
+
+### FinalResult
+
+`FinalResult` represents a result that we know not to be partial; for example,
+the sort of result that we get when running a parser using `parseWith`.
+
+```haskell
+data FinalResult i a = FinalResult i (Either ParseError a)
+```
+
+### ParseError
+
+`ParseError` represents only the case in which parsing has failed.
+
+```haskell
+data ParseError = ParseError [String] String
+```
+
+Our `showParseError` function gives an error string in the same format as
+Attoparsec's `eitherResult` function.
+
+```haskell
+showParseError :: ParseError -> String
+```
+
+### BufferedInput
+
+The `BufferedInput` type corresponds to two arguments of Attoparsec's
+`parseWith`, the initial input and the action to obtain more input.
+
+```haskell
+data BufferedInput m i = BufferedInput i (m i)
+```
+
+In each of the modules `Data.Attoparsec.ByteString.Run` and
+`Data.Attoparsec.Text.Run`, we provide a function called `parseStream`. This
+closely corresponds to Attoparsec's `parseWith` function, but ours returns the
+more specific `FinalResult` type, rather than `Result`, reflecting the fact that
+a result returned here is never partial.
+
+```haskell
+parseStream :: Monad m =>
+    BufferedInput m ByteString -> Parser a -> m (FinalResult ByteString a)
+```
+
+### RestorableInput
+
+`RestorableInput` offers a new way to do streaming parsing that may be more
+convenient than `parseStream` and `BufferedInput`. This type represents an input
+stream with the ability to push unused input back to it.
+
+```haskell
+data RestorableInput m i = RestorableInput (m i) (i -> m ())
+```
+
+We use this type with the `parseAndRestore` function:
+
+```haskell
+parseAndRestore :: Monad m =>
+    RestorableInput m ByteString -> Parser a -> m (Either ParseError a)
+```
+
+This shifts the burden of storing the unused remainder to feed back into the
+next parsing step from the user of `parseStream` to the definer of the
+`RestorableInput`.
