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.6.9.4
+version:        0.7.0.0
 synopsis:       Opinionated Haskell Interoperability
 description:    A library to help build command-line programs, both tools and
                 longer-running daemons.
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
@@ -177,6 +177,7 @@
 import Data.List qualified as List (intersperse)
 import GHC.Conc (getNumProcessors, numCapabilities, setNumCapabilities)
 import GHC.IO.Encoding (setLocaleEncoding, utf8)
+import GHC.Stack (HasCallStack)
 import System.Directory
     ( findExecutable
     )
@@ -1120,7 +1121,7 @@
 
 @since 0.5.1
 -}
-queryOptionValue' :: Externalize ξ => LongName -> Program τ (Maybe ξ)
+queryOptionValue' :: (Externalize ξ) => LongName -> Program τ (Maybe ξ)
 queryOptionValue' name = do
     context <- ask
     let params = commandLineFrom context
@@ -1189,7 +1190,7 @@
 
 @since 0.6.2
 -}
-queryEnvironmentValue' :: Externalize ξ => LongName -> Program τ (Maybe ξ)
+queryEnvironmentValue' :: (Externalize ξ) => LongName -> Program τ (Maybe ξ)
 queryEnvironmentValue' name = do
     context <- ask
     let params = commandLineFrom context
@@ -1229,8 +1230,22 @@
         Nothing -> error "Attempted lookup of command but not a Complex Config"
 
 {- |
+Exception thrown by 'invalid'. It's not meant to be caught and so is not
+exposed publicly.
+
+@since 0.6.10
+-}
+data InvalidState = InvalidState
+    deriving (Show)
+
+instance Exception InvalidState
+
+{- |
 Illegal internal state resulting from what should be unreachable code or
 otherwise a programmer error.
 -}
-invalid :: Program τ α
-invalid = error "Invalid State"
+invalid :: (HasCallStack) => Program τ α
+invalid = do
+    critical "Invalid state reached"
+    write "error: invalid state"
+    terminate 99
diff --git a/lib/Core/Program/Threads.hs b/lib/Core/Program/Threads.hs
--- a/lib/Core/Program/Threads.hs
+++ b/lib/Core/Program/Threads.hs
@@ -126,24 +126,72 @@
 'Program', including sharing the user-defined application state value.
 
 If you want to find out what the result of a thread was use 'waitThread' on
-the 'Thread' object returned from this function. If you don't need the
-result, use 'forkThread_' instead.
+the 'Thread' object returned from this function. For example:
 
+@
+    t1 <- 'forkThread' $ do
+        'Core.Program.Logging.info' \"Doing interesting stuff concurrently\"
+        'pure' True
+
+    ...
+
+    result <- 'waitThread' t1
+
+    if result
+        then -- expected
+        else -- not good
+@
+
+If you don't need the result, you can use 'forkThread_' instead.
+
 Threads that are launched off as children are on their own! If the code in the
 child thread throws an exception that is /not/ caught within that thread, the
 exception will kill the thread. Threads dying without telling anyone is a bit
 of an anti-pattern, so this library logs a warning-level log message if this
 happens.
 
-(this wraps __base__'s 'Control.Concurrent.forkIO')
+(this function wraps __base__'s 'Control.Concurrent.forkIO')
 
+/Concerning telemetry/
+
+Note that threads inherit the telemetry state from their parent. If you are
+using the tracing features from __core-telemetry__ any telemetry registered in
+that side task will be included in the enclosing span active in the parent
+thread that spawned the thread:
+
+@
+    t2 <- 'forkThread' $ do
+        'Core.Program.Logging.info' \"Performing quick side task\"
+        'Core.Telemetry.Observability.telemetry'
+            [ ''Core.Telemetry.Observability.metric' \"counter\" 42
+            ]
+        ...
+
+@
+
+In this case the @\"counter\"@ field in the parent thread's current span will
+get the value @42@. This is appropriate for the common case where you are doing
+small side tasks concurrently to accelerate a larger computation.
+
+But at other times you are launching off a fully independent control flow and
+want it to have its own telemetry. In those cases, you'll want to start a new
+span (or even a new trace) immediately after forking the thread:
+
+@
+    'forkThread_' $ do
+        'Core.Telemetry.Observability.encloseSpan' \"subTask\" $ do
+            ...
+@
+
+any telemetry from this worker thread will be appropriately nested in a new
+child span called @\"subTask\"@.
+
 @since 0.2.7
 -}
 forkThread :: Program τ α -> Program τ (Thread α)
 forkThread program = do
     context <- ask
     let i = startTimeFrom context
-    let v = currentDatumFrom context
     let scope = currentScopeFrom context
 
     liftIO $ do
@@ -154,18 +202,9 @@
         start <- readMVar i
         i' <- newMVar start
 
-        -- we also need to fork the current Datum, in the same way that we do
-        -- when we create a nested span. We do this simply by creating a new
-        -- MVar so that when the new thread updates the attached metadata
-        -- it'll be evolving a different object.
-
-        datum <- readMVar v
-        v' <- newMVar datum
-
         let context' =
                 context
                     { startTimeFrom = i'
-                    , currentDatumFrom = v'
                     }
 
         -- fork, and run nested program
