diff --git a/core-program.cabal b/core-program.cabal
--- a/core-program.cabal
+++ b/core-program.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           core-program
-version:        0.3.0.8
+version:        0.3.1.0
 synopsis:       Opinionated Haskell Interoperability
 description:    A library to help build command-line programs, both tools and
                 longer-running daemons.
@@ -74,5 +74,6 @@
     , text
     , text-short
     , transformers
+    , typed-process
     , unix
   default-language: Haskell2010
diff --git a/lib/Core/Program/Context.hs b/lib/Core/Program/Context.hs
--- a/lib/Core/Program/Context.hs
+++ b/lib/Core/Program/Context.hs
@@ -109,10 +109,11 @@
 
 {- |
 Unique identifier for a trace. If your program is the top of an service stack
-then you can use 'beginTrace' to generate a new idenfifier for this request or
-iteration. More commonly, however, you will inherit the trace identifier from
-the application or service which invokes this program or request handler, and
-you can specify it by using 'usingTrace'.
+then you can use 'Core.Telemetry.Observability.beginTrace' to generate a new
+idenfifier for this request or iteration. More commonly, however, you will
+inherit the trace identifier from the application or service which invokes
+this program or request handler, and you can specify it by using
+'Core.Telemetry.Observability.usingTrace'.
 -}
 newtype Trace = Trace Rope
     deriving (Show, IsString)
diff --git a/lib/Core/Program/Execute.hs b/lib/Core/Program/Execute.hs
--- a/lib/Core/Program/Execute.hs
+++ b/lib/Core/Program/Execute.hs
@@ -76,6 +76,7 @@
     -- * Useful actions
     outputEntire,
     inputEntire,
+    execProcess,
 
     -- * Concurrency
     Thread,
@@ -148,6 +149,7 @@
 import GHC.IO.Encoding (setLocaleEncoding, utf8)
 import System.Exit (ExitCode (..))
 import qualified System.Posix.Process as Posix (exitImmediately)
+import System.Process.Typed (closed, proc, readProcess, setStdin)
 import Prelude hiding (log)
 
 --
@@ -544,10 +546,49 @@
 outputEntire handle contents = liftIO (hOutput handle contents)
 
 {- |
- Read the (entire) contents of the specified @Handle@.
+Read the (entire) contents of the specified @Handle@.
 -}
 inputEntire :: Handle -> Program τ Bytes
 inputEntire handle = liftIO (hInput handle)
+
+{- |
+Execute an external child process and wait for its output and result. The
+command is specified first and and subsequent arguments as elements of the
+list. This helper then logs the command being executed to the debug output,
+which can be useful when you're trying to find out what exactly what program
+is being invoked.
+
+Keep in mind that this isn't invoking a shell; arguments and their values have
+to be enumerated separately:
+
+@
+    'execProcess' [\"\/usr\/bin\/ssh\", \"-l\", \"admin\", \"203.0.113.42\", \"\\\'remote command here\\\'\"]
+@
+
+having to write out the individual options and arguments and deal with
+escaping is a bit of an annoyance but that's /execvp(3)/ for you.
+
+The return tuple is the exit code from the child process, its entire @stdout@
+and its entire @stderr@, if any. Note that this is not a streaming interface,
+so if you're doing something that returns huge amounts of output you'll want
+to use something like __io-streams__ instead.
+
+(this wraps __typed-process__'s 'readProcess')
+-}
+execProcess :: [Rope] -> Program τ (ExitCode, Rope, Rope)
+execProcess [] = error "No command provided"
+execProcess (cmd : args) =
+    let cmdStr = fromRope cmd
+        argsStr = fromRope <$> args
+        task = proc cmdStr argsStr
+        task' = setStdin closed task
+     in do
+            debugS "command" task'
+
+            (exit, out, err) <- liftIO $ do
+                readProcess task'
+
+            return (exit, intoRope out, intoRope err)
 
 {- |
 A thread for concurrent computation. Haskell uses green threads: small lines
