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/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,12 @@
 # Unreleased
 
+* Allow `Process` to emit custom chunks constructed by an interpreter of `ProcessOutput` instead of `ByteString`s
+  containing whatever the `Handle` produced.
+* Rename stdio interpreters.
+* Add helper `resolveExecutable` that looks up names in `$PATH` and ensures the files are executable.
+* Add effect `Pty` for interacting with pseudo terminals.
+* Add low-level process abstraction effect, `SystemProcess`.
+
 # 0.5.0.0
 
 * Add the effect `Process`, wrapping `System.Process.Typed` using `Scoped`.
diff --git a/lib/Polysemy/Process.hs b/lib/Polysemy/Process.hs
--- a/lib/Polysemy/Process.hs
+++ b/lib/Polysemy/Process.hs
@@ -3,24 +3,108 @@
   -- * Introduction
   -- $intro
 
-  -- * Effect
-  Process,
+  -- * Effects
+  -- ** Process
+  Process (..),
   recv,
   recvError,
   send,
   withProcess,
 
+  -- ** ProcessOutput
+  ProcessOutput,
+
+  -- ** SystemProcess
+  SystemProcess,
+  withSystemProcess,
+
+  -- ** Pty
+  Pty,
+  withPty,
+
   -- * Interpreters
-  interpretProcessNative,
-  interpretProcessIOE,
+  -- ** Process
+  interpretProcessByteStringNative,
+  interpretProcessByteStringLinesNative,
+  interpretProcessTextNative,
+  interpretProcessTextLinesNative,
+  interpretProcess,
+  interpretProcessByteString,
+  interpretProcessByteStringLines,
+  interpretProcessText,
+  interpretProcessTextLines,
+
+  -- ** ProcessOutput
+  interpretProcessOutputId,
+  interpretProcessOutputLines,
+  interpretProcessOutputText,
+  interpretProcessOutputTextLines,
+
+  -- ** SystemProcess
+  interpretSystemProcessWithProcess,
+  interpretSystemProcessNativeSingle,
+  interpretSystemProcessNative,
+  interpretSystemProcessWithProcessOpaque,
+  interpretSystemProcessNativeOpaqueSingle,
+  interpretSystemProcessNativeOpaque,
+
+  -- ** Pty
+  interpretPty,
+
+  -- * Tools
+  resolveExecutable,
 ) where
 
-import Polysemy.Process.Effect.Process (Process, recv, recvError, send, withProcess)
-import Polysemy.Process.Interpreter.Process (interpretProcessNative)
-import Polysemy.Process.Interpreter.ProcessIOE (interpretProcessIOE)
+import Prelude hiding (send)
 
+import Polysemy.Process.Effect.Process (Process (..), recv, recvError, send, withProcess)
+import Polysemy.Process.Effect.ProcessOutput (ProcessOutput)
+import Polysemy.Process.Effect.Pty (Pty, withPty)
+import Polysemy.Process.Effect.SystemProcess (
+  SystemProcess,
+  withSystemProcess,
+  )
+import Polysemy.Process.Executable (resolveExecutable)
+import Polysemy.Process.Interpreter.Process (
+  interpretProcess,
+  interpretProcessByteString,
+  interpretProcessByteStringLines,
+  interpretProcessText,
+  interpretProcessTextLines,
+  )
+import Polysemy.Process.Interpreter.ProcessOutput (
+  interpretProcessOutputId,
+  interpretProcessOutputLines,
+  interpretProcessOutputText,
+  interpretProcessOutputTextLines,
+  )
+import Polysemy.Process.Interpreter.ProcessStdio (
+  interpretProcessByteStringLinesNative,
+  interpretProcessByteStringNative,
+  interpretProcessTextLinesNative,
+  interpretProcessTextNative,
+  )
+import Polysemy.Process.Interpreter.Pty (interpretPty)
+import Polysemy.Process.Interpreter.SystemProcess (
+  interpretSystemProcessNative,
+  interpretSystemProcessNativeOpaque,
+  interpretSystemProcessNativeOpaqueSingle,
+  interpretSystemProcessNativeSingle,
+  interpretSystemProcessWithProcess,
+  interpretSystemProcessWithProcessOpaque,
+  )
+
 -- $intro
 -- This library provides an abstraction of a system process in the effect 'Process', whose constructors represent the
 -- three standard file descriptors.
 --
