diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Mathieu Boespflug (c) 2017
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Mathieu Boespflug nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,6 @@
+# streaming-binary
+
+This library offers incremental serialization and deserialization of
+Haskell values using the `binary` package. Under the hood, we wrap the
+incremental decoders from `binary`'s Incremental API as first-class
+streams as defined in `streaming`.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Streaming/Binary.hs b/src/Streaming/Binary.hs
new file mode 100644
--- /dev/null
+++ b/src/Streaming/Binary.hs
@@ -0,0 +1,57 @@
+-- | This module implements a method to ingest a sequence of "Data.Binary"
+-- encoded records using bounded memory. Minimal example:
+--
+-- > {-# LANGUAGE TypeApplications #-}
+-- >
+-- > import Data.Function ((&))
+-- > import qualified Data.ByteString.Streaming as Q
+-- > import Streaming
+-- > import Streaming.Binary
+-- > import qualified Streaming.Prelude as S
+-- >
+-- > -- Interpret all bytes on stdin as a sequence of integers.
+-- > -- Print them on-the-fly on stdout.
+-- > main = Q.getContents & decoded @Int & S.print
+
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Streaming.Binary (decoded) where
+
+import qualified Data.Binary.Get as Binary
+import Data.Binary (Binary, get)
+import qualified Data.ByteString.Streaming as Q
+import Data.ByteString.Streaming (ByteString)
+import Data.Int (Int64)
+import Streaming
+import qualified Streaming.Prelude as S
+
+_decode
+  :: (Binary a, Monad m)
+  => ByteString m r
+  -> m (ByteString m r, Int64, Either String a)
+_decode = undefined -- TODO implement this.
+
+decoded
+  :: (Binary a, Monad m)
+  => ByteString m r
+  -> Stream (Of a) m (ByteString m r, Int64, Either String r)
+decoded = go 0 decoder0
+  where
+    decoder0 = Binary.runGetIncremental get
+    go !total (Binary.Fail leftover nconsumed err) p = do
+        return (Q.chunk leftover >> p, total + nconsumed, Left err)
+    go !total (Binary.Done "" nconsumed x) p = do
+        S.yield x
+        lift (Q.nextChunk p) >>= \case
+          Left res -> return (return res, total + nconsumed, Right res)
+          Right (bs, p') -> do
+            go (total + nconsumed) decoder0 (Q.chunk bs >> p')
+    go !total (Binary.Done leftover nconsumed x) p = do
+        S.yield x
+        go (total + nconsumed) decoder0 (Q.chunk leftover >> p)
+    go !total (Binary.Partial k) p = do
+      lift (Q.nextChunk p) >>= \case
+        Left res -> go total (k Nothing) (return res)
+        Right (bs, p') -> go total (k (Just bs)) p'
diff --git a/streaming-binary.cabal b/streaming-binary.cabal
new file mode 100644
--- /dev/null
+++ b/streaming-binary.cabal
@@ -0,0 +1,45 @@
+name: streaming-binary
+version: 0.1.0.0
+synopsis: Streaming interface to binary.
+homepage: https://github.com/mboes/streaming-binary#readme
+license: BSD3
+license-file: LICENSE
+author: Mathieu Boespflug
+maintainer: m@tweag.io
+copyright: (c) 2017 Mathieu Boespflug
+category: Streaming, Parsing
+build-type: Simple
+extra-source-files: README.md
+cabal-version: >=1.10
+
+source-repository head
+  type: git
+  location: https://github.com/mboes/streaming-binary
+
+library
+  hs-source-dirs: src
+  exposed-modules: Streaming.Binary
+  build-depends:
+    base >= 4.7 && < 5,
+    binary >= 0.8,
+    streaming >= 0.1.4,
+    streaming-bytestring >= 0.1.4
+  default-language: Haskell2010
+
+test-suite tests
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test
+  main-is: Main.hs
+  other-modules:
+    Spec
+    Streaming.BinarySpec
+  build-depends:
+    base,
+    binary,
+    bytestring >= 0.10,
+    hspec >= 2.4,
+    streaming,
+    streaming-binary,
+    streaming-bytestring
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  default-language: Haskell2010
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,7 @@
+module Main where
+
+import qualified Spec
+import Test.Hspec
+
+main :: IO ()
+main = hspec Spec.spec
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --module-name=Spec #-}
diff --git a/test/Streaming/BinarySpec.hs b/test/Streaming/BinarySpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Streaming/BinarySpec.hs
@@ -0,0 +1,36 @@
+{-# LANGUAGE TypeApplications #-}
+
+module Streaming.BinarySpec where
+
+import Control.Monad (replicateM_, void)
+import Data.Binary (put)
+import Data.Binary.Put (runPut)
+import Data.Function ((&))
+import qualified Data.ByteString.Streaming as Q
+import Streaming.Binary
+import qualified Streaming.Prelude as S
+import Test.Hspec
+
+spec :: Spec
+spec = do
+    let input n = Q.fromLazy $ runPut $ replicateM_ n $ put (42 :: Int)
+    describe "decoded" $ do
+      it "succeeds on empty inputs" $ do
+        output <- void (decoded @Int (input 0)) & S.toList_
+        output `shouldBe` []
+      it "decodes single integers" $ do
+        output <- void (decoded @Int (input 1)) & S.toList_
+        output `shouldBe` [42]
+      it "decodes multiple integers" $ do
+        output <- void (decoded @Int (input 10)) & S.toList_
+        output `shouldBe` (replicate 10 42)
+      it "decodes multiple integers even when the input is incomplete" $ do
+        n <- fromIntegral <$> Q.length_ (input 10)
+        let input' = Q.take (n - 1) (input 10)
+        output <- void (decoded @Int input') & S.toList_
+        output `shouldBe` (replicate 9 42)
+      it "leaves the right amount of leftover on incomplete input" $ do
+        n <- Q.length_ (input 10)
+        let input' = Q.take (fromIntegral (n - 1)) (input 10)
+        (leftover, _, _) <- S.effects (decoded @Int input')
+        Q.length_ leftover `shouldReturn` (n `div` 10) - 1
