diff --git a/lib/Core/Encoding/Json.hs b/lib/Core/Encoding/Json.hs
--- a/lib/Core/Encoding/Json.hs
+++ b/lib/Core/Encoding/Json.hs
@@ -19,36 +19,29 @@
 schema over time. For ease of exploration this module simply defines an
 easy to use intermediate type representing JSON as a format.
 
-To use this module, you may find the following imports helpful:
+Often you'll be working with literals directly in your code. While you can
+write:
 
 @
-\{\-\# LANGUAGE OverloadedStrings \#\-\}
-\{\-\# LANGUAGE OverloadedLists \#\-\}
-
-import "Data.HashMap.Strict" ('HashMap')
-import qualified "Data.HashMap.Strict" as 'HashMap'  -- from the __unordered-containers__ package.
-import "Data.Scientific" ('Scientific')              -- from the __scientific__ package
-import "Core.Encoding.Json"
+    j = 'JsonObject' ('intoMap' [('JsonKey' "answer", 'JsonNumber' 42)])
 @
 
-Often you'll be working with literals directly in your code. While you can
-write:
+and it would be correct, enabling:
 
 @
-    j = JsonObject (HashMap.fromList [(JsonKey "answer", JsonNumber 42)])
+\{\-\# LANGUAGE OverloadedStrings \#\-\}
+\{\-\# LANGUAGE OverloadedLists \#\-\}
 @
 
-and it would be correct, enabling @OverloadedStrings@ and @OverloadedLists@
 allows you to write:
 
 @
-    j = JsonObject [("answer", 42)]
+    j = 'JsonObject' [("answer", 42)]
 @
 
-which you is somewhat less cumbersome. You're certainly welcome to use the
-constructors if you find it makes for more readable code or if you need
-the type annotations.
-
+which you is somewhat less cumbersome in declaration-heavy code. You're
+certainly welcome to use the constructors if you find it makes for more
+readable code or if you need the type annotations.
 -}
 --
 -- As currently implemented this module, in conjunction with
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
@@ -70,9 +70,6 @@
       , retrieve
       , update
         {-* Useful actions -}
-      , write
-      , writeS
-      , writeR
       , output
         {-* Concurrency -}
       , Thread
@@ -93,9 +90,8 @@
     , ExceptionInLinkedThread(..), AsyncCancelled, race_)
 import Control.Concurrent.MVar (readMVar, putMVar, modifyMVar_)
 import Control.Concurrent.STM (atomically, check)
-import Control.Concurrent.STM.TQueue (TQueue, readTQueue
-    , writeTQueue, isEmptyTQueue)
-import qualified Control.Exception as Base (throwIO, evaluate)
+import Control.Concurrent.STM.TQueue (TQueue, readTQueue, isEmptyTQueue)
+import qualified Control.Exception as Base (throwIO)
 import Control.Exception.Safe (SomeException, Exception(displayException))
 import qualified Control.Exception.Safe as Safe (throw, catchesAsync)
 import Control.Monad (when, forever)
@@ -111,7 +107,6 @@
 import Core.Data.Structures
 import Core.Text.Bytes
 import Core.Text.Rope
-import Core.Text.Utilities
 import Core.System.Base
 import Core.Program.Context
 import Core.Program.Logging
@@ -378,66 +373,23 @@
 update = setApplicationState
 
 {-|
-Write the supplied text to @stdout@.
-
-This is for normal program output.
-
-@
-     'write' "Beginning now"
-@
--}
-write :: Rope -> Program τ ()
-write text = do
-    context <- ask
-    liftIO $ do
-        let out = outputChannelFrom context
-
-        !text' <- Base.evaluate text
-        atomically (writeTQueue out text')
-
-{-|
-Call 'show' on the supplied argument and write the resultant text to
-@stdout@.
-
-(This is the equivalent of 'print' from __base__)
--}
-writeS :: Show α => α -> Program τ ()
-writeS = write . intoRope . show
-
-{-|
-Pretty print the supplied argument and write the resultant text to
-@stdout@. This will pass the detected terminal width to the 'render'
-function, resulting in appopriate line wrapping when rendering your value.
--}
-writeR :: Render α => α -> Program τ ()
-writeR thing = do
-    context <- ask
-    liftIO $ do
-        let out = outputChannelFrom context
-        let columns = terminalWidthFrom context
-
-        let text = render columns thing
-        !text' <- Base.evaluate text
-        atomically (writeTQueue out text')
-
-{-|
 Write the supplied @Bytes@ to the given @Handle@. Note that in contrast to
 'write' we don't output a trailing newline.
 
 @
-    output h b
+    'output' h b
 @
 
-Do not use this to output to @stdout@ as that would bypass the mechanism
-used by the @write*@ functions to sequence output correctly. If you wish to
-write to the terminal use:
+Do /not/ use this to output to @stdout@ as that would bypass the mechanism
+used by the 'write'*, 'event', and 'debug'* functions to sequence output
+correctly. If you wish to write to the terminal use:
 
 @
-    write (intoRope b)
+    'write' ('intoRope' b)
 @
 
 (which is not /unsafe/, but will lead to unexpected results if the binary
-blob you pass in is not UTF-8 text).
+blob you pass in is other than UTF-8 text).
 -}
 output :: Handle -> Bytes -> Program τ ()
 output h b = liftIO $ do
diff --git a/lib/Core/Program/Logging.hs b/lib/Core/Program/Logging.hs
--- a/lib/Core/Program/Logging.hs
+++ b/lib/Core/Program/Logging.hs
@@ -5,11 +5,127 @@
 {-# LANGUAGE BangPatterns #-}
 {-# OPTIONS_HADDOCK prune #-}
 
+{-|
+Output and Logging from your program.
+
+Broadly speaking, there are two kinds of program: console tools invoked for
+a single purpose, and long-running daemons that effectively run forever.
+
+Tools tend to be run to either have an effect (in which case they tend not
+to say much of anything) or to report a result (which is usually printed to
+your terminal). This tends to be written to \"standard output\",
+traditionally abbreviated in code as @stdout@.
+
+Daemons, on the other hand, don't write their output to file descriptor 1;
+rather they tend to respond to requests by writing to files, replying over
+network sockets, or sending up smoke signals (@ECPUTOOHOT@, in case you're
+curious). What daemons /do/ output, however, is log messages.
+
+While there are many sophisticated logging services around that you can
+interact with directly, from the point of view of an individual /program/
+these tend to have faded away and have become more an aspect of the
+Infrastructure- or Platform-as-a-Service you're running on. Over the past
+few years containerization mechanisms like __docker__, then more recently
+container orchestration layers like __kubernetes__, have generally simply
+captured programs' standard output /as if it were the program's log output/
+and then sent that down external logging channels to whatever log analysis
+system is available. Even programs running locally under __systemd__ or
+similar tend to follow the same pattern; services write to @stdout@ and
+that output, as "logs", ends up being fed to the system journal.
+
+So with that in mind, in your program you will either be outputting results
+to @stdout@ or not writing there at all, and you will either be describing
+extensively what your application is up to, or not at all. 
+
+There is also a \"standard error\" file descriptor available. We recommend
+not using it. At best it is unclear what is written to @stderr@ and what
+isn't; at worse it is lost as many environments in the wild discard
+@stderr@ entirely. To avoid this most of the time people just combine them
+in the invoking shell with @2>&1@, which inevitably results in @stderr@
+text appearing in the middle of normal @stdout@ lines corrupting them.
+
+The original idea of standard error was to provde a way to adverse
+conditions without interrupting normal text output, but as we have just
+observed if it happens without context or out of order there isn't much
+point. Instead this library offers a mechanism which caters for the
+different /kinds/ of output in a unified, safe manner.
+
+== Three kinds of output/logging messages
+
+/Standard output/
+
+Your program's normal output to the terminal. This library provides the
+'write' (and 'writeS' and 'writeR') functions to send output to @stdout@.
+
+/Events/
+
+When running a tool, you sometimes need to know /what it is doing/ as it is
+carrying out its steps. The 'event' function allows you to emit descriptive
+messages to the log channel tracing the activities of your program.
+
+Ideally you would never need to turn this on in a command-line tool, but
+sometimes a user or operations engineer needs to see what an application is
+up to. These should be human readable status messages to convey a sense of
+progress.
+
+In the case of long-running daemons, 'event' can be used to describe
+high-level lifecycle events, to document individual requests, or even
+describing individual transitions in a request handler's state machine, all
+depending on the nature of your program.
+
+/Debugging/
+
+Programmers, on the other hand, often need to see the internal state of
+the program when /debugging/.
+
+You almost always you want to know the value of some variable or parameter,
+so the 'debug' (and 'debugS' and 'debugR') utility functions here send
+messages to the log channel prefixed with a label that is, by convention,
+the name of the value you are examining.
+
+The important distinction here is that such internal values are almost
+never useful for someone other than the person or team who wrote the code
+emitting it. Operations engineers might be asked by developers to turn on
+@--debug@ing and report back the results; but a user of your program is not
+going to do that in and of themselves to solve a problem.
+
+== Single output channel
+
+It is the easy to make the mistake of having multiple subsystems attempting
+to write to @stdout@ and these outputs corrupting each other, especially in
+a multithreaded language like Haskell. The output actions described here
+send all output to terminal down a single thread-safe channel. Output will
+be written in the order it was executed, and (so long as you don't use the
+@stdout@ Handle directly yourself) your terminal output will be sound.
+
+Passing @--verbose@ on the command-line of your program will cause 'event'
+to write its tracing messages to the terminal. This shares the same output
+channel as the 'write'@*@ functions and will /not/ cause corruption of your
+program's normal output.
+
+Passing @--debug@ on the command-line of your program will cause the
+'debug'@*@ actions to write their debug-level messages to the terminal.
+This shares the same output channel as above and again will not cause
+corruption of your program's normal output.
+
+== Logging channel
+
+/Event and debug messages are internally also sent to a "logging channel",/
+/as distinct from the "output" one. This would allow us to send them/
+/directly to a file, syslog, or network logging service, but this is/
+/as-yet unimplemented./
+-}
 module Core.Program.Logging
     (
         putMessage
       , Verbosity(..)
+        {-* Normal output -}
+      , write
+      , writeS
+      , writeR
+        {-* Event tracing -}
       , event
+        {-* Debugging -}
       , debug
       , debugS
       , debugR
@@ -94,6 +210,49 @@
   where
     pad = S.replicate len "0"
     len = digits - length str
+
+{-|
+Write the supplied text to @stdout@.
+
+This is for normal program output.
+
+@
+     'write' "Beginning now"
+@
+-}
+write :: Rope -> Program τ ()
+write text = do
+    context <- ask
+    liftIO $ do
+        let out = outputChannelFrom context
+
+        !text' <- evaluate text
+        atomically (writeTQueue out text')
+
+{-|
+Call 'show' on the supplied argument and write the resultant text to
+@stdout@.
+
+(This is the equivalent of 'print' from __base__)
+-}
+writeS :: Show α => α -> Program τ ()
+writeS = write . intoRope . show
+
+{-|
+Pretty print the supplied argument and write the resultant text to
+@stdout@. This will pass the detected terminal width to the 'render'
+function, resulting in appopriate line wrapping when rendering your value.
+-}
+writeR :: Render α => α -> Program τ ()
+writeR thing = do
+    context <- ask
+    liftIO $ do
+        let out = outputChannelFrom context
+        let columns = terminalWidthFrom context
+
+        let text = render columns thing
+        !text' <- evaluate text
+        atomically (writeTQueue out text')
 
 {-|
 Note a significant event, state transition, status, or debugging
diff --git a/lib/Core/Text/Rope.hs b/lib/Core/Text/Rope.hs
--- a/lib/Core/Text/Rope.hs
+++ b/lib/Core/Text/Rope.hs
@@ -80,10 +80,10 @@
     , contains
       {-* Interoperation and Output -}
     , Textual(fromRope, intoRope, append)
-    , unsafeIntoRope
     , hWrite
       {-* Internals -}
     , unRope
+    , unsafeIntoRope
     , Width(..)
     ) where
 
diff --git a/unbeliever.cabal b/unbeliever.cabal
--- a/unbeliever.cabal
+++ b/unbeliever.cabal
@@ -4,17 +4,25 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 1a286309370d68d11f651b386263a174b2744b09bf4f1919f38209b05542a639
+-- hash: dc4af83e0e28d0d48d8e6550b7def407de3c5e3cc34d5c6380ecbfdc74229985
 
 name:           unbeliever
-version:        0.7.3.0
+version:        0.8.0.0
 synopsis:       Opinionated Haskell Interoperability
 description:    A library to help build command-line programs, both tools and
                 longer-running daemons.
                 .
-                Useful starting points are "Core.Program.Execute" and "Core.Text.Rope".
+                A description of this package, a list of features, and some background
+                to its design is contained in the
+                <https://github.com/oprdyn/unbeliever/blob/master/README.markdown README>
+                on GitHub.
+                .
+                Useful starting points in the documentation are "Core.Program.Execute"
+                and "Core.Text.Rope".
 category:       System
 stability:      experimental
+homepage:       https://github.com/oprdyn/unbeliever#readme
+bug-reports:    https://github.com/oprdyn/unbeliever/issues
 author:         Andrew Cowie <andrew@operationaldynamics.com>
 maintainer:     Andrew Cowie <andrew@operationaldynamics.com>
 copyright:      © 2018 Operational Dynamics Consulting Pty Ltd, and Others
@@ -22,6 +30,10 @@
 license-file:   LICENCE
 tested-with:    GHC == 8.4
 build-type:     Simple
+
+source-repository head
+  type: git
+  location: https://github.com/oprdyn/unbeliever
 
 flag development
   manual: True