--- The values produced by the constructors are chunks of the process' output when using the default interpreter.
+-- An intermediate effect, 'SystemProcess', is more concretely tied to the functionality of the "System.Process"
+-- library.
+-- See "Polysemy.Process.SystemProcess" for its constructors.
+--
+-- The utility effect 'ProcessOutput' takes care of decoding the process output, getting called by the 'Process'
+-- interpreters whenever a chunk was read, while accumulating chunks until they were decoded successfully.
+-- See "Polysemy.Process.ProcessOutput" for its constructors.
+--
+-- The effect 'Pty' abstracts pseudo terminals.
+-- See "Polysemy.Process.Pty" for its constructors.
diff --git a/lib/Polysemy/Process/Data/ProcessError.hs b/lib/Polysemy/Process/Data/ProcessError.hs
--- a/lib/Polysemy/Process/Data/ProcessError.hs
+++ b/lib/Polysemy/Process/Data/ProcessError.hs
@@ -6,4 +6,4 @@
 data ProcessError =
   -- |The process terminated.
   Terminated Text
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
diff --git a/lib/Polysemy/Process/Data/PtyError.hs b/lib/Polysemy/Process/Data/PtyError.hs
new file mode 100644
--- /dev/null
+++ b/lib/Polysemy/Process/Data/PtyError.hs
@@ -0,0 +1,9 @@
+{-# options_haddock prune #-}
+
+-- |PtyError ADT, Internal
+module Polysemy.Process.Data.PtyError where
+
+-- |Internal error used by an interpreter for 'Polysemy.Process.Pty'.
+data PtyError =
+  PtyError Text
+  deriving stock (Eq, Show)
diff --git a/lib/Polysemy/Process/Data/PtyResources.hs b/lib/Polysemy/Process/Data/PtyResources.hs
new file mode 100644
--- /dev/null
+++ b/lib/Polysemy/Process/Data/PtyResources.hs
@@ -0,0 +1,17 @@
+{-# options_haddock prune #-}
+
+-- |Description: PtyResources ADT, Internal
+module Polysemy.Process.Data.PtyResources where
+
+import System.IO (Handle)
+import System.Posix (Fd)
+import System.Posix.Pty (Pty)
+
+-- |The resources used by the default interpreter for 'Polysemy.Process.Pty'.
+data PtyResources =
+  PtyResources {
+    primary :: Fd,
+    secondary :: Fd,
+    handle :: Handle,
+    pty :: Pty
+  }
diff --git a/lib/Polysemy/Process/Data/SystemProcessError.hs b/lib/Polysemy/Process/Data/SystemProcessError.hs
new file mode 100644
--- /dev/null
+++ b/lib/Polysemy/Process/Data/SystemProcessError.hs
@@ -0,0 +1,12 @@
+{-# options_haddock prune #-}
+
+-- |Description: SystemProcessError, Internal
+module Polysemy.Process.Data.SystemProcessError where
+
+-- |Signal error for 'Polysemy.Process.SystemProcess'.
+data SystemProcessError =
+  -- |The process terminated.
+  Terminated Text
+  |
+  NoPipes
+  deriving stock (Eq, Show)
diff --git a/lib/Polysemy/Process/Effect/Process.hs b/lib/Polysemy/Process/Effect/Process.hs
--- a/lib/Polysemy/Process/Effect/Process.hs
+++ b/lib/Polysemy/Process/Effect/Process.hs
@@ -1,10 +1,11 @@
 {-# options_haddock prune #-}
+
 -- |Description: Process Effect, Internal
 module Polysemy.Process.Effect.Process where
 
-import Polysemy (makeSem_)
 import Polysemy.Conc.Effect.Scoped (Scoped, scoped)
-import Polysemy.Resume (type (!!))
+import Polysemy.Resume (interpretResumable, restop, type (!!))
+import Prelude hiding (send)
 
 -- |Abstraction of a process with stdin/stdout/stderr.
 --
@@ -18,8 +19,8 @@
 --
 -- prog :: Member (Scoped resource (Process Text Text e !! err)) r => Sem r Text
 -- prog =
---  withProcess do
---    resumeAs "failed" do
+--  resumeAs "failed" do
+--    withProcess do
 --      send "input"
 --      recv
 --
@@ -56,8 +57,22 @@
 
 -- |Create a scoped resource for 'Process'.
 withProcess ::
-  ∀ resource i o e err r .
-  Member (Scoped resource (Process i o e !! err)) r =>
-  InterpreterFor (Process i o e !! err) r
+  ∀ resource i o e r .
+  Member (Scoped resource (Process i o e)) r =>
+  InterpreterFor (Process i o e) r
 withProcess =
   scoped @resource
+
+-- |Convert 'Output' and 'Input' to 'Process'.
+runProcessIO ::
+  ∀ i o e err r .
+  Member (Process i o e !! err) r =>
+  InterpretersFor [Output i !! err, Input o !! err] r
+runProcessIO =
+  interpretResumable \case
+    Input ->
+      restop @err @(Process i o e) (recv @i @o @e)
+  .
+  interpretResumable \case
+    Output o ->
+      restop @err @(Process i o e) (send @i @o @e o)
diff --git a/lib/Polysemy/Process/Effect/ProcessOutput.hs b/lib/Polysemy/Process/Effect/ProcessOutput.hs
new file mode 100644
--- /dev/null
+++ b/lib/Polysemy/Process/Effect/ProcessOutput.hs
@@ -0,0 +1,20 @@
+{-# options_haddock prune #-}
+
+-- |Description: ProcessOutput effect, Internal.
+module Polysemy.Process.Effect.ProcessOutput where
+
+-- |This effect is used by the effect 'Polysemy.Process.Process' to accumulate and decode chunks of 'ByteString's, for
+-- example using a parser.
+-- The interpreter may be stateful or stateless, since the constructor 'Chunk' is expected to be called with both the
+-- accumulated unprocessed output as well as the new chunk.
+data ProcessOutput a :: Effect where
+  -- |Add a chunk of output to the accumulator, returning any number of successfully parsed values and the leftover
+  -- output.
+  Chunk ::
+    -- |The accumulation of the previous leftovers.
+    ByteString ->
+    -- |The new chunk read from the process.
+    ByteString ->
+    ProcessOutput a m ([a], ByteString)
+
+makeSem ''ProcessOutput
diff --git a/lib/Polysemy/Process/Effect/Pty.hs b/lib/Polysemy/Process/Effect/Pty.hs
new file mode 100644
--- /dev/null
+++ b/lib/Polysemy/Process/Effect/Pty.hs
@@ -0,0 +1,38 @@
+{-# options_haddock prune #-}
+
+-- |Description: Pty Effect, Internal
+module Polysemy.Process.Effect.Pty where
+
+import Polysemy.Conc.Effect.Scoped (Scoped, scoped)
+import System.IO (Handle)
+
+-- |Horizontal size of a pseudo terminal in characters.
+newtype Rows =
+  Rows { unRows :: Int }
+  deriving stock (Eq, Show, Generic)
+  deriving newtype (Num, Real, Enum, Integral, Ord)
+
+-- |Vertical size of a pseudo terminal in characters.
+newtype Cols =
+  Cols { unCols :: Int }
+  deriving stock (Eq, Show, Generic)
+  deriving newtype (Num, Real, Enum, Integral, Ord)
+
+-- |A pseudo terminal, to be scoped with 'withPty'.
+data Pty :: Effect where
+  -- |The file descriptor that can be connected to stdio of a process.
+  Handle :: Pty m Handle
+  -- |Set the size of the terminal.
+  Resize :: Rows -> Cols -> Pty m ()
+  -- |Get the size of the terminal.
+  Size :: Pty m (Rows, Cols)
+
+makeSem ''Pty
+
+-- |Bracket an action with the creation and destruction of a pseudo terminal.
+withPty ::
+  ∀ resource r .
+  Member (Scoped resource Pty) r =>
+  InterpreterFor Pty r
+withPty =
+  scoped @resource
diff --git a/lib/Polysemy/Process/Effect/SystemProcess.hs b/lib/Polysemy/Process/Effect/SystemProcess.hs
new file mode 100644
--- /dev/null
+++ b/lib/Polysemy/Process/Effect/SystemProcess.hs
@@ -0,0 +1,58 @@
+{-# options_haddock prune #-}
+
+-- |Description: SystemProcess Effect, Internal
+module Polysemy.Process.Effect.SystemProcess where
+
+import Polysemy.Conc.Effect.Scoped (Scoped, scoped)
+import Polysemy.Resume (type (!!))
+import System.Exit (ExitCode)
+import qualified System.Posix as Signal
+import System.Posix (Signal)
+import System.Process (Pid)
+
+-- |Low-level interface for a process, operating on raw chunks of bytes.
+-- Interface is modeled after "System.Process".
+data SystemProcess :: Effect where
+  -- |Read a chunk from stdout.
+  ReadStdout :: SystemProcess m ByteString
+  -- |Read a chunk from stderr.
+  ReadStderr :: SystemProcess m ByteString
+  -- |Write a 'ByteString' to stdin.
+  WriteStdin :: ByteString -> SystemProcess m ()
+  -- |Obtain the process ID.
+  Pid :: SystemProcess m Pid
+  -- |Send a 'System.Posix.Signal' to the process.
+  Signal :: Signal -> SystemProcess m ()
+  -- |Wait for the process to terminate, returning its exit code.
+  Wait :: SystemProcess m ExitCode
+
+makeSem ''SystemProcess
+
+-- |Create a scoped resource for 'SystemProcess'.
+withSystemProcess ::
+  ∀ resource err r .
+  Member (Scoped resource (SystemProcess !! err)) r =>
+  InterpreterFor (SystemProcess !! err) r
+withSystemProcess =
+  scoped @resource
+
+-- |Send signal INT(2) to the process.
+interrupt ::
+  Member SystemProcess r =>
+  Sem r ()
+interrupt =
+  signal Signal.sigINT
+
+-- |Send signal INT(15) to the process.
+term ::
+  Member SystemProcess r =>
+  Sem r ()
+term =
+  signal Signal.sigTERM
+
+-- |Send signal INT(9) to the process.
+kill ::
+  Member SystemProcess r =>
+  Sem r ()
+kill =
+  signal Signal.sigKILL
diff --git a/lib/Polysemy/Process/Executable.hs b/lib/Polysemy/Process/Executable.hs
new file mode 100644
--- /dev/null
+++ b/lib/Polysemy/Process/Executable.hs
@@ -0,0 +1,48 @@
+{-# options_haddock prune #-}
+
+-- |Description: Executable helpers, Internal
+module Polysemy.Process.Executable where
+
+import Path (Abs, File, Path, Rel, toFilePath)
+import qualified Path.IO as Path
+import Path.IO (executable, getPermissions)
+
+checkExecutable ::
+  Member (Embed IO) r =>
+  Text ->
+  Path Abs File ->
+  Sem r (Either Text (Path Abs File))
+checkExecutable name path =
+  tryAny (getPermissions path) <&> \case
+    Right (executable -> True) ->
+      Right path
+    Right _ ->
+      Left (message "executable")
+    Left _ ->
+      Left (message "a readable file")
+  where
+    message what =
+      "specified path for `" <> name <> "` is not " <> what <> ": " <> pathText
+    pathText =
+      toText (toFilePath path)
+
+-- |Find a file in @$PATH@, verifying that it is executable by this process.
+resolveExecutable ::
+  Member (Embed IO) r =>
+  -- |Executable name, for @$PATH@ lookup and error messages
+  Path Rel File ->
+  -- |Explicit override to be checked for adequate permissions
+  Maybe (Path Abs File) ->
+  Sem r (Either Text (Path Abs File))
+resolveExecutable exe = \case
+  Just path ->
+    checkExecutable name path
+  Nothing ->
+    tryAny (Path.findExecutable exe) <&> \case
+      Right (Just path) ->
+        Right path
+      _ ->
+        Left ("could not find executable `" <> name <> "` in `$PATH`.")
+  where
+    name =
+      toText (toFilePath exe)
diff --git a/lib/Polysemy/Process/Interpreter/Process.hs b/lib/Polysemy/Process/Interpreter/Process.hs
--- a/lib/Polysemy/Process/Interpreter/Process.hs
+++ b/lib/Polysemy/Process/Interpreter/Process.hs
@@ -1,41 +1,227 @@
 {-# options_haddock prune #-}
+
 -- |Description: Process Interpreters, Internal
 module Polysemy.Process.Interpreter.Process where
 
-import Polysemy.Async (Async)
-import Polysemy.Conc (Race)
+import Control.Concurrent.STM.TBMQueue (TBMQueue)
+import Polysemy.Conc.Async (withAsync_)
+import qualified Polysemy.Conc.Data.QueueResult as QueueResult
+import qualified Polysemy.Conc.Effect.Queue as Queue
+import Polysemy.Conc.Effect.Queue (Queue)
+import Polysemy.Conc.Effect.Race (Race)
 import Polysemy.Conc.Effect.Scoped (Scoped)
-import Polysemy.Conc.Interpreter.Scoped (runScoped)
-import Polysemy.Error (fromException, runError)
-import Polysemy.Resource (Resource, bracket)
-import Polysemy.Resume (type (!!))
+import Polysemy.Conc.Interpreter.Queue.TBM (interpretQueueTBMWith, withTBMQueue)
+import Polysemy.Conc.Interpreter.Scoped (interpretScopedResumableWith_)
+import Polysemy.Resume (Stop, resumeOr, stop, type (!!))
 import Prelude hiding (fromException)
-import qualified System.Process.Typed as System
-import System.Process.Typed (
-  ProcessConfig,
-  startProcess,
-  stopProcess,
-  )
 
+import Polysemy.Process.Data.ProcessError (ProcessError (Terminated))
+import qualified Polysemy.Process.Effect.Process as Process
 import Polysemy.Process.Effect.Process (Process)
+import qualified Polysemy.Process.Effect.ProcessOutput as ProcessOutput
+import Polysemy.Process.Effect.ProcessOutput (ProcessOutput)
+import qualified Polysemy.Process.Effect.SystemProcess as SystemProcess
+import Polysemy.Process.Effect.SystemProcess (SystemProcess, withSystemProcess)
+import Polysemy.Process.Interpreter.ProcessOutput (
+  interpretProcessOutputId,
+  interpretProcessOutputLines,
+  interpretProcessOutputText,
+  interpretProcessOutputTextLines,
+  )
 
-withProcess ::
+newtype In a =
+  In { unIn :: a }
+  deriving stock (Eq, Show)
+
+newtype Out a =
+  Out { unOut :: a }
+  deriving stock (Eq, Show)
+
+newtype Err a =
+  Err { unErr :: a }
+  deriving stock (Eq, Show)
+
+data ProcessQueues o e =
+  ProcessQueues {
+    pqIn :: TBMQueue (In ByteString),
+    pqOut :: TBMQueue (Out o),
+    pqErr :: TBMQueue (Err e)
+  }
+
+interpretQueues ::
+  Members [Resource, Race, Embed IO] r =>
+  ProcessQueues o e ->
+  InterpretersFor [Queue (In ByteString), Queue (Out o), Queue (Err e)] r
+interpretQueues (ProcessQueues inQ outQ errQ) =
+  interpretQueueTBMWith errQ .
+  interpretQueueTBMWith outQ .
+  interpretQueueTBMWith inQ
+
+handleProcessWithQueues ::
+  ∀ o e m r a .
+  Members [Queue (In ByteString), Queue (Out o), Queue (Err e), Stop ProcessError] r =>
+  Process ByteString o e m a ->
+  Sem r a
+handleProcessWithQueues = \case
+  Process.Recv ->
+    Queue.read >>= \case
+      QueueResult.Closed ->
+        stop (Terminated "closed")
+      QueueResult.NotAvailable ->
+        stop (Terminated "impossible: empty")
+      QueueResult.Success (Out msg) ->
+        pure msg
+  Process.RecvError ->
+    Queue.read >>= \case
+      QueueResult.Closed ->
+        stop (Terminated "closed")
+      QueueResult.NotAvailable ->
+        stop (Terminated "impossible: empty")
+      QueueResult.Success (Err msg) ->
+        pure msg
+  Process.Send msg -> do
+    whenM (Queue.closed @(In ByteString)) (stop (Terminated "closed"))
+    Queue.write (In msg)
+
+withSTMResources ::
+  ∀ o e r a .
   Members [Resource, Embed IO] r =>
-  ProcessConfig stdin stdout stderr ->
-  (System.Process stdin stdout stderr -> Sem r a) ->
+  Int ->
+  (ProcessQueues o e -> Sem r a) ->
   Sem r a
-withProcess config =
-  bracket (embed @IO (startProcess config)) (runError @SomeException . fromException @SomeException . stopProcess)
+withSTMResources qSize action = do
+  withTBMQueue qSize \ inQ ->
+    withTBMQueue qSize \ outQ ->
+      withTBMQueue qSize \ errQ ->
+        action (ProcessQueues inQ outQ errQ)
 
--- |Interpret 'Process' with a system process resource.
-interpretProcessNative ::
-  ∀ resource i o e err stdin stdout stderr r .
-  Members [Resource, Race, Async, Embed IO] r =>
-  ProcessConfig stdin stdout stderr ->
-  (∀ x. System.Process stdin stdout stderr -> (resource -> Sem r x) -> Sem r x) ->
-  (resource -> InterpreterFor (Process i o e !! err) r) ->
-  InterpreterFor (Scoped resource (Process i o e !! err)) r
-interpretProcessNative config fromProcess =
-  runScoped \ f ->
-    withProcess config \ prc ->
-      fromProcess prc f
+withQueues ::
+  Members [Race, Resource, Embed IO] r =>
+  Int ->
+  InterpretersFor [Queue (In ByteString), Queue (Out o), Queue (Err e)] r
+withQueues qSize action =
+  withSTMResources qSize \ qs -> interpretQueues qs action
+
+outputQueue ::
+  ∀ pipe chunk err r .
+  Coercible (pipe chunk) chunk =>
+  Members [SystemProcess !! err, ProcessOutput chunk, Queue (pipe chunk), Embed IO] r =>
+  Bool ->
+  Sem (SystemProcess : r) ByteString ->
+  Sem r ()
+outputQueue discardWhenFull readChunk = do
+  spin ""
+  where
+    spin buffer =
+      resumeOr @err readChunk (write buffer) (const (Queue.close @(pipe chunk)))
+    write buffer msg = do
+      (chunks, newBuffer) <- ProcessOutput.chunk buffer msg
+      for_ chunks \ (coerce @chunk @(pipe chunk) -> c) ->
+        if discardWhenFull then void (Queue.tryWrite c) else Queue.write c
+      spin newBuffer
+
+inputQueue ::
+  ∀ err r .
+  Members [SystemProcess !! err, Queue (In ByteString), Embed IO] r =>
+  (ByteString -> Sem (SystemProcess : r) ()) ->
+  Sem r ()
+inputQueue writeChunk =
+  spin
+  where
+    spin =
+      Queue.read >>= \case
+        QueueResult.Success (In msg) ->
+          resumeOr @err (writeChunk msg) (const spin) (const (Queue.close @(In ByteString)))
+        _ ->
+          unit
+
+type ScopeEffects o e err =
+  [Queue (In ByteString), Queue (Out o), Queue (Err e), SystemProcess !! err]
+
+scope ::
+  ∀ o e resource err r .
+  Member (Scoped resource (SystemProcess !! err)) r =>
+  Members [ProcessOutput e, ProcessOutput o, Resource, Race, Async, Embed IO] r =>
+  Bool ->
+  Int ->
+  InterpretersFor (ScopeEffects o e err) r
+scope discard qSize =
+  withSystemProcess @resource .
+  withQueues qSize .
+  withAsync_ (outputQueue @Err @e @err discard SystemProcess.readStderr) .
+  withAsync_ (outputQueue @Out @o @err discard SystemProcess.readStdout) .
+  withAsync_ (inputQueue @err SystemProcess.writeStdin)
+
+-- |Interpret 'Process' with a system process resource whose file descriptors are connected to three 'TBMQueue's,
+-- deferring decoding of stdout and stderr to the interpreters of two 'ProcessOutput' effects.
+interpretProcess ::
+  ∀ resource err o e r .
+  Member (Scoped resource (SystemProcess !! err)) r =>
+  Members [ProcessOutput o, ProcessOutput e, Resource, Race, Async, Embed IO] r =>
+  -- |Whether to discard output chunks if the queue is full or block.
+  Bool ->
+  -- |Maximum number of chunks allowed to be queued for each of the three standard pipes.
+  Int ->
+  InterpreterFor (Scoped () (Process ByteString o e) !! ProcessError) r
+interpretProcess discard qSize =
+  interpretScopedResumableWith_ @(ScopeEffects o e err) (scope @o @e @resource discard qSize) handleProcessWithQueues
+
+-- |Interpret 'Process' with a system process resource whose file descriptors are connected to three 'TBMQueue's,
+-- producing 'ByteString's.
+interpretProcessByteString ::
+  ∀ resource err r .
+  Members [Scoped resource (SystemProcess !! err), Resource, Race, Async, Embed IO] r =>
+  -- |Whether to discard output chunks if the queue is full.
+  Bool ->
+  -- |Maximum number of chunks allowed to be queued for each of the three standard pipes.
+  Int ->
+  InterpreterFor (Scoped () (Process ByteString ByteString ByteString) !! ProcessError) r
+interpretProcessByteString discard qSize =
+  interpretProcessOutputId .
+  interpretProcess @resource @err discard qSize .
+  raiseUnder
+
+-- |Interpret 'Process' with a system process resource whose file descriptors are connected to three 'TBMQueue's,
+-- producing chunks of lines of 'ByteString's.
+interpretProcessByteStringLines ::
+  ∀ resource err r .
+  Members [Scoped resource (SystemProcess !! err), Resource, Race, Async, Embed IO] r =>
+  -- |Whether to discard output chunks if the queue is full.
+  Bool ->
+  -- |Maximum number of chunks allowed to be queued for each of the three standard pipes.
+  Int ->
+  InterpreterFor (Scoped () (Process ByteString ByteString ByteString) !! ProcessError) r
+interpretProcessByteStringLines discard qSize =
+  interpretProcessOutputLines .
+  interpretProcess @resource @err discard qSize .
+  raiseUnder
+
+-- |Interpret 'Process' with a system process resource whose file descriptors are connected to three 'TBMQueue's,
+-- producing 'Text's.
+interpretProcessText ::
+  ∀ resource err r .
+  Members [Scoped resource (SystemProcess !! err), Resource, Race, Async, Embed IO] r =>
+  -- |Whether to discard output chunks if the queue is full.
+  Bool ->
+  -- |Maximum number of chunks allowed to be queued for each of the three standard pipes.
+  Int ->
+  InterpreterFor (Scoped () (Process ByteString Text Text) !! ProcessError) r
+interpretProcessText discard qSize =
+  interpretProcessOutputText .
+  interpretProcess @resource @err discard qSize .
+  raiseUnder
+
+-- |Interpret 'Process' with a system process resource whose file descriptors are connected to three 'TBMQueue's,
+-- producing chunks of lines of 'Text's.
+interpretProcessTextLines ::
+  ∀ resource err r .
+  Members [Scoped resource (SystemProcess !! err), Resource, Race, Async, Embed IO] r =>
+  -- |Whether to discard output chunks if the queue is full.
+  Bool ->
+  -- |Maximum number of chunks allowed to be queued for each of the three standard pipes.
+  Int ->
+  InterpreterFor (Scoped () (Process ByteString Text Text) !! ProcessError) r
+interpretProcessTextLines discard qSize =
+  interpretProcessOutputTextLines .
+  interpretProcess @resource @err discard qSize .
+  raiseUnder
diff --git a/lib/Polysemy/Process/Interpreter/ProcessIOE.hs b/lib/Polysemy/Process/Interpreter/ProcessIOE.hs
deleted file mode 100644
--- a/lib/Polysemy/Process/Interpreter/ProcessIOE.hs
+++ /dev/null
@@ -1,172 +0,0 @@
-{-# options_haddock prune #-}
-{-# language CPP #-}
-
--- |Description: Process Interpreters for stdpipes, Internal
-module Polysemy.Process.Interpreter.ProcessIOE where
-
-import Control.Concurrent.STM.TBMQueue (TBMQueue)
-import Data.ByteString (hGetSome, hPut)
-import Polysemy (InterpretersFor, insertAt)
-import Polysemy.Async (Async)
-import Polysemy.Conc.Async (withAsync_)
-import qualified Polysemy.Conc.Data.QueueResult as QueueResult
-import qualified Polysemy.Conc.Effect.Queue as Queue
-import Polysemy.Conc.Effect.Queue (Queue)
-import Polysemy.Conc.Effect.Race (Race)
-import Polysemy.Conc.Effect.Scoped (Scoped)
-import Polysemy.Conc.Interpreter.Queue.TBM (interpretQueueTBMWith, withTBMQueue)
-import Polysemy.Resource (Resource)
-import Polysemy.Resume (interpretResumable, stop, type (!!))
-import Prelude hiding (fromException)
-import qualified System.Process.Typed as System
-import System.Process.Typed (
-  ProcessConfig,
-  createPipe,
-  getStderr,
-  getStdin,
-  getStdout,
-  setStderr,
-  setStdin,
-  setStdout,
-  )
-
-import Polysemy.Process.Data.ProcessError (ProcessError (Terminated))
-import qualified Polysemy.Process.Effect.Process as Process
-import Polysemy.Process.Effect.Process (Process)
-import Polysemy.Process.Interpreter.Process (interpretProcessNative)
-
-#if !MIN_VERSION_relude(1,0,0)
-import System.IO (BufferMode (NoBuffering), hSetBuffering)
-#endif
-
-newtype In a =
-  In { unIn :: a }
-  deriving (Eq, Show)
-
-newtype Out a =
-  Out { unOut :: a }
-  deriving (Eq, Show)
-
-newtype Err a =
-  Err { unErr :: a }
-  deriving (Eq, Show)
-
-data ProcessQueues =
-  ProcessQueues {
-    pqIn :: TBMQueue (In ByteString),
-    pqOut :: TBMQueue (Out ByteString),
-    pqErr :: TBMQueue (Err ByteString)
-  }
-
-processWithQueues :: ProcessConfig () () () -> ProcessConfig Handle Handle Handle
-processWithQueues =
-  setStdin createPipe . setStdout createPipe . setStderr createPipe
-
-readQueue ::
-  ∀ r .
-  Members [Queue (In ByteString), Embed IO] r =>
-  Bool ->
-  Handle ->
-  Sem r ()
-readQueue discardWhenFull handle = do
-  void $ tryAny (hSetBuffering handle NoBuffering)
-  tryAny (hGetSome handle 4096) >>= traverse_ \ msg -> do
-    if discardWhenFull then void (Queue.tryWrite (In msg)) else Queue.write (In msg)
-    readQueue discardWhenFull handle
-
-writeQueue ::
-  ∀ r .
-  Members [Queue (Out ByteString), Embed IO] r =>
-  Handle ->
-  Sem r ()
-writeQueue handle = do
-  void $ tryAny (hSetBuffering handle NoBuffering)
-  spin
-  where
-    spin =
-      Queue.read >>= \case
-        QueueResult.Success (Out msg) ->
-          traverse_ (const spin) =<< tryAny (hPut handle msg)
-        _ ->
-          pass
-
-interpretQueues ::
-  Members [Resource, Race, Embed IO] r =>
-  ProcessQueues ->
-  InterpretersFor [Queue (In ByteString), Queue (Out ByteString), Queue (Err ByteString)] r
-interpretQueues (ProcessQueues inQ outQ errQ) =
-  interpretQueueTBMWith errQ .
-  interpretQueueTBMWith outQ .
-  interpretQueueTBMWith inQ
-
-interpretProcessWithQueues ::
-  Members [Queue (In ByteString), Queue (Out ByteString), Queue (Err ByteString)] r =>
-  InterpreterFor (Process ByteString ByteString ByteString !! ProcessError) r
-interpretProcessWithQueues =
-  interpretResumable \case
-    Process.Recv ->
-      Queue.read >>= \case
-        QueueResult.Closed ->
-          stop (Terminated "closed")
-        QueueResult.NotAvailable ->
-          stop (Terminated "impossible: empty")
-        QueueResult.Success (In msg) ->
-          pure msg
-    Process.RecvError ->
-      Queue.read >>= \case
-        QueueResult.Closed ->
-          stop (Terminated "closed")
-        QueueResult.NotAvailable ->
-          stop (Terminated "impossible: empty")
-        QueueResult.Success (Err msg) ->
-          pure msg
-    Process.Send msg -> do
-      whenM (Queue.closed @(Out ByteString)) (stop (Terminated "closed"))
-      Queue.write (Out msg)
-
-withSTMResources ::
-  ∀ r a .
-  Members [Resource, Embed IO] r =>
-  Int ->
-  (ProcessQueues -> Sem r a) ->
-  Sem r a
-withSTMResources qSize action = do
-  withTBMQueue qSize \ inQ ->
-    withTBMQueue qSize \ outQ ->
-      withTBMQueue qSize \ errQ ->
-        action (ProcessQueues inQ outQ errQ)
-
-withProcessResources ::
-  Members [Resource, Race, Async, Embed IO] r =>
-  Bool ->
-  Int ->
-  System.Process Handle Handle Handle ->
-  (ProcessQueues -> Sem r a) ->
-  Sem r a
-withProcessResources discardWhenFull qSize prc f =
-  withSTMResources qSize \ qs ->
-    interpretQueues qs $
-    withAsync_ (readQueue discardWhenFull (getStderr prc)) $
-    withAsync_ (readQueue discardWhenFull (getStdout prc)) $
-    withAsync_ (writeQueue (getStdin prc)) $
-    insertAt @0 (f qs)
-
-interpretProcessQueues ::
-  Members [Resource, Race, Async, Embed IO] r =>
-  ProcessQueues ->
-  InterpreterFor (Process ByteString ByteString ByteString !! ProcessError) r
-interpretProcessQueues qs =
-  interpretQueues qs .
-  interpretProcessWithQueues .
-  raiseUnder3
-
--- |Interpret 'Process' with a system process resource whose file descriptors are connected to three 'TBMQueue's,
--- producing 'ByteString's.
-interpretProcessIOE ::
-  Members [Resource, Race, Async, Embed IO] r =>
-  Bool ->
-  Int ->
-  ProcessConfig () () () ->
-  InterpreterFor (Scoped ProcessQueues (Process ByteString ByteString ByteString !! ProcessError)) r
-interpretProcessIOE discardWhenFull qSize config =
-  interpretProcessNative (processWithQueues config) (withProcessResources discardWhenFull qSize) interpretProcessQueues
diff --git a/lib/Polysemy/Process/Interpreter/ProcessOutput.hs b/lib/Polysemy/Process/Interpreter/ProcessOutput.hs
new file mode 100644
--- /dev/null
+++ b/lib/Polysemy/Process/Interpreter/ProcessOutput.hs
@@ -0,0 +1,51 @@
+{-# options_haddock prune #-}
+
+-- |Description: ProcessOutput Interpreters, Internal
+module Polysemy.Process.Interpreter.ProcessOutput where
+
+import qualified Data.ByteString as ByteString
+
+import Polysemy.Process.Effect.ProcessOutput (ProcessOutput (Chunk))
+
+-- |Interpret 'ProcessOutput' by immediately emitting raw 'ByteString's without accumulation.
+interpretProcessOutputId :: InterpreterFor (ProcessOutput ByteString) r
+interpretProcessOutputId =
+  interpret \case
+    Chunk buffer new ->
+      pure ([buffer <> new], "")
+{-# inline interpretProcessOutputId #-}
+
+splitLines :: ByteString -> ByteString -> ([ByteString], ByteString)
+splitLines buffer new =
+  second fold (foldr' folder ([], Nothing) parts)
+  where
+    parts =
+      ByteString.split 10 (buffer <> new)
+    folder a (z, Nothing) =
+      (z, Just a)
+    folder a (z, Just r) =
+      (a : z, Just r)
+
+-- |Interpret 'ProcessOutput' by emitting individual 'ByteString' lines of output.
+interpretProcessOutputLines :: InterpreterFor (ProcessOutput ByteString) r
+interpretProcessOutputLines =
+  interpret \case
+    Chunk buffer new ->
+      pure (splitLines buffer new)
+{-# inline interpretProcessOutputLines #-}
+
+-- |Interpret 'ProcessOutput' by immediately emitting 'Text' without accumulation.
+interpretProcessOutputText :: InterpreterFor (ProcessOutput Text) r
+interpretProcessOutputText =
+  interpret \case
+    Chunk buffer new ->
+      pure ([decodeUtf8 (buffer <> new)], "")
+{-# inline interpretProcessOutputText #-}
+
+-- |Interpret 'ProcessOutput' by emitting individual 'Text' lines of output.
+interpretProcessOutputTextLines :: InterpreterFor (ProcessOutput Text) r
+interpretProcessOutputTextLines =
+  interpret \case
+    Chunk buffer new ->
+      pure (first (fmap decodeUtf8) (splitLines buffer new))
+{-# inline interpretProcessOutputTextLines #-}
diff --git a/lib/Polysemy/Process/Interpreter/ProcessStdio.hs b/lib/Polysemy/Process/Interpreter/ProcessStdio.hs
new file mode 100644
--- /dev/null
+++ b/lib/Polysemy/Process/Interpreter/ProcessStdio.hs
@@ -0,0 +1,80 @@
+{-# options_haddock prune #-}
+
+-- |Description: Process Interpreters for stdio, Internal
+module Polysemy.Process.Interpreter.ProcessStdio where
+
+import Polysemy.Conc.Effect.Race (Race)
+import Polysemy.Conc.Effect.Scoped (Scoped)
+import Polysemy.Resume (type (!!))
+import System.Process.Typed (ProcessConfig)
+
+import Polysemy.Process.Data.ProcessError (ProcessError)
+import Polysemy.Process.Data.SystemProcessError (SystemProcessError)
+import Polysemy.Process.Effect.Process (Process)
+import Polysemy.Process.Interpreter.Process (
+  interpretProcessByteString,
+  interpretProcessByteStringLines,
+  interpretProcessText,
+  interpretProcessTextLines,
+  )
+import Polysemy.Process.Interpreter.SystemProcess (PipesProcess, interpretSystemProcessNative)
+
+-- |Interpret 'Process' as a native 'Polysemy.Process.SystemProcess', producing unaccumulated chunks of 'ByteString'.
+interpretProcessByteStringNative ::
+  Members [Resource, Race, Async, Embed IO] r =>
+  -- |Whether to discard output chunks if the queue is full.
+  Bool ->
+  -- |Maximum number of chunks allowed to be queued for each of the three standard pipes.
+  Int ->
+  -- |Basic config. The pipes will be changed to 'System.IO.Handle' by the interpreter.
+  ProcessConfig () () () ->
+  InterpreterFor (Scoped () (Process ByteString ByteString ByteString) !! ProcessError) r
+interpretProcessByteStringNative discard qSize conf =
+  interpretSystemProcessNative conf .
+  interpretProcessByteString @PipesProcess @SystemProcessError discard qSize .
+  raiseUnder
+
+-- |Interpret 'Process' as a native 'Polysemy.Process.SystemProcess', producing lines of 'ByteString'.
+interpretProcessByteStringLinesNative ::
+  Members [Resource, Race, Async, Embed IO] r =>
+  -- |Whether to discard output chunks if the queue is full.
+  Bool ->
+  -- |Maximum number of chunks allowed to be queued for each of the three standard pipes.
+  Int ->
+  -- |Basic config. The pipes will be changed to 'System.IO.Handle' by the interpreter.
+  ProcessConfig () () () ->
+  InterpreterFor (Scoped () (Process ByteString ByteString ByteString) !! ProcessError) r
+interpretProcessByteStringLinesNative discard qSize conf =
+  interpretSystemProcessNative conf .
+  interpretProcessByteStringLines @PipesProcess @SystemProcessError discard qSize .
+  raiseUnder
+
+-- |Interpret 'Process' as a native 'Polysemy.Process.SystemProcess', producing unaccumulated chunks of 'Text'.
+interpretProcessTextNative ::
+  Members [Resource, Race, Async, Embed IO] r =>
+  -- |Whether to discard output chunks if the queue is full.
+  Bool ->
+  -- |Maximum number of chunks allowed to be queued for each of the three standard pipes.
+  Int ->
+  -- |Basic config. The pipes will be changed to 'System.IO.Handle' by the interpreter.
+  ProcessConfig () () () ->
+  InterpreterFor (Scoped () (Process ByteString Text Text) !! ProcessError) r
+interpretProcessTextNative discard qSize conf =
+  interpretSystemProcessNative conf .
+  interpretProcessText @PipesProcess @SystemProcessError discard qSize .
+  raiseUnder
+
+-- |Interpret 'Process' as a native 'Polysemy.Process.SystemProcess', producing lines of 'Text'.
+interpretProcessTextLinesNative ::
+  Members [Resource, Race, Async, Embed IO] r =>
+  -- |Whether to discard output chunks if the queue is full.
+  Bool ->
+  -- |Maximum number of chunks allowed to be queued for each of the three standard pipes.
+  Int ->
+  -- |Basic config. The pipes will be changed to 'System.IO.Handle' by the interpreter.
+  ProcessConfig () () () ->
+  InterpreterFor (Scoped () (Process ByteString Text Text) !! ProcessError) r
+interpretProcessTextLinesNative discard qSize conf =
+  interpretSystemProcessNative conf .
+  interpretProcessTextLines @PipesProcess @SystemProcessError discard qSize .
+  raiseUnder
diff --git a/lib/Polysemy/Process/Interpreter/Pty.hs b/lib/Polysemy/Process/Interpreter/Pty.hs
new file mode 100644
--- /dev/null
+++ b/lib/Polysemy/Process/Interpreter/Pty.hs
@@ -0,0 +1,58 @@
+{-# options_haddock prune #-}
+
+-- |Description: Pty Interpreters, Internal
+module Polysemy.Process.Interpreter.Pty where
+
+import Polysemy.Conc.Effect.Scoped (Scoped)
+import Polysemy.Conc.Interpreter.Scoped (interpretScopedResumable)
+import Polysemy.Resume (Stop, stopEitherWith, stopNote, type (!!))
+import System.Posix (closeFd, fdToHandle, openPseudoTerminal)
+import System.Posix.Pty (closePty, createPty, ptyDimensions, resizePty)
+
+import Polysemy.Process.Data.PtyError (PtyError (PtyError))
+import Polysemy.Process.Data.PtyResources (PtyResources (PtyResources, handle, primary, pty, secondary))
+import Polysemy.Process.Effect.Pty (Cols (Cols), Pty (Handle, Resize, Size), Rows (Rows))
+
+tryStop ::
+  Members [Stop PtyError, Embed IO] r =>
+  IO a ->
+  Sem r a
+tryStop =
+  stopEitherWith PtyError <=< tryAny
+
+acquirePty ::
+  Member (Embed IO) r =>
+  Sem (Stop PtyError : r) PtyResources
+acquirePty = do
+  (primary, secondary) <- tryStop openPseudoTerminal
+  pty <- stopNote (PtyError "no pty returned") =<< tryStop (createPty secondary)
+  handle <- tryStop (fdToHandle secondary)
+  pure PtyResources {..}
+
+releasePty ::
+  Member (Embed IO) r =>
+  PtyResources ->
+  Sem r ()
+releasePty PtyResources {primary, pty} = do
+  ignoreException (closePty pty)
+  ignoreException (closeFd primary)
+
+withPty ::
+  Members [Resource, Embed IO] r =>
+  (PtyResources -> Sem (Stop PtyError : r) a) ->
+  Sem (Stop PtyError : r) a
+withPty =
+  bracket acquirePty releasePty
+
+-- |Interpret Pty as a 'System.Posix.Pty'.
+interpretPty ::
+  Members [Resource, Embed IO] r =>
+  InterpreterFor (Scoped PtyResources Pty !! PtyError) r
+interpretPty =
+  interpretScopedResumable withPty \ PtyResources {..} -> \case
+    Handle ->
+      pure handle
+    Resize rows cols -> do
+      tryStop (resizePty pty (fromIntegral rows, fromIntegral cols))
+    Size ->
+      bimap Rows Cols <$> tryStop (ptyDimensions pty)
diff --git a/lib/Polysemy/Process/Interpreter/SystemProcess.hs b/lib/Polysemy/Process/Interpreter/SystemProcess.hs
new file mode 100644
--- /dev/null
+++ b/lib/Polysemy/Process/Interpreter/SystemProcess.hs
@@ -0,0 +1,186 @@
+{-# options_haddock prune #-}
+
+-- |Description: SystemProcess Interpreters, Internal
+module Polysemy.Process.Interpreter.SystemProcess where
+
+import Data.ByteString (hGetSome, hPut)
+import Polysemy.Conc.Effect.Scoped (Scoped)
+import Polysemy.Conc.Interpreter.Scoped (runScoped)
+import Polysemy.Resume (Stop, interpretResumable, stop, stopNote, type (!!))
+import Prelude hiding (fromException)
+import System.IO (BufferMode (NoBuffering), Handle, hSetBuffering)
+import qualified System.Posix as Signal
+import System.Process (Pid, getPid)
+import System.Process.Typed (
+  Process,
+  ProcessConfig,
+  createPipe,
+  getStderr,
+  getStdin,
+  getStdout,
+  setStderr,
+  setStdin,
+  setStdout,
+  startProcess,
+  stopProcess,
+  unsafeProcessHandle,
+  waitExitCode,
+  )
+
+import qualified Polysemy.Process.Data.SystemProcessError as SystemProcessError
+import Polysemy.Process.Data.SystemProcessError (SystemProcessError)
+import qualified Polysemy.Process.Effect.SystemProcess as SystemProcess
+import Polysemy.Process.Effect.SystemProcess (SystemProcess)
+
+type PipesProcess =
+  Process Handle Handle Handle
+
+processWithPipes :: ProcessConfig () () () -> ProcessConfig Handle Handle Handle
+processWithPipes =
+  setStdin createPipe .
+  setStdout createPipe .
+  setStderr createPipe
+
+start ::
+  Member (Embed IO) r =>
+  ProcessConfig () () () ->
+  Sem r PipesProcess
+start =
+  startProcess . processWithPipes
+
+withProcess ::
+  Members [Resource, Embed IO] r =>
+  ProcessConfig () () () ->
+  (PipesProcess -> Sem r a) ->
+  Sem r a
+withProcess config use =
+  bracket (start config) (tryAny . stopProcess) \ p -> do
+    unbuffer (getStdin p)
+    unbuffer (getStdout p)
+    unbuffer (getStderr p)
+    use p
+  where
+    unbuffer h =
+      void $ tryMaybe (hSetBuffering h NoBuffering)
+
+startOpaque ::
+  Member (Embed IO) r =>
+  ProcessConfig i o e ->
+  Sem r (Process i o e)
+startOpaque =
+  startProcess
+
+withProcessOpaque ::
+  Members [Resource, Embed IO] r =>
+  ProcessConfig i o e ->
+  (Process i o e -> Sem r a) ->
+  Sem r a
+withProcessOpaque config =
+  bracket (startOpaque config) (tryAny . stopProcess)
+
+terminate ::
+  Member (Stop SystemProcessError) r =>
+  Text ->
+  Maybe a ->
+  Sem r a
+terminate msg =
+  stopNote (SystemProcessError.Terminated msg)
+
+tryStop ::
+  Members [Stop SystemProcessError, Embed IO] r =>
+  Text ->
+  IO a ->
+  Sem r a
+tryStop msg =
+  terminate msg <=< tryMaybe
+
+processId ::
+  Members [Stop SystemProcessError, Embed IO] r =>
+  Process i o e ->
+  Sem r Pid
+processId process =
+  terminate "getPid returned Nothing" =<< embed (getPid (unsafeProcessHandle process))
+
+-- |Interpret 'SystemProcess' with a concrete 'System.Process' with connected pipes.
+interpretSystemProcessWithProcess ::
+  ∀ r .
+  Member (Embed IO) r =>
+  Process Handle Handle Handle ->
+  InterpreterFor (SystemProcess !! SystemProcessError) r
+interpretSystemProcessWithProcess process =
+  interpretResumable \case
+    SystemProcess.Pid ->
+      processId process
+    SystemProcess.Signal sig -> do
+      pid <- processId process
+      tryStop "signal failed" (Signal.signalProcess sig pid)
+    SystemProcess.ReadStdout ->
+      tryStop "stdout failed" (hGetSome (getStdout process) 4096)
+    SystemProcess.ReadStderr ->
+      tryStop "stderr failed" (hGetSome (getStderr process) 4096)
+    SystemProcess.WriteStdin msg ->
+      tryStop "stdin failed" (hPut (getStdin process) msg)
+    SystemProcess.Wait ->
+      tryStop "wait failed" (waitExitCode process)
+
+-- |Interpret 'SystemProcess' as a single global 'System.Process' that's started immediately.
+interpretSystemProcessNativeSingle ::
+  ∀ r .
+  Members [Resource, Embed IO] r =>
+  ProcessConfig () () () ->
+  InterpreterFor (SystemProcess !! SystemProcessError) r
+interpretSystemProcessNativeSingle config sem =
+  withProcess config \ process ->
+    interpretSystemProcessWithProcess process sem
+
+-- |Interpret 'SystemProcess' as a scoped 'System.Process' that's started wherever 'Polysemy.Process.withSystemProcess'
+-- is called and terminated when the wrapped action finishes.
+interpretSystemProcessNative ::
+  ∀ r .
+  Members [Resource, Embed IO] r =>
+  ProcessConfig () () () ->
+  InterpreterFor (Scoped PipesProcess (SystemProcess !! SystemProcessError)) r
+interpretSystemProcessNative config =
+  runScoped (withProcess config) interpretSystemProcessWithProcess
+
+-- |Interpret 'SystemProcess' with a concrete 'System.Process' with connected pipes.
+interpretSystemProcessWithProcessOpaque ::
+  ∀ i o e r .
+  Member (Embed IO) r =>
+  Process i o e ->
+  InterpreterFor (SystemProcess !! SystemProcessError) r
+interpretSystemProcessWithProcessOpaque process =
+  interpretResumable \case
+    SystemProcess.Pid ->
+      processId process
+    SystemProcess.Signal sig -> do
+      pid <- processId process
+      tryStop "signal failed" (Signal.signalProcess sig pid)
+    SystemProcess.ReadStdout ->
+      stop SystemProcessError.NoPipes
+    SystemProcess.ReadStderr ->
+      stop SystemProcessError.NoPipes
+    SystemProcess.WriteStdin _ ->
+      stop SystemProcessError.NoPipes
+    SystemProcess.Wait ->
+      tryStop "wait failed" (waitExitCode process)
+
+-- |Interpret 'SystemProcess' as a single global 'System.Process' that's started immediately.
+interpretSystemProcessNativeOpaqueSingle ::
+  ∀ i o e r .
+  Members [Resource, Embed IO] r =>
+  ProcessConfig i o e ->
+  InterpreterFor (SystemProcess !! SystemProcessError) r
+interpretSystemProcessNativeOpaqueSingle config sem =
+  withProcessOpaque config \ process ->
+    interpretSystemProcessWithProcessOpaque process sem
+
+-- |Interpret 'SystemProcess' as a scoped 'System.Process' that's started wherever 'Polysemy.Process.withSystemProcess'
+-- is called and terminated when the wrapped action finishes.
+interpretSystemProcessNativeOpaque ::
+  ∀ i o e r .
+  Members [Resource, Embed IO] r =>
+  ProcessConfig i o e ->
+  InterpreterFor (Scoped (Process i o e) (SystemProcess !! SystemProcessError)) r
+interpretSystemProcessNativeOpaque config =
+  runScoped (withProcessOpaque config) interpretSystemProcessWithProcessOpaque
diff --git a/lib/Polysemy/Process/ProcessOutput.hs b/lib/Polysemy/Process/ProcessOutput.hs
new file mode 100644
--- /dev/null
+++ b/lib/Polysemy/Process/ProcessOutput.hs
@@ -0,0 +1,17 @@
+{-# options_haddock prune #-}
+
+-- |The utility effect 'ProcessOutput' takes care of decoding process output, getting called by the
+-- 'Polysemy.Process.Process' interpreters whenever a chunk was read, while accumulating chunks until they were decoded
+-- successfully.
+module Polysemy.Process.ProcessOutput (
+  module Polysemy.Process.Effect.ProcessOutput,
+  module Polysemy.Process.Interpreter.ProcessOutput,
+) where
+
+import Polysemy.Process.Effect.ProcessOutput (ProcessOutput (Chunk), chunk)
+import Polysemy.Process.Interpreter.ProcessOutput (
+  interpretProcessOutputId,
+  interpretProcessOutputLines,
+  interpretProcessOutputText,
+  interpretProcessOutputTextLines,
+  )
diff --git a/lib/Polysemy/Process/Pty.hs b/lib/Polysemy/Process/Pty.hs
new file mode 100644
--- /dev/null
+++ b/lib/Polysemy/Process/Pty.hs
@@ -0,0 +1,10 @@
+{-# options_haddock prune #-}
+
+-- |The effect 'Pty' abstracts pseudo terminals.
+module Polysemy.Process.Pty (
+  module Polysemy.Process.Effect.Pty,
+  module Polysemy.Process.Interpreter.Pty,
+) where
+
+import Polysemy.Process.Effect.Pty (Pty (..), handle, resize, size, withPty)
+import Polysemy.Process.Interpreter.Pty (interpretPty)
diff --git a/lib/Polysemy/Process/SystemProcess.hs b/lib/Polysemy/Process/SystemProcess.hs
new file mode 100644
--- /dev/null
+++ b/lib/Polysemy/Process/SystemProcess.hs
@@ -0,0 +1,24 @@
+{-# options_haddock prune #-}
+
+-- |The effect 'SystemProcess' is a low-level abstraction of a native system process.
+module Polysemy.Process.SystemProcess (
+  module Polysemy.Process.Effect.SystemProcess,
+  module Polysemy.Process.Interpreter.SystemProcess,
+) where
+
+import Polysemy.Process.Effect.SystemProcess (
+  SystemProcess (..),
+  interrupt,
+  pid,
+  readStderr,
+  readStdout,
+  signal,
+  wait,
+  withSystemProcess,
+  writeStdin,
+  )
+import Polysemy.Process.Interpreter.SystemProcess (
+  interpretSystemProcessNative,
+  interpretSystemProcessNativeSingle,
+  interpretSystemProcessWithProcess,
+  )
diff --git a/polysemy-process.cabal b/polysemy-process.cabal
--- a/polysemy-process.cabal
+++ b/polysemy-process.cabal
@@ -1,11 +1,11 @@
 cabal-version: 2.2
 
--- This file has been generated from package.yaml by hpack version 0.34.4.
+-- This file has been generated from package.yaml by hpack version 0.34.6.
 --
 -- see: https://github.com/sol/hpack
 
 name:           polysemy-process
-version:        0.5.1.1
+version:        0.6.0.0
 synopsis:       Polysemy Effects for System Processes
 description:    See <https://hackage.haskell.org/package/polysemy-process/docs/Polysemy-Process.html>
 category:       Concurrency
@@ -29,13 +29,22 @@
   exposed-modules:
       Polysemy.Process
       Polysemy.Process.Data.ProcessError
+      Polysemy.Process.Data.PtyError
+      Polysemy.Process.Data.PtyResources
+      Polysemy.Process.Data.SystemProcessError
       Polysemy.Process.Effect.Process
+      Polysemy.Process.Effect.ProcessOutput
+      Polysemy.Process.Effect.Pty
+      Polysemy.Process.Effect.SystemProcess
+      Polysemy.Process.Executable
       Polysemy.Process.Interpreter.Process
-      Polysemy.Process.Interpreter.ProcessIOE
-  other-modules:
-      Paths_polysemy_process
-  autogen-modules:
-      Paths_polysemy_process
+      Polysemy.Process.Interpreter.ProcessOutput
+      Polysemy.Process.Interpreter.ProcessStdio
+      Polysemy.Process.Interpreter.Pty
+      Polysemy.Process.Interpreter.SystemProcess
+      Polysemy.Process.ProcessOutput
+      Polysemy.Process.Pty
+      Polysemy.Process.SystemProcess
   hs-source-dirs:
       lib
   default-extensions:
@@ -96,27 +105,22 @@
       UndecidableInstances
       UnicodeSyntax
       ViewPatterns
-  ghc-options: -flate-specialise -fspecialise-aggressively -Wall -Wredundant-constraints
+  ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Widentities
   build-depends:
-      async
-    , base ==4.*
-    , bytestring
-    , containers
+      base ==4.*
+    , incipit-core >=0.1.0.1
+    , path >=0.7
+    , path-io >=1.6.2
     , polysemy >=1.6
     , polysemy-conc
-    , polysemy-resume >=0.2
-    , polysemy-time >=0.1.4
-    , relude >=0.7
-    , stm
+    , polysemy-resume >=0.3
+    , posix-pty >=0.2
+    , process
     , stm-chans >=2
-    , template-haskell
-    , text
-    , time
-    , typed-process
+    , typed-process >=0.2.6
+    , unix
   mixins:
       base hiding (Prelude)
-    , polysemy-conc hiding (Prelude)
-    , polysemy-conc (Polysemy.Conc.Prelude as Prelude)
   if impl(ghc >= 8.10)
     ghc-options: -Wunused-packages
   default-language: Haskell2010
@@ -126,7 +130,6 @@
   main-is: Main.hs
   other-modules:
       Polysemy.Process.Test.ProcessTest
-      Paths_polysemy_process
   hs-source-dirs:
       test
   default-extensions:
@@ -187,10 +190,10 @@
       UndecidableInstances
       UnicodeSyntax
       ViewPatterns
-  ghc-options: -flate-specialise -fspecialise-aggressively -Wall -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
+  ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Widentities -threaded -rtsopts -with-rtsopts=-N
   build-depends:
       base ==4.*
-    , bytestring
+    , incipit-core
     , polysemy
     , polysemy-conc
     , polysemy-plugin
@@ -201,8 +204,6 @@
     , typed-process
   mixins:
       base hiding (Prelude)
-    , polysemy-conc hiding (Prelude)
-    , polysemy-conc (Polysemy.Conc.Prelude as Prelude)
   if impl(ghc >= 8.10)
     ghc-options: -Wunused-packages
   default-language: Haskell2010
diff --git a/readme.md b/readme.md
--- a/readme.md
+++ b/readme.md
@@ -1,8 +1,8 @@
 # About
 
-This library provides a few convenience [polysemy] effects for using STM queues, MVars, signal handling and racing.
+This library provides [Polysemy] effects for system processes and pseudo terminals.
 
-Please visit [hackage] for documentation.
+Please visit [Hackage] for documentation.
 
-[polysemy]: https://hackage.haskell.org/package/polysemy
-[hackage]: https://hackage.haskell.org/package/polysemy-conc
+[Polysemy]: https://hackage.haskell.org/package/polysemy
+[Hackage]: https://hackage.haskell.org/package/polysemy-process/docs/Polysemy-Process.html
diff --git a/test/Polysemy/Process/Test/ProcessTest.hs b/test/Polysemy/Process/Test/ProcessTest.hs
--- a/test/Polysemy/Process/Test/ProcessTest.hs
+++ b/test/Polysemy/Process/Test/ProcessTest.hs
@@ -2,31 +2,43 @@
 
 module Polysemy.Process.Test.ProcessTest where
 
-import qualified Data.ByteString as ByteString
-import Polysemy.Async (asyncToIOFinal)
 import Polysemy.Conc.Interpreter.Race (interpretRace)
-import Polysemy.Resume (resuming)
+import Polysemy.Resume (resumeHoistError)
 import Polysemy.Test (UnitTest, runTestAuto, (===))
 import qualified System.Process.Typed as Process
 import System.Process.Typed (ProcessConfig)
 
+import Polysemy.Process.Data.ProcessError (ProcessError)
 import qualified Polysemy.Process.Effect.Process as Process
 import Polysemy.Process.Effect.Process (withProcess)
-import Polysemy.Process.Interpreter.ProcessIOE (interpretProcessIOE)
+import Polysemy.Process.Interpreter.ProcessStdio (interpretProcessByteStringNative, interpretProcessTextLinesNative)
 
 config :: ProcessConfig () () ()
 config =
   Process.proc "cat" []
 
+messageLines :: [Text]
+messageLines =
+  replicate 4 "line"
+
 message :: ByteString
 message =
-  ByteString.replicate 10 120
+  encodeUtf8 (unlines messageLines)
 
 test_process :: UnitTest
 test_process =
-  runTestAuto $ interpretRace $ asyncToIOFinal $ interpretProcessIOE True 10 config do
-    withProcess do
-      response <- resuming (pure . show) do
+  runTestAuto $ interpretRace $ asyncToIOFinal $ interpretProcessByteStringNative True 10 config do
+    response <- resumeHoistError @ProcessError show do
+      withProcess do
         Process.send message
         Process.recv
-      message === response
+    message === response
+
+test_processLines :: UnitTest
+test_processLines =
+  runTestAuto $ interpretRace $ asyncToIOFinal $ interpretProcessTextLinesNative True 10 config do
+    response <- resumeHoistError @ProcessError show do
+      withProcess do
+        Process.send message
+        replicateM 4 Process.recv
+    messageLines === response
